Árboles en WPF

En muchas tecnologías, los elementos y componentes se organizan en una estructura de árbol en la que los desarrolladores manipulan directamente los nodos de objeto en el árbol para modificar la representación o el comportamiento de una aplicación. Windows Presentation Foundation (WPF) también usa varias metáforas de la estructura de árbol para definir las relaciones entre los elementos de programa. En su mayor parte, los desarrolladores de WPF pueden crear una aplicación en código o definir partes de la aplicación en código XAML mientras piensan conceptualmente en la metáfora de árbol de objetos, pero estarán llamando a una API concreta o usando un marcado concreto para llamarla, en lugar de usar una API de manipulación de árbol de objetos general como se usaría en DOM XML. WPF expone dos clases auxiliares que proporcionan una vista de metáfora de árbol, LogicalTreeHelper y VisualTreeHelper . Los términos árbol visual y árbol lógico también se usan en la documentación de WPF porque estos mismos árboles son útiles para entender el comportamiento de ciertas características clave de WPF. En este tema se define lo que representan el árbol visual y el árbol lógico, se explica cómo se relacionan estos árboles con un concepto de árbol de objetos general, y se presentan LogicalTreeHelper y VisualTreeHelper .

Árboles en WPF

La estructura de árbol más completa de WPF es el árbol de objetos. Si se define una página de aplicación en XAML y después se carga el XAML, se crea una estructura de árbol basada en las relaciones de anidamiento de los elementos del marcado. Si se define una aplicación o una parte de la aplicación en código, se crea la estructura de árbol en función de cómo se asignen los valores de propiedad de las propiedades que implementan el modelo de contenido para un objeto determinado. En Windows Presentation Foundation (WPF), hay dos maneras de conceptualizar el árbol de objetos completo y de notificarlo a su API pública: como el árbol lógico y como el árbol visual. Las distinciones entre árbol lógico y árbol visual no son siempre importantes, pero en ocasiones pueden dar lugar a problemas con algunos subsistemas de WPF y afectar a las decisiones que se toman en el marcado o el código.

Aunque no siempre se manipula directamente el árbol lógico o el árbol visual, entender los conceptos relativos a cómo interactúan ayuda a entender WPF como tecnología. Pensar en WPF como en un tipo de metáfora de árbol también es crucial para entender cómo funcionan la herencia de propiedades y el enrutamiento de eventos en WPF.

Nota

Dado que el árbol de objetos es más un concepto que una API real, otra manera de pensar en el concepto es como un gráfico de objetos. En la práctica, hay relaciones entre los objetos en tiempo de ejecución en las que la metáfora del árbol no servirá. Pero la metáfora del árbol es lo bastante relevante para que la mayor parte de la documentación de WPF use el término árbol de objetos al hacer referencia a este concepto general, en especial con la interfaz de usuario definida en XAML.

El árbol lógico

En WPF, el contenido se agrega a los elementos de la interfaz de usuario estableciendo propiedades de los objetos que respaldan esos elementos. Por ejemplo, los elementos se agregan a un ListBox control manipulando su Items propiedad. Al hacerlo, se colocan elementos en ItemCollection que es el Items valor de propiedad. Del mismo modo, para agregar objetos a un DockPanel , se manipula su Children valor de propiedad. Aquí, va a agregar objetos al UIElementCollection . Para obtener un ejemplo de código, vea Cómo: agregar un elemento dinámicamente.

En Lenguaje XAML (Extensible Application Markup Language) , cuando se colocan elementos de lista en un ListBox control o u otros elementos de la interfaz de usuario de DockPanel , también se usan las Items Children propiedades y, ya sea de forma explícita o implícita, como en el ejemplo siguiente.

<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>

Si se procesara este código XAML como XML bajo un modelo de objetos de documento y se incluyeran como implícitas las etiquetas marcadas como comentario (lo que sería válido), el árbol DOM XML resultante incluiría elementos para <ListBox.Items> y los demás elementos implícitos. Pero XAML no se procesa de esta forma al leer el marcado y escribir en los objetos; el gráfico de objetos resultante no incluye literalmente ListBox.Items. Sin embargo, tiene una ListBox propiedad denominada Items que contiene un ItemCollection y que ItemCollection se inicializa pero está vacía cuando ListBox se procesa el XAML. A continuación, cada elemento de objeto secundario que existe como contenido de ListBox se agrega a ItemCollection mediante llamadas del analizador a ItemCollection.Add . Este ejemplo de procesamiento de XAML en un árbol de objetos es hasta ahora aparentemente un ejemplo en el que el árbol de objetos creado es básicamente el árbol lógico.

