Partager via


Arbres dans WPF

Dans de nombreuses technologies, les éléments et les composants sont organisés dans une arborescence où les développeurs manipulent directement les nœuds d’objet dans l’arborescence pour affecter le rendu ou le comportement d’une application. Windows Presentation Foundation (WPF) utilise également plusieurs métaphores de structure d’arborescence pour définir des relations entre les éléments de programme. Pour la plupart, les développeurs WPF peuvent créer une application dans du code ou définir des parties de l’application en XAML tout en pensant conceptuellement à la métaphore de l’arborescence d’objets, mais appellent une API spécifique ou utilisent un balisage spécifique pour le faire plutôt que certaines API de manipulation d’arborescence d’objets générales telles que vous pouvez utiliser dans le DOM XML. WPF expose deux classes d’assistance qui fournissent une vue métaphorique d’arborescence, LogicalTreeHelper et VisualTreeHelper. Les termes arborescence visuelle et arborescence logique sont également utilisés dans la documentation WPF, car ces mêmes arborescences sont utiles pour comprendre le comportement de certaines fonctionnalités WPF clés. Cette rubrique définit ce que représentent l’arborescence visuelle et l’arborescence logique, explique comment ces arborescences sont liées à un concept d’arborescence d’objets global et présente LogicalTreeHelper et les VisualTreeHelpers.

Arbres dans WPF

La structure d’arborescence la plus complète dans WPF est l’arborescence d’objets. Si vous définissez une page d’application en XAML, puis chargez le code XAML, la structure d’arborescence est créée en fonction des relations d’imbrication des éléments dans le balisage. Si vous définissez une application ou une partie de l’application dans le code, la structure d’arborescence est créée en fonction de la façon dont vous affectez des valeurs de propriété pour les propriétés qui implémentent le modèle de contenu pour un objet donné. Dans WPF, il existe deux façons que l’arborescence d’objets complète soit conceptualisée et puisse être signalée à son API publique : en tant qu’arborescence logique et en tant qu’arborescence visuelle. Les distinctions entre l’arborescence logique et l’arborescence visuelle ne sont pas nécessairement importantes, mais elles peuvent parfois provoquer des problèmes avec certains sous-systèmes WPF et affecter les choix que vous effectuez dans le balisage ou le code.

Même si vous ne manipulez pas toujours l’arborescence logique ou l’arborescence visuelle directement, comprendre les concepts de l’interaction des arborescences est utile pour comprendre WPF en tant que technologie. La pensée de WPF comme une métaphore d’arbre d’un certain type est également essentielle pour comprendre comment l’héritage des propriétés et le routage des événements fonctionnent dans WPF.

Note

Étant donné que l’arborescence d’objets est plus d’un concept qu’une API réelle, une autre façon de considérer le concept est un graphe d’objet. Dans la pratique, il existe des relations entre les objets au moment de l’exécution où la métaphore de l’arbre se décompose. Néanmoins, en particulier avec l’interface utilisateur définie par XAML, la métaphore de l’arborescence est suffisamment pertinente pour que la plupart de la documentation de WPF utilise le terme d’arborescence d'objets pour faire référence à ce concept général.

Arborescence logique

Dans WPF, vous ajoutez du contenu aux éléments d’interface utilisateur en définissant les propriétés des objets qui sauvegardent ces éléments. Par exemple, vous ajoutez des éléments à un contrôle ListBox en manipulant sa propriété Items. En procédant ainsi, vous placez des éléments dans le ItemCollection, qui est la valeur de la propriété Items. De même, pour ajouter des objets à un DockPanel, vous manipulez sa valeur de propriété Children. Ici, vous ajoutez des objets au UIElementCollection. Pour obtenir un exemple de code, consultez Guide pratique pour ajouter un élément dynamiquement.

Dans XAML (Extensible Application Markup Language), lorsque vous placez des éléments de liste dans un ListBox ou des contrôles ou d’autres éléments d’interface utilisateur dans un DockPanel, vous utilisez également les propriétés Items et Children, explicitement ou implicitement, comme dans l’exemple suivant.

