Á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. Em geral, os desenvolvedores de WPF conseguem criar um aplicativo em código ou definir partes do aplicativo em XAML enquanto pensam conceitualmente sobre a metáfora da árvore de objeto, mas chamarão uma API específica ou usarão marcação específica para fazer isso em vez de alguma API geral de manipulação de árvore de objeto, como a que poderia ser usada em XML DOM. O WPF expõe duas classes auxiliares que fornecem uma exibição LogicalTreeHelper de metáfora em árvore e VisualTreeHelper. Os termos “árvore visual” e “árvore lógica” também são utilizados na documentação do WPF porque essas mesmas árvores são úteis para entender o comportamento de alguns 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 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, em seguida, carregar o XAML, a estrutura de á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 em código, a estrutura de árvore será criada com base em como os valores de propriedade são designados para propriedades que implementam o modelo de conteúdo de um determinado objeto. No WPF, há duas maneiras pelas quais a árvore de objetos completa é conceituada 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 WPF e afetar as escolhas que você faz na marcação ou no código.

Apesar de a árvore lógica ou a árvore visual não serem sempre manipuladas diretamente, entender os conceitos de como elas 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 de uma API real, outra maneira de pensar o conceito é como um grafo de objeto. Na prática, existem relações entre objetos em tempo de execução em que a metáfora de árvore travará. No entanto, especialmente com a interface do usuário definida em XAML, a metáfora da árvore é relevante o suficiente para a maior parte da documentação do WPF usar o termo “árvore de objetos” ao fazer referência a esse conceito geral.

A árvore lógica

No WPF, você adiciona conteúdo aos elementos da interface do usuário definindo 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 que é o valor da Items propriedade. Da mesma forma, para adicionar objetos a um DockPanel, você manipula seu Children valor de propriedade. 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 ou controles ou outros elementos da interface do usuário em um ListBoxDockPanel, você também usa as Items propriedades e Children , explícita 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 do Documento e se tivesse incluído as marcas de comentário como implícitas (o que seria legal), a árvore DOM XML resultante incluiria elementos para o <ListBox.Items> e os outros itens implícitos. Contudo, o XAML não processa dessa forma quando você lê a marcação e grava em objetos; o grafo do objeto resultante não inclui, literalmente, o ListBox.Items. No entanto, ele tem uma ListBox propriedade chamada Items que contém um ItemCollection, e que ItemCollection é inicializada, mas vazia quando o ListBox XAML é processado. Em seguida, cada elemento de objeto filho que existe como conteúdo para o ListBox é adicionado às ItemCollection chamadas do analisador por .ItemCollection.Add Esse exemplo de processamento de XAML em uma árvore de objetos é, até o momento, aparentemente um exemplo em que a árvore de objetos criada é, basicamente, a árvore lógica.

No entanto, a árvore lógica não é o gráfico 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. A principal razão para isso são visuais e modelos. Por exemplo, considere o Buttonarquivo . A árvore lógica relata o Button objeto e também sua cadeia de caracteres Content. Porém, esse botão contém mais recursos na árvore de objetos de tempo de execução. Em particular, o botão só aparece na tela da maneira que aparece porque um modelo de controle específico Button foi aplicado. Os elementos 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 examinando a árvore lógica durante o tempo de execução (como manipular um evento de entrada da interface do usuário visível e, em seguida, ler a árvore lógica). Para encontrar os visuais do modelo, seria necessário examinar a árvore visual.

Para obter mais informações sobre como a sintaxe XAML é mapeada para o gráfico 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 ser iterados imediatamente sobre os 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 nos casos em que todos os objetos na árvore lógica são carregados. Basicamente, a árvore lógica é uma aproximação de um grafo de objeto de tempo de execução no nível da estrutura, que exclui visuais, mas é adequada para várias 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 através da árvore lógica para coleções no objeto solicitante inicial e, em seguida, continuando a árvore lógica e verificando cada (ou FrameworkContentElement) para Resources outro Resources valor que contém um ResourceDictionary, possivelmente contendo essa chave.FrameworkElement 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 de XAML.

Composição da árvore lógica

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

Substituindo a árvore lógica

