Partilhar via


Árvores no WPF

Em muitas tecnologias, elementos e componentes são organizados em uma estrutura de árvore onde os desenvolvedores manipulam diretamente os nós de objeto na árvore para afetar a renderização ou o comportamento de um aplicativo. 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 em código ou definir partes do aplicativo em XAML enquanto pensam conceitualmente sobre a metáfora da árvore de objetos, mas chamarão uma API específica ou usarão uma marcação específica para fazê-lo em vez de alguma API geral de manipulação de árvore de objetos, como você pode usar no DOM XML. O WPF expõe duas classes auxiliares que fornecem uma visão de metáfora de árvore, LogicalTreeHelper e VisualTreeHelper. Os termos á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 certos 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 depois carregar o XAML, a estrutura de árvore será criada com base nas relações de aninhamento dos elementos no markup. Se você definir um aplicativo ou uma parte do aplicativo no código, a estrutura em á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 é 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 certos subsistemas do WPF e afetar as escolhas que você faz na marcação ou no código.

Mesmo que você nem sempre manipule a árvore lógica ou a árvore visual diretamente, 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 gráfico de objeto. Na prática, há relações entre objetos em tempo de execução onde a metáfora da árvore não se sustentará. 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 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 apoiam esses elementos. Por exemplo, você adiciona itens a um ListBox controle manipulando sua Items propriedade. Ao fazer isso, coloca itens no ItemCollection, que é o valor da propriedade Items. Da mesma forma, para adicionar objetos a um DockPanel, deve-se manipular o valor da sua propriedade Children. Aqui, você está adicionando objetos ao UIElementCollection. Para obter um exemplo de código, consulte Como adicionar um elemento dinamicamente.

Em Extensible Application Markup Language (XAML), ao colocar itens de lista em um ListBox ou controles ou outros elementos da interface do utilizador em um DockPanel, também utiliza as propriedades Items 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ê processasse esse XAML como XML em um modelo de objeto de documento e 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 os outros itens implícitos. Mas o XAML não processa dessa maneira quando se lê o código de marcação e escreve em objetos, a estrutura de objetos resultante não inclui ListBox.Items literalmente. No entanto, ele tem uma ListBox propriedade chamada Items que contém um ItemCollection, e essa ItemCollection é inicializada, mas permanece vazia quando o ListBox XAML é processado. Em seguida, cada elemento de objeto filho que existe como conteúdo para o ListBox é adicionado ao ItemCollection através de chamadas do analisador 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 de utilizador da aplicação em tempo de execução, mesmo considerando os itens de sintaxe implícita do XAML. A principal razão para isso são os visuais e os modelos. Por exemplo, considere o Button. A árvore lógica relata o objeto Button e também a sua "string" Content. Mas há mais neste botão na árvore de objetos em 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 em cinza escuro em torno do botão gráfico) não aparecem na árvore lógica, mesmo que você esteja a observar a árvore lógica durante o tempo de execução (como ao gerir um evento de entrada da interface de utilizador visível e, em seguida, a leitura da árvore lógica). Para encontrar os modelos visuais, seria necessário examinar a árvore visual.

Para obter mais informações sobre como a sintaxe XAML mapeia para o gráfico de objeto criado e sintaxe implícita em XAML, consulte Sintaxe XAML em detalhes ou XAML em WPF.

O Propósito da Árvore Lógica

A árvore lógica existe para que os modelos de conteúdo possam facilmente iterar sobre os seus possíveis objetos filhos e para que esses modelos 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 gráfico de objeto de tempo de execução no nível da estrutura, que exclui elementos 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 subindo pela árvore lógica para coleções Resources no objeto solicitante inicial e, em seguida, continuando a subir na árvore lógica e verificando cada FrameworkElement (ou FrameworkContentElement) para 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 pesquisa, consulte Recursos XAML.

Composição da Árvore Lógica

A árvore lógica é definida ao nível de estrutura do WPF, o que significa que o elemento base do WPF que é mais relevante para operações de árvore lógica é ou FrameworkElement, ou FrameworkContentElement. No entanto, como pode ver se realmente usar a LogicalTreeHelper API, a árvore lógica às vezes contém nós que não são nem FrameworkElement nem FrameworkContentElement. 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 obter um exemplo de como substituir a árvore lógica, consulte Substituir a árvore lógica.

Herança de Valores da Propriedade