<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 vous deviez traiter ce code XAML en tant que CODE XML sous un modèle objet de document et si vous aviez inclus les balises commentées comme implicites (ce qui aurait été légal), l’arborescence DOM XML résultante aurait inclus des éléments pour <ListBox.Items> et les autres éléments implicites. Mais XAML ne traite pas de cette façon lorsque vous lisez le balisage et écrivez dans des objets, le graphique d’objets résultant n’inclut pas littéralement ListBox.Items. Il a toutefois une propriété ListBox nommée Items qui contient un ItemCollection, et cette ItemCollection est initialisée mais vide lorsque le code XAML ListBox est traité. Ensuite, chaque élément d'objet enfant qui existe en tant que contenu de ListBox est ajouté à ItemCollection par des appels de l'analyseur à ItemCollection.Add. Cet exemple de traitement xaml dans une arborescence d’objets est jusqu’à présent un exemple où l’arborescence d’objets créée est essentiellement l’arborescence logique.

Toutefois, l’arborescence logique n’est pas le graphique d’objet entier qui existe pour l’interface utilisateur de votre application au moment de l’exécution, même avec les éléments de syntaxe implicite XAML pris en compte. La principale raison de cela est les visuels et les modèles. Prenons l'exemple de Button. L’arborescence logique signale l’objet Button et sa chaîne Content. Mais il y a plus à ce bouton dans l’arborescence d’objets d’exécution. En particulier, le bouton n'apparaît à l'écran que parce qu'un modèle de contrôle spécifique Button a été appliqué. Les visuels provenant d’un modèle appliqué (tels que les Border définis par le modèle de gris foncé autour du bouton visuel) ne sont pas signalés dans l’arborescence logique, même si vous examinez l’arborescence logique pendant l’exécution (par exemple, la gestion d’un événement d’entrée à partir de l’interface utilisateur visible, puis la lecture de l’arborescence logique). Pour rechercher les visuels de modèle, vous devez plutôt examiner l’arborescence visuelle.

Pour plus d’informations sur la façon dont la syntaxe XAML est mappée au graphe objet créé et la syntaxe implicite en XAML, consultez syntaxe XAML en détail ou XAML dans WPF.

Objectif de l’arborescence logique

L’arborescence logique existe afin que les modèles de contenu puissent facilement itérer sur leurs objets enfants possibles, et afin que les modèles de contenu puissent être extensibles. En outre, l’arborescence logique fournit une infrastructure pour certaines notifications, par exemple lorsque tous les objets de l’arborescence logique sont chargés. En fait, l’arborescence logique est une approximation d’un graphique d’objet d’exécution au niveau de l’infrastructure, qui exclut les visuels, mais convient à de nombreuses opérations d’interrogation sur la composition de votre propre application au moment de l’exécution.

En outre, les références de ressources statiques et dynamiques sont résolues en regardant vers le haut dans l’arborescence logique des collections Resources sur l’objet demandeur initial, puis en continuant l’arborescence logique et en vérifiant chaque FrameworkElement (ou FrameworkContentElement) pour une autre valeur de Resources qui contient un ResourceDictionary, éventuellement contenant cette clé. L’arborescence logique est utilisée pour la recherche de ressources lorsque l’arborescence logique et l’arborescence visuelle sont présentes. Pour plus d’informations sur les dictionnaires de ressources et la recherche, consultez ressources XAML.

Composition de l’arborescence logique

L’arborescence logique est définie au niveau du framework WPF, ce qui signifie que l’élément de base WPF le plus pertinent pour les opérations d’arborescence logique est FrameworkElement ou FrameworkContentElement. Toutefois, comme vous pouvez le voir si vous utilisez réellement l’API LogicalTreeHelper, l’arborescence logique contient parfois des nœuds qui ne sont pas FrameworkElement ou FrameworkContentElement. Par exemple, l’arborescence logique signale la valeur Text d’un TextBlock, qui est une chaîne.

