Á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 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 árbol de las metáforas, 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 describe cómo se relacionan estos árboles con un concepto de árbol de objetos global y se presentan las clases 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 archivo 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 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, puede agregar elementos a un control ListBox mediante la manipulación de su propiedad Items. Al hacerlo, coloca los elementos en la ItemCollection que es el valor de propiedad de Items. De la misma manera, para agregar objetos a un DockPanel, se debe manipular su valor de propiedad Children. En este caso, se agregan objetos a UIElementCollection. Para obtener un ejemplo de código, consulte How to: Add an Element Dynamically (tutorial: Agregar dinámicamente un elemento).

En el lenguaje Extensible Application Markup Language (XAML), cuando coloca elementos de lista en una ListBox o controles u otros elementos de UI en un DockPanel, también usa las propiedades Items y Children, ya sea de forma directa o indirecta, como se muestra 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, sí que tiene una propiedad ListBox denominada Items que contiene una ItemCollection, y esa ItemCollection se inicializa, pero se vacía cuando se procesa el XAML de ListBox. A continuación, cada elemento de objeto secundario que existe como contenido para la ListBox se agrega a la ItemCollection mediante llamadas al 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.

Pero el árbol lógico no es el gráfico de objetos completo que existe en tiempo de ejecución para la interfaz de usuario de la aplicación, incluso con los elementos de sintaxis implícitos de XAML factorizados. El principal motivo de esto son los objetos visuales y plantillas. Por ejemplo, considere el Button. El árbol lógico comunica el objeto Button 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 pantalla de esta forma porque se ha aplicado una plantilla de control Button concreta. Los elementos visuales que proceden de una plantilla aplicada (como el objeto Border definido por la plantilla de gris oscuro alrededor del botón visual) no se notifican en el árbol lógico, incluso si se examina el árbol lógico durante el tiempo de ejecución (por ejemplo, se controla un evento de entrada desde la interfaz de usuario visible y, después, se lee el árbol lógico). Para buscar los elementos visuales de la plantilla, sería necesario examinar el árbol visual en su lugar.

Para obtener 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 XAML en 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, ambas referencias de recursos estáticos y dinámicos se resuelven mirando hacia arriba en el árbol lógico para las recopilaciones Resources en el objeto de solicitud inicial, y continuando hasta el árbol lógico y realizando la comprobación de cada FrameworkElement (o FrameworkContentElement) para otro valor Resources que contiene un ResourceDictionary y posiblemente también una 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 pertinente para las operaciones del árbol lógico es FrameworkElement o FrameworkContentElement. Sin embargo, como se puede ver si usa la API LogicalTreeHelper, el árbol lógico a veces contiene nodos que no son FrameworkElement o FrameworkContentElement. Por ejemplo, el árbol lógico comunica el valor Text de un TextBlock, que es una cadena.

Invalidar el árbol lógico

Los autores de controles avanzados pueden invalidar el árbol lógico si invalidan varias API que definen la forma en que un objeto general o un modelo de contenido agrega o quita objetos en dicho árbol. 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 propiedad Inherits que habilitan la herencia de propiedades es la clase de nivel de marco FrameworkPropertyMetadata de WPF. Por consiguiente, tanto el objeto primario que contiene el valor original como el objeto secundario que lo hereda deben ser FrameworkElement o FrameworkContentElement, y 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, representados por la clase base Visual. 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 se derivan de ContentElement) no forman parte del árbol visual, no heredan de Visual ni tienen representación visual. Para aparecer en la UI, debe hospedarse un ContentElement en un host de contenido que sea a la vez un Visual y un participante de árbol lógico. Normalmente, este objeto es un 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. En general, la clase de host FrameworkElement incluye código de implementación que agrega cualquier ContentElement hospedado a la ruta del evento a través de subnodos del árbol lógico de contenido, aunque el contenido hospedado no forme parte del verdadero árbol visual. Esto es necesario para que un ContentElement pueda originar un evento enrutado que se enruta a cualquier elemento que no sea él mismo.

Exploración transversal del árbol

La clase LogicalTreeHelper proporciona los métodos GetChildren, GetParent y FindLogicalNode para la exploración transversal 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 escenario de exploración transversal del árbol se usa principalmente por autores de controles que optan por no derivar de los patrones de control previstos, tales como ItemsControl o Panel en los que ya están definidas las propiedades de colección, sino que quieren proporcionar su propia compatibilidad con propiedades de colección.

El árbol visual también admite una clase del asistente para su propia exploración transversal, VisualTreeHelper. El árbol visual no se expone de un modo tan cómodo a través de las propiedades específicas del control, por lo que la clase VisualTreeHelper es la manera recomendada de recorrer el árbol visual si fuera necesario en un 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. Aunque se esté recorriendo un árbol visual para un control en el que se define la plantilla, los consumidores del control siempre pueden cambiar la plantilla si establecen la propiedad Template en las instancias e incluso el usuario final puede influir en la plantilla aplicada si cambia 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 sus métodos 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, únicamente los nodos de árbol lógico pueden tener una propiedad Resources que contenga una clase ResourceDictionary. Por tanto, recorrer el árbol visual en busca de recursos con clave desde una clase ResourceDictionary no reviste ninguna ventaja.

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.

Vea también