Os 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 ver 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 opera por meio de uma árvore híbrida. Os metadados reais que contêm a propriedade que habilita a herança de propriedade é a Inherits classe de nível FrameworkPropertyMetadata de estrutura 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 de valor da propriedade pode ser perpetuada por meio de um objeto intermediário que não está na árvore lógica. Principalmente, isso é relevante para fazer com que elementos de modelo usem valores da propriedade herdados definidos na instância que é modelada ou em níveis ainda mais altos de composição de nível de página e, portanto, mais altos na árvore lógica. Para a herança de valor da propriedade funcionar consistentemente em tal limite, a propriedade que herda deve ser registrada como uma propriedade anexada. Você deve seguir esse padrão caso pretenda definir uma propriedade de dependência personalizada com comportamento de herança de propriedade. A árvore exata usada para a herança de propriedade não pode ser totalmente prevista por um método de 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 de árvore lógica, há também o conceito de árvore visual no WPF. A árvore visual descreve a estrutura dos objetos visuais, conforme representado pela Visual classe base. Quando você grava um modelo para um controle, define ou redefine a árvore visual que se aplica a ele. A árvore visual também interessa aos desenvolvedores que desejam controle de baixo nível sobre o desenho por motivos de desempenho e otimização. Uma exposição da árvore visual como parte da programação de aplicativos WPF convencional é que as rotas de eventos para um evento roteado viajam principalmente ao longo da árvore visual, não da árvore lógica. Essa sutileza do comportamento do evento roteado pode não ser imediatamente aparente, a menos que você seja um autor de controle. O roteamento de eventos através da árvore visual habilita controles que implementam composição no nível visual para manipular eventos ou criar setters de eventos.

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

Os elementos de conteúdo (classes que derivam de ContentElement) não fazem parte da árvore visual, não herdam Visual e não têm uma representação visual. Para aparecer em uma interface do usuário, um deve ser hospedado em um host de conteúdo que seja um ContentElementVisual participante e uma árvore lógica. Normalmente, tal objeto é um FrameworkElementarquivo . É possível conceitualizar que o host de conteúdo se assemelha a um “navegador” para o conteúdo e escolhe como exibir esse conteúdo dentro da região da tela controlada pelo host. Quando o conteúdo é hospedado, pode se tornar um participante em determinados processos de árvore que, normalmente, estão associados à árvore visual. Geralmente, a FrameworkElement classe host inclui código de implementação que adiciona qualquer hospedado à rota de evento por meio de subnós da árvore lógica de conteúdo, mesmo que o conteúdo hospedado ContentElement não faça parte da árvore visual verdadeira. Isso é necessário para que um pode originar um ContentElement evento roteado que roteia para qualquer elemento que não seja ele mesmo.

Passagem da árvore

A LogicalTreeHelper classe fornece os métodos , GetParente FindLogicalNode para a travessia GetChildrende árvore lógica. Na maioria dos casos, não deve ser necessário atravessar a árvore lógica de controles existentes, porque esses controles quase sempre expõem seus elementos filho lógicos como uma propriedade de coleção dedicada que dá suporte ao acesso de coleção, como Add, um indexador etc. A travessia de árvore é principalmente um cenário usado por autores de controle que optam por não derivar de padrões de controle pretendidos, como ItemsControl ou Panel onde as propriedades de coleção já estão definidas, e que pretendem fornecer seu próprio suporte à propriedade de coleção.

A árvore visual também oferece suporte a uma classe auxiliar para a travessia de árvore visual, VisualTreeHelper. A árvore visual não é exposta tão convenientemente por meio de propriedades específicas de controle, portanto, a classe é a maneira recomendada de atravessar a VisualTreeHelper árvore visual, se isso for necessário para 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 se você estiver atravessando uma árvore visual para um controle onde você define o modelo, os consumidores do seu controle sempre podem alterar o modelo definindo a Template propriedade em instâncias, e até mesmo o usuário final pode influenciar o modelo aplicado alterando o tema do sistema.

Rotas para eventos roteados como “árvore”

Como mencionado antes, a rota de qualquer evento roteado percorre um caminho único e 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 de ser um evento roteado por túnel ou propagação. O conceito de rota de evento não tem uma classe auxiliar diretamente de suporte que possa ser usada para “conduzir” a rota do evento independentemente de gerar um evento que seja realmente roteado. Há uma classe que representa a rota, EventRoutemas os métodos dessa classe são geralmente apenas para uso interno.

Dicionários de recursos e árvores

A pesquisa de dicionário de recursos para todos os Resources definidos em uma página basicamente atravessa a árvore lógica. Os objetos que não estão na árvore lógica podem fazer referência a recursos com chave, mas a sequência de pesquisa de recursos começa no ponto em que o objeto está conectado à árvore lógica. No WPF, somente nós de árvore lógica podem ter uma Resources propriedade que contém um , portanto, não há nenhum benefício em atravessar a árvore visual procurando recursos chaveados de um ResourceDictionaryResourceDictionaryarquivo .

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 recurso pode seguir adiante até dicionários de recursos no nível do aplicativo e, a seguir, até valores de suporte e sistema de tema que são referenciados como propriedades estáticas ou chaves. Os próprios temas também poderão fazer referência a valores do sistema fora da árvore lógica de tema se as referências de recurso forem dinâmicas. Para obter mais informações sobre dicionários de recursos e lógica de pesquisa, consulte Recursos de XAML.

Confira também