Sin embargo, el árbol lógico no es el gráfico de objetos completo que existe para la interfaz de usuario de la aplicación en tiempo de ejecución, incluso con los elementos de sintaxis implícita de XAML factorizados. La razón principal de esto son los objetos visuales y las plantillas. Por ejemplo, considere Button . El árbol lógico notifica el Button objeto y también su cadena Content . Pero este botón es más complejo en el árbol de objetos en tiempo de ejecución. En concreto, el botón solo aparece en la pantalla de la forma en que lo hace porque Button se aplicó una plantilla de control específica. Los objetos visuales que provienen de una plantilla aplicada (por ejemplo, la plantilla definida con el Border color gris oscuro alrededor del botón visual) no se incluyen en el árbol lógico, aunque esté viendo el árbol lógico durante el tiempo de ejecución (por ejemplo, controle un evento de entrada desde la interfaz de usuario visible y, a continuación, leyendo el árbol lógico). Para buscar los elementos visuales de la plantilla, sería necesario examinar el árbol visual en su lugar.

Para más información sobre cómo se asigna la sintaxis de XAML al gráfico de objetos creado, y la sintaxis implícita en XAML, vea Detalles de la sintaxis XAML o Información general sobre XAML (WPF).

Finalidad del árbol lógico

El árbol lógico existe para que los modelos de contenido puedan iterar fácilmente sus posibles objetos secundarios y para que los modelos de contenido puedan ser extensibles. Además, el árbol lógico proporciona un marco para algunas notificaciones, como, por ejemplo cuándo se cargan todos los objetos en él. Básicamente, el árbol lógico es una aproximación de un gráfico de objetos en tiempo de ejecución en el nivel de marco, que excluye los elementos visuales, pero es adecuado para muchas operaciones de consulta en la composición de su propia aplicación en tiempo de ejecución.

Además, las referencias de recursos estáticos y dinámicos se resuelven al mirar hacia arriba a través del árbol lógico para Resources las colecciones en el objeto de solicitud inicial y, a continuación, continuar con el árbol lógico y comprobar cada FrameworkElement (o FrameworkContentElement ) Resources un valor que contenga un ResourceDictionary , que posiblemente contenga esa clave. El árbol lógico se usa para la búsqueda de recursos cuando también está presente el árbol visual. Para más información sobre los diccionarios de recursos y la búsqueda, vea Recursos XAML.

Composición del árbol lógico

El árbol lógico se define en el nivel de marco de WPF, lo que significa que el elemento base de WPF más relevante para las operaciones de árbol lógico es FrameworkElement o FrameworkContentElement . Sin embargo, como puede ver si realmente usa la LogicalTreeHelper API, el árbol lógico contiene a veces nodos que no son FrameworkElement ni FrameworkContentElement . Por ejemplo, el árbol lógico informa del Text valor de TextBlock , que es una cadena.

Invalidar el árbol lógico

Los autores de controles avanzados pueden invalidar el árbol lógico invalidando varias API que definen cómo un objeto general o modelo de contenido agrega o quita objetos en el árbol lógico. Para obtener un ejemplo de cómo invalidar el árbol lógico, vea Invalidar el árbol lógico.

Herencia de valores de propiedad

La herencia de valores de propiedad funciona a través de un árbol híbrido. Los metadatos reales que contienen la Inherits propiedad que habilita la herencia de propiedades es la clase de nivel de marco de WPF FrameworkPropertyMetadata . Por lo tanto, tanto el elemento primario que contiene el valor original como el objeto secundario que hereda ese valor deben ser FrameworkElement o FrameworkContentElement , y ambos deben formar parte de un árbol lógico. Pero para las propiedades de WPF existentes que admiten la herencia de propiedades, la herencia del valor de propiedad puede perpetuarse a través de un objeto intermedio que no está en el árbol lógico. Esto es especialmente pertinente para hacer que los elementos de plantilla usen cualquier valor de propiedad heredado establecido en la instancia basada en una plantilla o en los niveles aun más altos de composición de página y, por tanto, superiores en el árbol lógico. Para que la herencia del valor de propiedad funcione de forma coherente a través de un límite de este tipo, la propiedad que hereda se debe registrar como una propiedad adjunta y se debe seguir este patrón si se piensa definir una propiedad de dependencia personalizada con un comportamiento de herencia de propiedades. El árbol exacto que se usa para la herencia de propiedades no se puede prever completamente mediante un método de utilidad de clase del asistente, ni siquiera en tiempo de ejecución. Para más información, vea Herencia de valores de propiedad.

El árbol visual

