Partilhar via


Namescopes XAML

Um namescope XAML armazena relações entre os nomes de objetos definidos por XAML e seus equivalentes de instância. Este conceito é semelhante ao significado mais amplo do termo namescope em outras linguagens e tecnologias de programação.

Como os namescopes XAML são definidos

Os nomes em namescopes XAML permitem que o código do usuário faça referência aos objetos que foram inicialmente declarados em XAML. O resultado interno da análise de XAML é que o runtime cria um conjunto de objetos que retêm algumas ou todas as relações que esses objetos tinham nas declarações XAML. Essas relações são mantidas como propriedades de objeto específicas dos objetos criados ou são expostas a métodos utilitários nas APIs do modelo de programação.

O uso mais comum de um nome em um namescope XAML é como uma referência direta a uma instância de objeto, que é habilitada pela passagem de compilação de marcação como uma ação de build de projeto, combinada com um método InitializeComponent gerado nos modelos de classe parcial.

Você também pode usar o método utilitário FindName em tempo de execução para retornar uma referência a objetos que foram definidos com um nome na marcação XAML.

Mais sobre ações de build e XAML

O que acontece tecnicamente é que o próprio XAML passa por uma passagem do compilador de marcação ao mesmo tempo em que o XAML e a classe parcial que ele define para code-behind são compilados juntos. Cada elemento de objeto com um atributo Name ou x:Name definido na marcação gera um campo interno com um nome que corresponde ao nome XAML. Este campo está inicialmente vazio. Em seguida, a classe gera um método InitializeComponent que é chamado somente depois que todo o XAML é carregado. Dentro da lógica InitializeComponent, cada campo interno é preenchido com o valor retornado FindName para a cadeia de caracteres de nome equivalente. Você pode observar essa infraestrutura por si mesmo examinando os arquivos ".g" (gerados) criados para cada página XAML na subpasta /obj de um projeto de aplicativo do Tempo de Execução do Windows após a compilação. Você também pode ver os campos e o método InitializeComponent como membros de seus assemblies resultantes se refletir sobre eles ou examinar o conteúdo do idioma da interface.

Observação Especificamente para aplicativos de extensões de componente do Visual C++ (C++/CX), um campo de suporte para uma referência x:Name não é criado para o elemento raiz de um arquivo XAML. Se você precisar fazer referência ao objeto raiz do code-behind do C++/CX, use outras APIs ou a passagem de árvore. Por exemplo, você pode chamar FindName para um elemento filho nomeado conhecido e, em seguida, chamar Parent.

Criando objetos em tempo de execução com XamlReader.Load

O XAML também pode ser usado como a entrada de cadeia de caracteres para o método XamlReader.Load , que atua de forma análoga à operação inicial de análise de origem XAML. XamlReader.Load cria uma nova árvore desconectada de objetos em tempo de execução. A árvore desconectada pode então ser anexada a algum ponto na árvore de objetos principal. Você deve conectar explicitamente a árvore de objetos criada, adicionando-a a uma coleção de propriedades de conteúdo, como Children, ou definindo alguma outra propriedade que use um valor de objeto (por exemplo, carregando um novo ImageBrush para um valor de propriedade Fill).

Implicações do namescope XAML de XamlReader.Load

O namescope XAML preliminar definido pela nova árvore de objetos criada por XamlReader.Load avalia todos os nomes definidos no XAML fornecido quanto à exclusividade. Se os nomes no XAML fornecido não forem internamente exclusivos neste momento, XamlReader.Load gerará uma exceção. A árvore de objetos desconectada não tenta mesclar seu namescope XAML com o namescope XAML do aplicativo principal, se ou quando estiver conectada à árvore de objetos do aplicativo principal. Depois de conectar as árvores, seu aplicativo tem uma árvore de objetos unificada, mas essa árvore tem namescopes XAML discretos dentro dela. As divisões ocorrem nos pontos de conexão entre objetos, onde você define alguma propriedade como o valor retornado de uma chamada XamlReader.Load .

A complicação de ter namescopes XAML discretos e desconectados é que as chamadas para o método FindName , bem como referências diretas de objetos gerenciados, não operam mais em um namescope XAML unificado. Em vez disso, o objeto específico no qual FindName é chamado implica o escopo, com o escopo sendo o namescope XAML no qual o objeto de chamada está. No caso de referência de objeto gerenciado direto, o escopo é implícito pela classe em que o código existe. Normalmente, o code-behind para interação em tempo de execução de uma "página" de conteúdo do aplicativo existe na classe parcial que dá suporte à "página" raiz e, portanto, o namescope XAML é o namescope XAML raiz.

Se você chamar FindName para obter um objeto nomeado no namescope XAML raiz, o método não encontrará os objetos de um namescope XAML discreto criado por XamlReader.Load. Por outro lado, se você chamar FindName de um objeto obtido de fora do namescope XAML discreto, o método não encontrará objetos nomeados no namescope XAML raiz.

Esse problema de namescope XAML discreto afeta apenas a localização de objetos por nome em namescopes XAML ao usar a chamada FindName .

Para obter referências a objetos definidos em um namescope XAML diferente, você pode usar várias técnicas:

  • Percorra toda a árvore em etapas discretas com as propriedades Parent e/ou collection que são conhecidas por existirem em sua estrutura de árvore de objetos (como a coleção retornada por Panel.Children).
  • Se você estiver chamando de um namescope XAML discreto e quiser o namescope XAML raiz, é sempre fácil obter uma referência à janela principal exibida no momento. Você pode obter a raiz visual (o elemento XAML raiz, também conhecido como fonte de conteúdo) da janela do aplicativo atual em uma linha de código com a chamada Window.Current.Content. Em seguida, você pode converter para FrameworkElement e chamar FindName desse escopo.
  • Se você estiver chamando do namescope XAML raiz e quiser um objeto dentro de um namescope XAML discreto, a melhor coisa a fazer é planejar com antecedência em seu código e manter uma referência ao objeto que foi retornado por XamlReader.Load e, em seguida, adicionado à árvore de objetos principal. Esse objeto agora é um objeto válido para chamar FindName dentro do namescope XAML discreto. Você pode manter esse objeto disponível como uma variável global ou passá-lo usando parâmetros de método.
  • Você pode evitar nomes e considerações de namescope XAML inteiramente examinando a árvore visual. A API VisualTreeHelper permite que você percorra a árvore visual em termos de objetos pai e coleções filho, com base puramente na posição e no índice.

Namescopes XAML em modelos

Os modelos em XAML fornecem a capacidade de reutilizar e reaplicar o conteúdo de maneira direta, mas os modelos também podem incluir elementos com nomes definidos no nível do modelo. Esse mesmo modelo pode ser usado várias vezes em uma página. Por esse motivo, os modelos definem seus próprios namescopes XAML, independentemente da página em que o estilo ou modelo é aplicado. Considere este exemplo:

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  >
  <Page.Resources>
    <ControlTemplate x:Key="MyTemplate">
      ....
      <TextBlock x:Name="MyTextBlock" />
    </ControlTemplate>
  </Page.Resources>
  <StackPanel>
    <SomeControl Template="{StaticResource MyTemplate}" />
    <SomeControl Template="{StaticResource MyTemplate}" />
  </StackPanel>
</Page>

Aqui, o mesmo modelo é aplicado a dois controles diferentes. Se os modelos não tivessem namescopes XAML discretos, o nome "MyTextBlock" usado no modelo causaria uma colisão de nomes. Cada instanciação do modelo tem seu próprio namescope de XAML, portanto, neste exemplo, cada namescope de XAML de modelo instanciado conteria exatamente um nome. No entanto, o namescope XAML raiz não contém o nome de nenhum dos modelos.

Devido aos namescopes XAML separados, localizar elementos nomeados em um modelo do escopo da página em que o modelo é aplicado requer uma técnica diferente. Em vez de chamar FindName em algum objeto na árvore de objetos, primeiro você obtém o objeto que tem o modelo aplicado e, em seguida, chama GetTemplateChild. Se você for um autor de controle e estiver gerando uma convenção em que um elemento nomeado específico em um modelo aplicado é o destino de um comportamento definido pelo próprio controle, poderá usar o método GetTemplateChild do código de implementação do controle. O método GetTemplateChild é protegido, portanto, somente o autor do controle tem acesso a ele. Além disso, há convenções que os autores de controle devem seguir para nomear partes e partes de modelo e relatá-las como valores de atributo aplicados à classe de controle. Essa técnica torna os nomes de partes importantes detectáveis para controlar os usuários que desejam aplicar um modelo diferente, que precisaria substituir as partes nomeadas para manter a funcionalidade de controle.