Compartilhar via


Árvores no WPF

Em muitas tecnologias, elementos e componentes são organizados em uma estrutura de árvore em que os desenvolvedores manipulam diretamente os nós de objeto na árvore para afetar a renderização ou o comportamento de um aplicativo. O Windows Presentation Foundation (WPF) também usa várias metáforas de estrutura de árvore para definir relações entre elementos do programa. Na maioria das vezes, os desenvolvedores do WPF podem criar um aplicativo no código ou definir partes do aplicativo no XAML enquanto pensam conceitualmente sobre a metáfora da árvore de objetos, mas chamarão uma API específica ou usarão marcação específica para fazer isso em vez de alguma API de manipulação de árvore de objetos geral, como você pode usar no XML DOM. O WPF expõe duas classes auxiliares que fornecem uma exibição metáfora de árvore, LogicalTreeHelper e VisualTreeHelper. Os termos de árvore visual e árvore lógica também são usados na documentação do WPF porque essas mesmas árvores são úteis para entender o comportamento de determinados recursos principais do WPF. Este tópico define o que a árvore visual e a árvore lógica representam, discute como essas árvores se relacionam com um conceito geral de árvore de objetos e introduz LogicalTreeHelpers e VisualTreeHelpers.

Árvores no WPF

A estrutura de árvore mais completa no WPF é a árvore de objetos. Se você definir uma página de aplicativo em XAML e carregar o XAML, a estrutura da árvore será criada com base nas relações de aninhamento dos elementos na marcação. Se você definir um aplicativo ou uma parte do aplicativo no código, a estrutura de árvore será criada com base em como você atribui valores de propriedade para propriedades que implementam o modelo de conteúdo para um determinado objeto. No WPF, há duas maneiras pelas quais a árvore de objetos completa é conceitualizada e pode ser relatada à sua API pública: como a árvore lógica e como a árvore visual. As distinções entre árvore lógica e árvore visual nem sempre são necessariamente importantes, mas ocasionalmente podem causar problemas com determinados subsistemas do WPF e afetar as escolhas que você faz na marcação ou no código.

Mesmo que você nem sempre manipule diretamente a árvore lógica ou a árvore visual, entender os conceitos de como as árvores interagem é útil para entender o WPF como uma tecnologia. Pensar no WPF como uma metáfora de árvore de algum tipo também é crucial para entender como a herança de propriedade e o roteamento de eventos funcionam no WPF.

Observação

Como a árvore de objetos é mais um conceito do que uma API real, outra maneira de pensar no conceito é como um grafo de objeto. Na prática, há relações entre objetos em momento de execução onde a metáfora da árvore tende a falhar. No entanto, particularmente com a interface do usuário definida por XAML, a metáfora da árvore é relevante o suficiente para que a maioria da documentação do WPF use o termo "árvore de objetos" ao referenciar esse conceito geral.

A árvore lógica

No WPF, você adiciona conteúdo aos elementos da interface do usuário definindo as propriedades dos objetos que dão suporte a esses elementos. Por exemplo, você adiciona itens a um ListBox controle manipulando sua Items propriedade. Ao fazer isso, você está colocando itens no ItemCollection valor da Items propriedade. Da mesma forma, para adicionar objetos a um DockPanel, você manipula o valor da propriedade Children. Aqui, você está adicionando objetos ao UIElementCollection. Para obter um exemplo de código, consulte Como adicionar um elemento dinamicamente.

Em XAML (Extensible Application Markup Language), quando você coloca itens de lista em um ListBox ou controles ou outros elementos de interface do usuário em um DockPanel, você também usa as Items propriedades e Children , explicitamente ou implicitamente, como no exemplo a seguir.

<DockPanel
  Name="ParentElement"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  >
  <!--implicit: <DockPanel.Children>-->
  <ListBox DockPanel.Dock="Top">
    <!--implicit: <ListBox.Items>-->
    <ListBoxItem>
      <TextBlock>Dog</TextBlock>
    </ListBoxItem>
    <ListBoxItem>
      <TextBlock>Cat</TextBlock>
    </ListBoxItem>
    <ListBoxItem>
      <TextBlock>Fish</TextBlock>
    </ListBoxItem>
  <!--implicit: </ListBox.Items>-->
  </ListBox>
  <Button Height="20" Width="100" DockPanel.Dock="Top">Buy a Pet</Button>
  <!--implicit: </DockPanel.Children>-->
</DockPanel>

Se você fosse processar esse XAML como XML em um modelo de objeto de documento, e se tivesse incluído as tags comentadas como implícitas (o que teria sido legal), a árvore DOM XML resultante teria incluído elementos para <ListBox.Items> e outros itens implícitos. Mas o XAML não processa dessa forma quando você lê a marcação e grava em objetos; o grafo resultante de objetos não inclui literalmente ListBox.Items. Contudo, ele tem uma ListBox propriedade denominada Items que inclui um ItemCollection, e que ItemCollection é inicializado, mas vazio quando o ListBox XAML é processado. Em seguida, cada elemento de objeto filho que existe como conteúdo para o ListBox é adicionado ao ItemCollection por meio de chamadas de analisador para o ItemCollection.Add. Este exemplo de processamento de XAML em uma árvore de objetos é até agora aparentemente um exemplo em que a árvore de objetos criada é basicamente a árvore lógica.

