Partilhar 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. WPF expõe duas classes auxiliares que fornecem uma visão metafórica 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 apresenta LogicalTreeHelper 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.

Nota

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 tempo de execução em que a metáfora da árvore deixa de fazer sentido. 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 configurando as propriedades dos objetos que suportam esses elementos. Por exemplo, você adiciona itens a um controle ListBox manipulando sua propriedade Items. Ao fazer isso, você está colocando itens no ItemCollection que é o valor da propriedade Items. Da mesma forma, para adicionar objetos a um DockPanel, você manipula seu valor de 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 propriedades Items 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ê processasse 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), então 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 de objeto resultante não inclui literalmente ListBox.Items. No entanto, ele tem uma propriedade ListBox chamada Items que contém um ItemCollection, e essa ItemCollection é inicializada, mas é vazia quando o XAML ListBox é processado. Em seguida, cada elemento de objeto filho que existe como conteúdo para o ListBox é adicionado ao ItemCollection por chamadas do parser para 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 objetos inteiro que existe para a interface do usuário do aplicativo em tempo de execução, mesmo com os itens de sintaxe implícita do XAML excluídos. O principal motivo para isso são os componentes visuais e os modelos. Por exemplo, considere o Button. A árvore lógica relata o objeto Button e também sua cadeia de caracteres 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 do jeito que ele faz porque um modelo de controle Button específico foi aplicado. Os elementos visuais provenientes de um modelo aplicado (como o elemento visual Border de cinza escuro ao redor do botão) não são relatados na árvore lógica, mesmo que você esteja consultando a árvore lógica durante a execução (como ao manipular um evento de entrada da interface do usuário visível e então ler a árvore lógica). Para localizar os elementos visuais do modelo, você precisaria, em vez disso, examinar a árvore visual.

Para obter mais informações sobre como a sintaxe XAML é mapeada para o gráfico de objetos 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 em tempo de execução no nível do framework, que exclui visuais, mas é adequada para diversas operações de consulta sobre a composição do seu próprio aplicativo em tempo de execução.

Além disso, as referências de recursos estáticos e dinâmicos são resolvidas percorrendo a árvore lógica para cima em busca de Resources coleções no objeto solicitante inicial e, em seguida, continuando a subir na árvore lógica e verificando cada FrameworkElement (ou FrameworkContentElement) em busca de outro valor Resources 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 consulta, veja XAML Recursos.

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 realmente usa a API LogicalTreeHelper, a árvore lógica às vezes contém nós que não são FrameworkElement ou FrameworkContentElement. Por exemplo, a árvore lógica relata o valor Text 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 de Valor da Propriedade

A herança de valores de propriedade se dá 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 a classe FrameworkPropertyMetadata ao nível de estrutura do WPF. Portanto, tanto o pai que contém o valor original quanto o objeto filho que herda esse valor devem ser FrameworkElement ou FrameworkContentElement, e ambos 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 do valor da propriedade pode se perpetuar através de um objeto interveniente que não está na árvore lógica. Principalmente, isso é relevante para que os elementos de modelo usem quaisquer valores de propriedade herdados definidos na instância que é modelo ou em níveis ainda mais altos de composição no nível da página e, portanto, mais alto na árvore lógica. Para que a herança do valor da propriedade funcione consistentemente em tal limite, a propriedade herdada deve ser registrada como uma propriedade anexada e você deverá seguir esse padrão 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 completamente prevista por um método utilitário de uma classe auxiliar, mesmo em tempo de execução. Para obter mais informações, consulte herança de valor de 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 classe base Visual. 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 característica da árvore visual como parte da programação convencional de aplicativos WPF é que as rotas de eventos para um evento encaminhado percorrem principalmente a árvore visual, não a árvore lógica. Essa sutileza do comportamento de evento roteado pode não ser imediatamente aparente, a menos que você seja um autor de controle. O roteamento de eventos por meio da árvore visual permite controles que implementam a composição visualmente para manipular eventos ou criar configuradores 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 de Visual e não têm uma representação visual. Para poder aparecer em uma interface do usuário, um ContentElement deve ser hospedado em um host de conteúdo que seja um Visual e 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, ele pode participar de certos processos relacionados à árvore que normalmente estão associados à árvore visual. Geralmente, a classe de host FrameworkElement inclui código de implementação que adiciona qualquer ContentElement hospedado à 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 roteia para qualquer elemento diferente de si mesmo.

Passagem de árvore

A classe LogicalTreeHelper fornece os métodos GetChildren, GetParente 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 desenvolvedores de controladores que optam por não derivar de padrões de controle designados, como ItemsControl ou Panel, onde as propriedades da coleção já estão definidas, e os quais pretendem fornecer suporte à sua própria propriedade de coleção.

A árvore visual também dá suporte a uma classe auxiliar para percorrer a árvore visual, VisualTreeHelper. A árvore visual não é exposta de maneira tão conveniente através de propriedades específicas de controle, portanto, a classe VisualTreeHelper é a maneira recomendada de percorrer a árvore visual se isso for necessário para seu cenário de programação. Para obter mais informações, consulte Visão Geral da Renderização Gráfica do WPF.

Nota

À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 modificar o modelo definindo a propriedade Template nas instâncias. Além disso, até mesmo o usuário final pode influenciar o modelo aplicado ao alterar o tema do sistema.

Rotas para eventos roteáveis 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 diretamente relacionada que possa ser usada para "percorrer" a rota do evento, independente de acionar um evento que realmente roteia. Há uma classe que representa a rota, EventRoute, mas os métodos dessa classe geralmente são apenas para uso interno.

Dicionários de recursos e árvores

A pesquisa do dicionário de recursos para todas as Resources definidas em uma página atravessa basicamente 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 propriedade Resources que contenha um ResourceDictionary, portanto, não há nenhum benefício em atravessar a árvore visual em busca de recursos chaveados de um ResourceDictionary.

No entanto, a pesquisa de recursos também pode se estender além da árvore lógica imediata. Para marcação de aplicativo, a pesquisa de recursos pode continuar aos dicionários de recursos no nível do aplicativo e então aos suporte a temas e valores de 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