Para ver el artículo en inglés, active la casilla Inglés. También puede ver el texto en inglés en una ventana emergente si pasa el puntero del mouse por el texto.
Traducción
Inglés

Árboles en WPF

 

Publicada: junio de 2016

En muchas tecnologías, los elementos y componentes se organizan en una estructura de árbol en la que los desarrolladores manipulan los nodos de objeto directamente para afectar a la presentación o el comportamiento de una aplicación. Windows Presentation Foundation (WPF) también utiliza varias metáforas de la estructura de árbol para definir las relaciones entre los elementos de programación. 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 pensando conceptualmente en la metáfora de árbol de objetos, pero estarán llamando a una API concreta o utilizando un marcado concreto para llamarla, en lugar de utilizar 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 utilizan en la documentación de WPF porque estos á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.

La estructura de árbol más completa de WPF es el árbol de objetos. Si define una página de aplicación en XAML y, a continuación, carga XAML, se crea una estructura de árbol basada en las relaciones de anidamiento de los elementos del marcado. Si 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, que se puede notificar 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 opciones que se hacen en marcado o en 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 la tecnología WPF. Considerar WPF una metáfora de árbol de algún tipo también es crucial para entender cómo funcionan la herencia de propiedades y el enrutamiento de eventos en WPF.

System_CAPS_noteNota

Dado que el árbol de objetos es más un concepto que una API real, otra manera de pensar en el concepto es 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á. No obstante, la metáfora del árbol es lo bastante pertinente para que la mayor parte de la documentación de WPF utilice el término árbol de objetos al hacer referencia a este concepto general, en especial con la interfaz de usuario definida en código XAML.

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, para agregar elementos a un control ListBox se manipula su propiedad Items. Al hacerlo, está colocando elementos en el objeto ItemCollection que es el valor de propiedad Items. De igual forma, para agregar objetos a DockPanel, se manipula su valor de propiedad Children. En este caso, se agregan objetos a UIElementCollection. Para obtener un ejemplo de código, vea Add an Element Dynamically.

En Lenguaje XAML (Extensible Application Markup Language), al colocar los elementos de lista en un objeto ListBox, o controles u otros elementos en un objeto DockPanel, también se utilizan las propiedades Items y Children, ya sea de manera explícita o implícita, como en el ejemplo siguiente.

Si va a procesar este código XAML como XML bajo un modelo de objetos de documento y ha incluido como implícitas las etiquetas marcadas como comentario (lo cual sería válido), el árbol DOM XML resultante incluiría los elementos de <ListBox.Items> y los demás elementos implícitos. Pero el código 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 propiedad ListBox denominada Items con un objeto ItemCollection que se inicializa pero está vacío cuando se procesa el código XAML ListBox. A continuación, cada elemento de objeto secundario que exista como contenido para ListBox se agrega al objeto ItemCollection mediante llamadas del analizador a ItemCollection.Add. Este ejemplo de procesar código 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 en tiempo de ejecución para su interfaz de usuario de la aplicación, incluso con los elementos de sintaxis implícitos de código XAML factorizados. La razón principal para esto son los efectos visuales y las plantillas. Por ejemplo, considere el objeto Button. El árbol lógico notifica 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 particular, el botón sólo aparece en pantalla de esta forma porque se aplicó una plantilla de control Button concreta. Los efectos 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, a continuación, se lee el árbol lógico). Para buscar los efectos visuales de la plantilla, necesitaría examinar el árbol visual en su lugar.

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

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. Asimismo, el árbol lógico proporciona un marco para algunas notificaciones, como, por ejemplo, cuándo se han cargado todos los elementos 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áticas y dinámicas se resuelven buscando hacia arriba en el árbol lógico las colecciones Resources en el objeto que realiza la solicitud inicial; después, se continúa hacia arriba en el árbol lógico y se comprueba cada objeto FrameworkElement (o FrameworkContentElement) en busca de otro valor Resources que contenga una clase ResourceDictionary y que posiblemente contenga esa clave. El árbol lógico se utiliza para la búsqueda de recursos cuando también está presente el árbol visual. Para obtener más información sobre los diccionarios de recursos y la búsqueda, vea Recursos XAML.

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, cuando puede ver si realmente utiliza la API de LogicalTreeHelper, el árbol lógico contiene a veces nodos que no son FrameworkElement o FrameworkContentElement. Por ejemplo, el árbol lógico notifica el valor Text de TextBlock, que es una cadena.