No entanto, a árvore lógica não é o grafo de objeto inteiro que existe para a interface do usuário do aplicativo em tempo de execução, mesmo com os itens de sintaxe implícita XAML fatorados. O principal motivo para isso são visuais e modelos. Por exemplo, considere o Button. A árvore lógica reporta o Button objeto e também sua string Content. Mas há mais nesse botão na árvore de objetos em tempo de execução. Em particular, o botão só aparece na tela da maneira como ele faz porque um modelo de controle específico Button foi aplicado. Os visuais provenientes de um modelo aplicado (como o modelo definido Border de cinza escuro ao redor do botão visual) não são relatados na árvore lógica, mesmo se você estiver olhando para a árvore lógica durante o tempo de execução (como manipular um evento de entrada da interface do usuário visível e ler a árvore lógica). Para, em vez disso, localizar os visuais de modelo, você precisaria examinar a árvore visual.

Para obter mais informações sobre como a sintaxe XAML é mapeada para o grafo de objeto criado e a sintaxe implícita em XAML, consulte sintaxe XAML em detalhes ou XAML no WPF.

A finalidade da árvore lógica

A árvore lógica existe para que os modelos de conteúdo possam iterar prontamente sobre seus possíveis objetos filho e para que os modelos de conteúdo possam ser extensíveis. Além disso, a árvore lógica fornece uma estrutura para determinadas notificações, como quando todos os objetos na árvore lógica são carregados. Basicamente, a árvore lógica é uma aproximação de um grafo de objetos de tempo de execução no nível do framework, que exclui visuais, mas é adequada para muitas operações de consulta em relação à composição do seu próprio aplicativo de tempo de execução.

Além disso, as referências de recursos estáticos e dinâmicos são resolvidas olhando para cima por meio da árvore lógica para Resources coleções no objeto de solicitação inicial e, em seguida, continuando a árvore lógica e verificando cada FrameworkElement um (ou FrameworkContentElement) para outro Resources valor que contenha um ResourceDictionary, possivelmente contendo essa chave. A árvore lógica é usada para pesquisa de recursos quando a árvore lógica e a árvore visual estão presentes. Para obter mais informações sobre dicionários de recursos e pesquisa, consulte Recursos XAML.

Composição da árvore lógica

A árvore lógica é definida no nível da estrutura do WPF, o que significa que o elemento base do WPF mais relevante para operações de árvore lógica é FrameworkElement ou FrameworkContentElement. No entanto, como você pode ver se você realmente usa a LogicalTreeHelper API, a árvore lógica às vezes contém nós que não são nem FrameworkElementFrameworkContentElement. Por exemplo, a árvore lógica relata o Text valor de um TextBlock, que é uma cadeia de caracteres.

Sobrescrevendo a árvore lógica

Autores de controle avançado podem substituir a árvore lógica substituindo várias APIs que definem como um objeto geral ou modelo de conteúdo adiciona ou remove objetos dentro da árvore lógica. Para obter um exemplo de como substituir a árvore lógica, consulte Substituir a Árvore Lógica.

Herança do valor da propriedade

A herança do valor da propriedade funciona através de uma árvore híbrida. Os metadados reais que contêm a propriedade Inherits, que habilita a herança de propriedade, são da classe ao nível de estrutura do WPF FrameworkPropertyMetadata. Portanto, tanto o pai que contém o valor original quanto o objeto filho que herda esse valor devem ser FrameworkElement ou FrameworkContentElementambos devem fazer parte de alguma árvore lógica. No entanto, para propriedades existentes do WPF que dão suporte à herança de propriedade, a herança de valor de propriedade pode se perpetuar através de um objeto intermediário que não está na árvore lógica. Principalmente, isso é relevante para que os elementos dentro do modelo usem quaisquer valores de propriedade herdados definidos na instância modelada ou em níveis ainda mais altos de composição no nível da página, portanto, mais altos na árvore lógica. Para que a herança do valor da propriedade funcione consistentemente através de tal limite, a propriedade herdada deve ser registrada como uma propriedade anexada, e você deve seguir essa abordagem se pretender definir uma propriedade de dependência personalizada com comportamento de herança de propriedade. A árvore exata usada para herança de propriedade não pode ser totalmente antecipada por um método utilitário de classe auxiliar, mesmo em tempo de execução. Para obter mais informações, consulte Herança do Valor da Propriedade.

A árvore visual