Además del concepto de árbol lógico, en WPF también existe el concepto de árbol visual. El árbol visual describe la estructura de los objetos visuales, tal como se representa en la Visual clase base. Cuando se escribe una plantilla para un control, se define o se vuelve a definir el árbol visual aplicable a ese control. El árbol visual también tiene interés para los desarrolladores que quieren un control más específico sobre el dibujo por motivos de optimización y rendimiento. Una exposición del árbol visual como parte de la programación convencional de aplicaciones de WPF consiste en que las rutas de evento de un evento enrutado recorren en su mayoría el árbol visual, no el árbol lógico. Esta sutileza de comportamiento de los eventos enrutados puede no resultar patente de forma inmediata salvo para los autores de controles. El enrutamiento de eventos a través del árbol visual permite que los controles que implementan la composición en el nivel visual controlen eventos o creen establecedores de eventos.

Árboles, elementos de contenido y hosts de contenido

Los elementos de contenido (clases que derivan de ContentElement ) no forman parte del árbol visual; no heredan de Visual y no tienen una representación visual. Para que aparezca en la interfaz de usuario, ContentElement se debe hospedar en un host de contenido que sea Visual y un participante del árbol lógico. Normalmente, este tipo de objeto es FrameworkElement . Puede considerarse el host del contenido como una especie de "explorador" para el contenido que decide cómo mostrar ese contenido dentro de la zona de la pantalla que controla. Cuando se hospeda el contenido, puede participar en algunos procesos del árbol que suelen asociarse al árbol visual. Por lo general, la FrameworkElement clase host incluye código de implementación que agrega cualquier hospedado ContentElement a la ruta de evento a través de subnodos del árbol lógico de contenido, aunque el contenido hospedado no forme parte del árbol visual verdadero. Esto es necesario para que un ContentElement pueda tener como origen un evento enrutado que enruta a cualquier elemento que no sea él mismo.

Exploración transversal del árbol

La LogicalTreeHelper clase proporciona los GetChildren GetParent métodos, y FindLogicalNode para el recorrido del árbol lógico. En la mayoría de los casos, no debería tener que recorrer el árbol lógico de los controles existentes, porque casi siempre exponen sus elementos secundarios lógicos como una propiedad de colección dedicada que admite el acceso a colecciones como Add, un indexador, etc. El cruce seguro del árbol es principalmente un escenario que usan los autores de controles que deciden no derivarse de los patrones de control previstos, como ItemsControl o Panel , en los que las propiedades de la colección ya están definidas, y que pretenden proporcionar su propia propiedad de colección.

El árbol visual también admite una clase auxiliar para el cruce seguro del árbol visual, VisualTreeHelper . El árbol visual no se expone como de manera cómoda a través de propiedades específicas del control, por lo que la VisualTreeHelper clase es la manera recomendada de atravesar el árbol visual si es necesario para el escenario de programación. Para más información, consulte Información general sobre la representación de gráficos en WPF.

Nota

A veces es necesario examinar el árbol visual de una plantilla aplicada. Se recomienda tener cuidado al usar esta técnica. Incluso si va a atravesar un árbol visual para un control en el que se define la plantilla, los consumidores del control siempre pueden cambiar la plantilla estableciendo la Template propiedad en las instancias e incluso el usuario final puede influir en la plantilla aplicada cambiando el tema del sistema.

Rutas para los eventos enrutados como un "árbol"

Como se ha mencionado antes, la ruta de cualquier evento enrutado determinado recorre una sola ruta de acceso predeterminada de un árbol, que es un híbrido de las representaciones del árbol visual y del árbol lógico. La ruta de evento puede recorrer el árbol hacia arriba o hacia abajo en función de si es un evento enrutado de tunelización o de propagación. El concepto de ruta de evento no tiene ninguna clase del asistente que lo respalde directamente y que se pueda usar para "recorrer" la ruta de evento con independencia de que se genere un evento que se enrute realmente. Hay una clase que representa la ruta, EventRoute , pero los métodos de esa clase suelen ser solo para uso interno.

Diccionarios de recursos y árboles

La búsqueda de diccionarios de recursos para todos los Resources definidos en una página recorre básicamente el árbol lógico. Los objetos que no están en el árbol lógico pueden hacer referencia a recursos con clave, pero la secuencia de búsqueda de recursos se inicia en el punto donde ese objeto está conectado al árbol lógico. En WPF, solo los nodos de árbol lógico pueden tener una Resources propiedad que contenga ResourceDictionary , por lo tanto, no hay ninguna ventaja al atravesar el árbol visual buscando recursos con clave de ResourceDictionary .

Pero la búsqueda de recursos también se puede extender más allá del árbol lógico inmediato. Para el marcado de la aplicación, la búsqueda de recursos puede continuar con los diccionarios de recursos de nivel de aplicación y, después, con los valores de compatibilidad y sistema de tema a los que se hace referencia como propiedades estáticas o claves. Los propios temas también pueden hacer referencia a valores del sistema situados fuera del árbol lógico del tema si las referencias de recurso son dinámicas. Para más información sobre los diccionarios de recursos y la lógica de búsqueda, vea Recursos XAML.

Consulte también