Depuis que j'utilise le WPF, un problème récurrent m'est apparu. En effet, il n'y a aucune fonction de recherche dans l'arbre logique des composants d'un logiciel WPF. Il existe une classe, VisualTreeHelper, qui propose quelques méthodes statiques intéressantes mais pas assez poussées. Imaginons que nous désirons récupérer un élément particulier de l'arbre logique, à partir de n'importe quel autre composant. Par exemple, nous disposons d'une structure telle que:

Structure_test_FindTreeHelper.jpg

Il serait intéressant de pouvoir accéder à partir d'un événement déclenché sur le bouton1 au bouton2. Pour ce faire, la propriété Name d'un objet de type FrameworkElement ou dérivé, est unique dans toute une arborescence WPF, et sur cette propriété que nous allons faire notre recherche de FrameworkElement

Si on s'attarde un peut sur les composants de base que nous fournit WPF dans le namespace System.Windows.Controls, on s'aperçoit qu'il existe 4 conteneurs différents. J'entends par conteneur un objet de type primitif FrameworkElement qui peut contenir d'autres objets. Voici la liste des trois conteneurs ainsi que la propriété qui permet de référencer les éléments enfants.

  • Decorator : il permet de référencer un et un seul élément enfant dans sa propriété Child
  • ContentControl : il permet de référencer un et un seul élément enfant dans sa propriété Content
  • ItemsControl : il permet de référencer une liste d'éléments enfants dans sa propriété Items
  • Panel : il permet de référencer une liste d'éléments enfants dans sa propriété Children

Bien que dans ce listage on ne peut pas voir la différence entre les objets du type ItemsControl et Panel, elle est majeur. D'un point de vue architecture, ItemsControl tout comme ContentControl héritent de Control. Par contre Panel hérite directement de FrameworkElement tout comme Control. De plus les items d'un objet ItemsControl seront affichés les uns derrière les autres avec des styles qui peuvent être différents si vous utilisez un DataTemplateSelector. Cette liste d'items peut bénéficier des fonctionnalités des scrolls. Les objets hérités de Panel quant à eux, permettent un placement logique des éléments enfants comme un Grid qui nous permet de les organiser en ligne et/ou en colonne, ou encore un Canvas où le développeur est libre de choisir la position relative des enfants par rapport à sa taille et sa position. Voici comment se présente cette hiérarchie:

Hierarchie_ContentCtrl_ItemsCtrl_Panel.jpg

Maintenant entrons dans le vif du sujet. Les composants WPF s'organisent dans un arbre qui est du type Arbre n-aires, pour ce faire, il nous faut faire une recherche sur un string qui correspondra à l'élément cherché. Deux mots clé C# vont nous être très utile : as pour tout ce qui est cast et var qui permet de prendre n'importe quel type.

Pour initialiser une recherche, il est préférable de se positionner sur l'élément racine de l'arbre, c'est-à-dire l'élément qui à pour Parent la valeur null. Voici la méthode qui permet d'atteindre la racine à partir de n'importe quel élément de l'arborescence :

FindTreeHelper_GetRacine.jpg

Maintenant que nous pouvont atteindre la racine, à partir de n'importe quel élément, nous allons effectuer notre recherche. Nous devons parcourir tous les éléments enfants en prenant compte les quatre types de conteneurs du framework. Cet algorithme est un exemple de parcours en profondeur sur un arbre n-aires. C'est par ce même parcours que les navigateurs interprètent les applications Silverlight basées également sur du WPF. Voici la méthode de recherche :

FindTreeHelper_GetElementFromLogicalTree.jpg

Cet algorithme est efficace pour la recherche d'un seul élément. Mais imaginons maintenant que nous voulons effectuer une recherche sur une clé qui n'est pas unique. Dans ce cas, deux choix s'offre à nous. Soit on décide de récupérer le premier élément possédant la clé, ce qui n'est pas très judicieux si l'élément ciblé se trouve, par exemple, en troisième position. Soit on récupère la liste de tout les éléments concordants avec la clé. Pour se faire un nouveau mot clé nous sera utile : yield. Ce mot clé offert par .Net nous permet de remplir un IEnumerable<> dans une boucle. Prenons le cas où nous voulons récupérer tous les éléments d'un type donné. Par exemple si l'on veut récupérer l'ensemble des boutons d'un arbre logique WPF. Voici à quoi doit ressembler l'algorithme.

FindTreeHelper_GetElementsFromLogicalTree.jpg

Nous avons désormais deux parcours similaires mais avec un retour différent. Maintenant nous pouvons imaginer n'importe quelle recherche sur n'importe quelle clé. Il nous reste à faire un accès à ces nouvelles fonctionnalité par la méthode décrite ci-dessous.

FindTreeHelper_Find.jpg

Le parcours de l'arbre logique n'a plus de secret désormais mais qu'en est-il de l'arbre visuel, c'est-à-dire, une arborescence définie dans le Template d'un composant. L'extension à l'arbre visuel est aisée. En effet La classe VisualTreeHelper, grâce à sa méthode GetChild, nous permet d'accéder à l'élément racine dans le Template de l'élément ciblé. De plus, toute l'arborescence définie dans un Template n'est autre qu'un arbre logique. Donc il nous suffit d'atteindre cet élément racine au sein du Template et de lancer la recherche effectuée par les algorithmes précédents.

Je tiens à remercier Arnaud Auroux pour l'idée.