Substitution de l’arborescence logique

Les auteurs de contrôles avancés peuvent remplacer l’arborescence logique en remplaçant plusieurs API qui définissent la façon dont un objet général ou un modèle de contenu ajoute ou supprime des objets dans l’arborescence logique. Pour obtenir un exemple de remplacement de l’arborescence logique, consultez Remplacer l’arborescence logique.

L'héritage de la valeur de la propriété

L’héritage des valeurs de propriété fonctionne via une arborescence hybride. Les métadonnées réelles qui contiennent la propriété Inherits qui active l’héritage des propriétés sont la classe FrameworkPropertyMetadata au niveau du framework WPF. Par conséquent, le parent qui contient la valeur d’origine et l’objet enfant qui hérite de cette valeur doivent être FrameworkElement ou FrameworkContentElement, et ils doivent tous les deux faire partie d’une arborescence logique. Toutefois, pour les propriétés WPF existantes qui prennent en charge l’héritage des propriétés, l’héritage de valeur de propriété peut se perpétuer à travers un objet intermédiaire qui ne fait pas partie de l’arborescence logique. Principalement, cela est pertinent pour que les éléments de modèle utilisent toutes les valeurs de propriété héritées définies sur l’instance qui est modèleée, ou à des niveaux encore plus élevés de composition au niveau de la page et, par conséquent, plus haut dans l’arborescence logique. Pour que l'héritage de la valeur d'une propriété fonctionne de manière cohérente à travers une telle limite, la propriété héritée doit être enregistrée en tant que propriété attachée. Vous devriez suivre ce modèle si vous avez l'intention de définir une propriété de dépendance personnalisée avec un comportement d'héritage de propriété. L’arborescence exacte utilisée pour l’héritage de propriété ne peut pas être entièrement anticipée par une méthode utilitaire de classe d’assistance, même au moment de l’exécution. Pour plus d’informations, consultez Héritage des valeurs de propriété.

Arborescence visuelle

Outre le concept de l’arborescence logique, il existe également le concept de l’arborescence visuelle dans WPF. L’arborescence visuelle décrit la structure des objets visuels, comme représenté par la classe de base Visual. Lorsque vous écrivez un modèle pour un contrôle, vous définissez ou redéfinissez l’arborescence visuelle qui s’applique à ce contrôle. L’arborescence visuelle est également intéressante pour les développeurs qui veulent un contrôle de niveau inférieur sur le dessin pour des raisons de performances et d’optimisation. Une exposition de l’arborescence visuelle dans le cadre de la programmation d’applications WPF conventionnelle est que les itinéraires d’événements pour un événement routé se déplacent principalement le long de l’arborescence visuelle, et non l’arborescence logique. Cette subtilité du comportement des événements routés peut ne pas être immédiatement apparente, sauf si vous êtes un auteur de contrôle. Le routage d’événements via l’arborescence visuelle permet aux contrôles qui implémentent la composition au niveau visuel de gérer les événements ou de créer des setters d’événements.

Arborescences, éléments de contenu et hôtes de contenu

Les éléments de contenu (classes dérivées de ContentElement) ne font pas partie de l’arborescence visuelle ; ils n’héritent pas de Visual et n’ont pas de représentation visuelle. Pour apparaître dans une interface utilisateur, un ContentElement doit être hébergé dans un hôte de contenu qui est à la fois un Visual et un participant d’arborescence logique. En général, un tel objet est un FrameworkElement. Vous pouvez conceptualiser que l’hôte de contenu est un peu comme un « navigateur » pour le contenu et choisit comment afficher ce contenu dans la région d’écran que l’hôte contrôle. Lorsqu'il est hébergé, le contenu peut être intégré en tant que participant à certains processus d'arborescence spécifiques qui sont normalement associés à l'arborescence visuelle. En règle générale, la classe hôte FrameworkElement inclut le code d’implémentation qui ajoute tout ContentElement hébergé à l’itinéraire d’événement via des sous-nœuds de l’arborescence logique de contenu, même si le contenu hébergé ne fait pas partie de la véritable arborescence visuelle. Cela est nécessaire afin qu’un ContentElement puisse sourcer un événement routé qui achemine vers n’importe quel élément autre que lui-même.