Além do conceito da árvore lógica, também há o conceito da árvore visual no WPF. A árvore visual descreve a estrutura de objetos visuais, conforme representado pela Visual classe base. Ao escrever um modelo para um controle, você está definindo ou redefinindo a árvore visual que se aplica a esse controle. A árvore visual também é de interesse para desenvolvedores que desejam um controle de nível inferior sobre o desenho por motivos de desempenho e otimização. Uma exposição da árvore visual como parte da programação convencional de aplicativos WPF é que as rotas de evento para um evento roteado viajam principalmente ao longo da árvore visual, não da árvore lógica. Essa sutileza do comportamento de evento roteado pode não ser imediatamente aparente, a menos que você seja um criador de controles. O roteamento de eventos por meio da árvore visual permite controles que fazem a composição no nível visual para lidar com eventos ou criar definidores de eventos.

Árvores, elementos de conteúdo e hosts de conteúdo

Elementos de conteúdo (classes que derivam de ContentElement) não fazem parte da árvore visual; eles não herdam Visual e não têm uma representação visual. Para aparecer em uma interface do usuário, um ContentElement deve estar hospedado em um host de conteúdo que seja tanto um Visual quanto um participante da árvore lógica. Normalmente, esse objeto é um FrameworkElement. Você pode conceituar que o host de conteúdo é um pouco como um "navegador" para o conteúdo e escolhe como exibir esse conteúdo dentro da região da tela que o host controla. Quando o conteúdo é hospedado, pode tornar-se um participante em certos processos de árvore que normalmente estão associados à árvore visual. Geralmente, a FrameworkElement classe host inclui código de implementação que adiciona qualquer host ContentElement à rota de evento por meio de subnodos da árvore lógica de conteúdo, mesmo que o conteúdo hospedado não faça parte da árvore visual verdadeira. Isso é necessário para que um ContentElement possa originar um evento roteado que seja direcionado para qualquer elemento diferente dele mesmo.

Passagem de árvore

A classe LogicalTreeHelper fornece os métodos GetChildren, GetParent e FindLogicalNode para passagem de árvore lógica. Na maioria dos casos, você não deve ter que atravessar a árvore lógica dos controles existentes, pois esses controles quase sempre expõem seus elementos filho lógicos como uma propriedade de coleção dedicada que dá suporte ao acesso à coleção, como Add, um indexador e assim por diante. Percurso de árvore é principalmente um cenário usado por autores de controle que preferem não se basear em padrões de controle pretendidos, como ItemsControl ou Panel, onde as propriedades da coleção já estão definidas, e que pretendem fornecer suporte às suas próprias propriedades de coleção.

A árvore visual também dá suporte a uma classe auxiliar para traversão da árvore visual, VisualTreeHelper. A árvore visual não é exposta de forma tão conveniente por meio de propriedades específicas do controle, portanto, a classe VisualTreeHelper é a maneira recomendada de percorrer a árvore visual se isso for necessário para o seu cenário de programação. Para obter mais informações, consulte Visão geral de renderização de gráficos do WPF.

Observação

Às vezes, é necessário examinar a árvore visual de um modelo aplicado. Você deve ter cuidado ao usar essa técnica. Mesmo que você esteja percorrendo uma árvore visual para um controle no qual você define o modelo, os consumidores do seu controle sempre poderão alterar o modelo definindo a propriedade Template em instâncias, e o usuário final também pode influenciar o modelo aplicado ao alterar o tema do sistema.

Rotas para eventos roteados como uma "árvore"

Conforme mencionado antes, a rota de qualquer determinado evento roteado percorre um único caminho predeterminado de uma árvore que é um híbrido das representações de árvore visual e lógica. A rota do evento pode percorrer as direções para cima ou para baixo dentro da árvore, dependendo se é um evento roteado de túnel ou de borbulha. O conceito de rota de evento não tem uma classe auxiliar de suporte diretamente que possa ser usada para "andar" a rota do evento independentemente de gerar um evento que realmente roteia. Há uma classe que representa a rota, EventRoutemas os métodos dessa classe geralmente são apenas para uso interno.

Dicionários de recursos e árvores

A busca no dicionário de recursos para tudo o que está definido em uma página percorre essencialmente a árvore lógica. Objetos que não estão na árvore lógica podem referenciar recursos chaveados, mas a sequência de pesquisa de recursos começa no ponto em que esse objeto está conectado à árvore lógica. No WPF, somente nós de árvore lógica podem ter uma Resources propriedade que contenha um ResourceDictionary, portanto, não há nenhum benefício em atravessar a árvore visual em busca de recursos chaveados a partir de um ResourceDictionary.

No entanto, a pesquisa de recursos também pode se estender além da árvore lógica imediata. Para a marcação do aplicativo, a pesquisa de recursos pode prosseguir para dicionários de recursos no nível do aplicativo e depois para suporte de tema e valores do sistema, que são referenciados como propriedades estáticas ou chaves. Os próprios temas também poderão referenciar valores do sistema fora da árvore lógica do tema se as referências de recurso forem dinâmicas. Para obter mais informações sobre dicionários de recursos e a lógica de pesquisa, consulte Recursos XAML.

Consulte também