A herança de valor da propriedade funciona por meio 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 de nível de estrutura do WPF FrameworkPropertyMetadata. Portanto, tanto o pai que deté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 WPF existentes que suportam herança de propriedade, a herança de valores das propriedades pode perpetuar-se através de um objeto intermediário 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 é modelada ou em níveis ainda mais altos de composição em nível de página e, portanto, mais altos na árvore lógica. Para que a herança do valor da propriedade funcione consistentemente através desse limite, a propriedade herdeira deve ser registrada como uma propriedade anexada e você deve 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 totalmente prevista por um método utilitário de classe auxiliar, mesmo em tempo de execução. Para obter mais informações, consulte Property Value Inheritance.

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 de objetos visuais, conforme representado pela Visual classe base. Quando você escreve 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 querem 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 do aplicativo WPF é 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 de 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 permite que os controles que implementam a composição no nível visual manipulem eventos ou criem definidores de eventos.

Árvores, elementos de conteúdo e hospedeiros 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 numa interface do utilizador, um ContentElement deve ser hospedado num host de conteúdo que seja um Visual e participante da árvore lógica. Normalmente, tal 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 área da tela que o host controla. Quando o conteúdo é hospedado, o conteúdo pode ser tornado um participante em determinados processos de árvore que são normalmente associados com a árvore visual. Geralmente, a classe FrameworkElement anfitriã inclui código de implementação que adiciona qualquer conteúdo hospedado ContentElement à rota do evento por meio de subnós da árvore lógica de conteúdo, mesmo que o conteúdo hospedado não faça parte da verdadeira árvore visual. Isso é necessário para que um ContentElement possa originar um evento roteado que roteie para qualquer elemento que não seja ele mesmo.

Travessia de Árvores

A classe LogicalTreeHelper fornece os métodos GetChildren, GetParent e FindLogicalNode para a travessia lógica da árvore. Na maioria das situações, não deverá ter que percorrer a árvore lógica dos controles existentes, porque esses controles quase sempre expõem os seus elementos filhos lógicos como uma propriedade específica de coleção que oferece suporte ao acesso à coleção, como Add, um indexador, entre outros. A travessia de árvores é principalmente um cenário utilizado por criadores de controlos que optam por não derivar de padrões de controlo pretendidos, como ItemsControl ou Panel, onde as propriedades de coleção já estão definidas, e que pretendem fornecer suporte para as suas próprias propriedades de coleção.

A árvore visual também suporta uma classe auxiliar para a travessia visual da árvore, VisualTreeHelper. A árvore visual não é exposta tão convenientemente por meio de propriedades específicas de controle, portanto, a VisualTreeHelper classe é 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 de gráficos do WPF.

Observação

Às vezes, é necessário examinar a árvore visual de um modelo aplicado. Deve ter cuidado ao utilizar esta técnica. Mesmo que estiveres a percorrer uma árvore visual para um controlo em que defines o modelo, os consumidores do teu controlo podem sempre alterar o modelo ao definir a propriedade Template nas instâncias, e até mesmo o utilizador final pode influenciar o modelo aplicado ao alterar o tema do sistema operacional.

Rotas para eventos roteados como uma "árvore"

Como mencionado anteriormente, a rota de qualquer evento roteado percorre um caminho único e predeterminado de uma árvore que é um híbrido das representações visuais e lógicas da árvore. A rota do evento pode viajar nas direções para cima ou para baixo dentro da árvore, dependendo se é um evento de tunelamento ou borbulhante. O conceito de rota de evento não tem uma classe auxiliar de suporte direto que poderia ser usada para percorrer a rota do evento independentemente de disparar um evento que siga realmente uma rota. Há uma classe que representa a rota, EventRoutemas os métodos dessa classe são geralmente apenas para uso interno.

Dicionários e Árvores de Recursos

A pesquisa de dicionário de recursos para todos os Resources definidos numa página atravessa, em essência, a árvore lógica. Os objetos que não estão na árvore lógica podem fazer referência a 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, apenas nós da árvore lógica podem ter uma Resources propriedade que contém um ResourceDictionary, portanto, não há vantagem em percorrer a árvore visual procurando recursos chaveados 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 de aplicações, a pesquisa de recursos pode continuar para dicionários de recursos ao nível da aplicação e, em seguida, para suporte a temas e valores de sistema referenciados como propriedades estáticas ou chaves. Os próprios temas também podem fazer referência a valores do sistema fora da árvore lógica do tema se as referências de recursos forem dinâmicas. Para obter mais informações sobre dicionários de recursos e a lógica de pesquisa, consulte Recursos XAML.

Ver também