Traversée d’arbre

La classe LogicalTreeHelper fournit les méthodes GetChildren, GetParentet FindLogicalNode pour la traversée d’arborescence logique. Dans la plupart des cas, vous ne devez pas avoir à parcourir l’arborescence logique des contrôles existants, car ces contrôles exposent presque toujours leurs éléments enfants logiques en tant que propriété de collection dédiée qui prend en charge l’accès à la collection, comme Add, un indexeur, etc. La traversée d’arborescence est principalement un scénario utilisé par les auteurs de contrôles qui choisissent de ne pas dériver des modèles de contrôle prévus tels que ItemsControl ou Panel où les propriétés de collection sont déjà définies et qui ont l’intention de fournir leur propre prise en charge des propriétés de collection.

L’arborescence visuelle prend également en charge une classe utilitaire pour traverser l'arborescence visuelle, VisualTreeHelper. L’arborescence visuelle n’est pas exposée aussi facilement par le biais de propriétés spécifiques au contrôle. Par conséquent, la classe VisualTreeHelper est la méthode recommandée pour parcourir l’arborescence visuelle si nécessaire pour votre scénario de programmation. Pour plus d’informations, consultez vue d’ensemble du rendu graphique WPF.

Note

Il est parfois nécessaire d’examiner l’arborescence visuelle d’un modèle appliqué. Vous devez être prudent lors de l’utilisation de cette technique. Même si vous parcourez une arborescence visuelle pour un contrôle où vous définissez le modèle, les consommateurs de votre contrôle peuvent toujours modifier le modèle en définissant la propriété Template sur les instances, et même l’utilisateur final peut influencer le modèle appliqué en modifiant le thème système.

Itinéraires pour les événements routés sous la forme d’une « arborescence »

Comme mentionné précédemment, l’itinéraire d’un événement routé donné se déplace le long d’un chemin unique et prédéterminé d’une arborescence qui est un hybride des représentations visuelles et logiques. Le chemin de l'événement peut se déplacer soit vers le haut soit vers le bas dans la hiérarchie de l’arborescence, selon qu’il s’agit d’un événement routé en tunnel ou par propagation. Le concept d’itinéraire d’événement n’a pas de classe d’assistance qui prend directement en charge et peut être utilisée pour « parcourir » l’itinéraire d’événement indépendamment du déclenchement d’un événement effectivement acheminé. Il existe une classe qui représente l’itinéraire, EventRoute, mais les méthodes de cette classe sont généralement destinées à une utilisation interne uniquement.

Dictionnaires de ressources et arborescences

Recherche de dictionnaire de ressources pour toutes les Resources définies dans une page traverse essentiellement l’arborescence logique. Les objets qui ne se trouvent pas dans l’arborescence logique peuvent référencer des ressources clés, mais la séquence de recherche de ressources commence au point où cet objet est connecté à l’arborescence logique. Dans WPF, seuls les nœuds d’arborescence logique peuvent avoir une propriété Resources qui contient un ResourceDictionary. Par conséquent, il n’existe aucun avantage de parcourir l’arborescence visuelle à la recherche de ressources clés à partir d’un ResourceDictionary.

Toutefois, la recherche de ressources peut également s’étendre au-delà de l’arborescence logique immédiate. Pour le balisage d’application, la recherche de ressources peut ensuite passer aux dictionnaires de ressources au niveau de l’application, puis à la prise en charge du thème et aux valeurs système référencées en tant que propriétés statiques ou clés. Les thèmes eux-mêmes peuvent également référencer des valeurs système en dehors de l’arborescence logique de thème si les références de ressource sont dynamiques. Pour plus d’informations sur les dictionnaires de ressources et la logique de recherche, consultez ressources XAML.

Voir aussi