Los autores de controles avanzados pueden invalidar el árbol lógico; para ello, deben invalidar varias APIs 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 Cómo: Invalidar el árbol lógico.

La herencia de valores de propiedad funciona a través de un árbol híbrido. Los metadatos reales que contienen la propiedad Inherits que habilita la herencia de propiedades consisten en la clase FrameworkPropertyMetadata de nivel de marco de trabajo de WPF. Por consiguiente, tanto el objeto primario que contiene el valor original como el objeto secundario que hereda deben ser FrameworkElement o FrameworkContentElement, y deben formar parte de un árbol lógico. Sin embargo, 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 utilicen 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 la página y, por consiguiente, 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. Debería seguir este modelo si piensa definir una propiedad de dependencia personalizada con un comportamiento de herencia de propiedades. El árbol exacto utilizado para la herencia de propiedad no se puede prever completamente mediante un método de utilidad de clase de aplicación auxiliar, ni siquiera en tiempo de ejecución. Para obtener más información, vea Herencia de valores de propiedad.

Además del concepto de árbol lógico, en WPF existe el concepto de árbol visual. El árbol visual describe la estructura de objetos visuales representados por la clase base Visual. Cuando se escribe una plantilla para un control, se define o vuelve a definir el árbol visual aplicable a ese control. El árbol visual también reviste interés para los programadores que desean un control de bajo nivel sobre el dibujo por motivos de optimización y rendimiento. Una exposición del árbol visual como parte de la programación de aplicaciones de WPF convencional 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. Enrutar 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.

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 que aparezcan en la interfaz de usuario, un objeto ContentElement se debe hospedar en un host de contenido que sea un objeto Visual y un elemento 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, éste puede participar en algunos procesos del árbol que suelen asociarse al árbol visual. En general, la clase 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.

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 atravesar 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 indizador, etc. El escenario de exploración transversal del árbol se utiliza principalmente por autores de controles que optan por no derivar de los patrones de control previstos, tales como ItemsControl o Panel donde ya están definidas las propiedades de colección, sino que desean proporcionar su propia compatibilidad con propiedades de colección.

El árbol visual también admite una clase de aplicación auxiliar 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 atravesar el árbol visual si fuera necesario en un escenario de programación. Para obtener más información, vea Información general sobre la representación de gráficos en WPF.

System_CAPS_noteNota

A veces es necesario examinar el árbol visual de una plantilla aplicada. Debería tener cuidado al utilizar esta técnica. Aunque esté atravesando un árbol visual para un control en el que ha definido la plantilla, los consumidores de su 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.

Tal y como se mencionó 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 de aplicación auxiliar que lo respalde directamente y que se pueda utilizar para "recorrer" la ruta de evento con independencia de que se provoque un evento que se enrute realmente. Hay una clase que representa la ruta, EventRoute, pero los métodos de esa clase suelen ser sólo para uso interno.

La búsqueda de diccionarios de recursos para todos los Resources definidos en una página atraviesa 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 contiene una clase ResourceDictionary; por consiguiente, atravesar el árbol visual en busca de recursos con clave desde una clase ResourceDictionary no reviste ninguna ventaja.

Sin embargo, 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 la compatibilidad del tema y los valores de sistema 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 referencias de recursos son dinámicas. Para obtener más información sobre los diccionarios de recursos y la lógica de búsqueda, vea Recursos XAML.

Mostrar: