Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

XAML : arbre visuel vs arbre logique sous WPF et WinRT

[new:30/10/2014]WPF propose deux visions de l’arbre des objets visuels là où WinRT ne propose de base qu’une seule approche. Mais quelle est la différence entre arbre logique et arbre visuel et comment s’en servir à la fois sous WPF et WinRT ?

Arbre logique et arbre visuel

Les éléments d’une interface XAML entretiennent des liens hiérarchiques, une grille contient un StackPanel qui lui-même contient des boutons par exemple. Chaque conteneur possède des enfants visuels qui eux-mêmes peuvent avoir des enfants visuels, et ce ad libitum (en tout cas tant que la mémoire le supporte !)

Cette organisation hiérarchisée des contrôles n’a rien d’exotique et tout le monde le comprend généralement bien.

Donc d’où vient cette nuance entre abre logique et arbre visuel ?

Prenons un exemple très simple de code XAML :

<Window>
<Grid>
<Label Content="Label" />
<Button Content="Button" />
</Grid>
</Window>

 

Une telle construction donne l’arbre suivant :

image

On voit que que le concepteur de cet affichage a utilisé une Window comme élément de base, c’est donc ici une page WPF, et qu’il a placé une Grid sur cette fenêtre et à l’intérieure de celle-ci un Label et un Button.

Mais ce que montre l’arbre de l’illustration c’est qu’il existe bien d’autres contrôles, pas tous visibles, qui constituent le véritable arbre des relations entre tous les contrôles. Ces contrôles sont généralement ce qui forme le Template de leur contrôle parent.

Ainsi un Label peut être templaté pour être formé d’un Border contenant un ContentPresenter qui enfin contient un TextBlock. De même le Button dispose lui aussi une hiérarchie visuelle similaire. Quant à l’objet Window il contient par exemple Border dans lequel est placé un AdornerDecorator etc…

On le comprend aisément il existe une différence entre l’arbre dit Visuel qui contient toute la hiérarchie dont les Templates et l’arbre dit Logique qui n’est fait que d’un Window, d’une grille et deux autres contrôles – le Label et le Button.

L’arbre Visuel c’est la totalité du schéma ci-dessus, l’arbre Logique n’est que la partie montrant les contrôles sur fond orange.

En quoi cette nuance est-elle utile ?

Je pense que vous pouvez trouver seul la réponse à cette question. Traverser l’arbre logique est plus conforme à l’idée que vous vous faites de l’UI que vous avez conçue : si vous n’avez placé qu’une grille avec un Label et un Button le tout sur une fenêtre WPF “votre arbre des contrôles tel que vous le concevez mentalement est l’arbre dit Logique. Si vous devez traverser l’arbre pour faire des traitements (rendre visible ou non certains contrôles par exemple) ce qui compte pour vous et votre code c’est de parcourir l’arbre Logique.

Si vous désirez atteindre les parties plus intérieures de chaque contrôle vous aurez alors besoin de balayer la totalité de l’arbre, donc l’arbre Visuel.

Rien ne sert de balayer tous les contenus de Template si on cherche uniquement à changer la couleur d’une série de boutons par exemple.

Mais la nuance entre les deux arbres n’est pas seulement qu’intellectuelle elle marque aussi une différence essentielle dans la façon dont WPF par exemple gère certaines opérations.

L’arbre Logique

C’est l’arbre du Designer pourrait-on dire, celui des contrôles de rang “supérieurs”, ceux qu’on place sur la surface choisie pour créer une UI. Il décrit les relations entre les éléments et l’interface utilisateur. Cet arbre est responsable de nombreuses fonctions :

  • L’héritage des valeurs des propriétés de dépendance (DependencyProperty)
  • La résolution des références aux ressources dynamiques (DynamicResources)
  • La recherche des noms d’éléments pour la gestion du Binding
  • La transmission des évènements de type RoutedEvents

 

L’arbre Visuel

L’arbre Visuel peut être vu comme l’arbre “technique” celui qui dévoile toute la “plomberie” et toutes les relations sous-jacentes entre les contrôles et leurs templates. Cette arbre possède d’autres responsabilités que l’arbre logique :

  • Le rendu des éléments visuels
  • La propagation de l’opacité des éléments visuels
  • La propagation des Layout et RenderTransforms
  • La propagation de la propriété IsEnabled
  • Le test de Hit

La résolution des sources relatives des Bindings (FindAncestor)

Traiter les arbres avec C#

Maintenant que nous savons qu’il existe deux types d’arbres et que en voyons l’intérêt reste à savoir comment traiter ces arbres de façon différente…

Pour cela le Framework .NET ou WinRT mettent à notre disposition des classes Helper. Sous WPF, comme d’habitude, nous disposons d’un XAML complet accompagné d’un Framework qui l’est tout autant, c’est ainsi que nous pouvons utiliser aussi bien le VisualTreeHelper de System.Windows.Media ou bien le LogicalTreeHelper de System.Windows.

Sous WinRT il faut se contenter du seul VisualTreeHelper de Windows.UI.Xaml.Media.

Avec WPF il est donc simple de traiter l’un ou l’autre des deux arbres puisque le Framework met à notre disposition des Helpers différenciés. Mais sous WinRT il existe deux nuances : la première est l’absence de traitement de l’arbre logique, la seconde est que l’héritage des éléments visuels est légèrement différent puisque UIElement descend directement de DependencyObject. Sous WPF la chaîne d’héritage est plus longue (Object, DispatcherObject, DependencyObject, Visual, et enfin UIElement.

Il reste néanmoins possible de simuler le fonctionnement de WPF sous WinRT, mais malgré la taille gigantesque de WinRT par rapport à .NET c’est à nous de rajouter du code ce qui est un peu un comble…

Pour ce faire on peut s’aider du VisualTreeHelper de Windows.Ui.Xaml. Et comme ce code n’est pas tout à fait pratique on peut le compléter pour le rendre plus directement utile :

IEnumerable<DependencyObject> GetVisualTree(DependencyObject obj)
{
    var list = new List<DependencyObject>() { obj };
    var res = obj;
    while ((res = VisualTreeHelper.GetParent(res)) != null)
        list.Add(res);
    return list.AsEnumerable().Reverse();
}

 

Si on imagine une ListBox posée sur notre surface de design et qu’on l’utilise comme base de notre recherche de l’arbre visuel en utilisant le code ci-dessus nous écrirons :

listbox.ItemsSource = GetVisualTree(listbox).Select(d => d.GetType().Name);

La listbox va alors se remplir de la liste de tous ces constituants (ScrollViewer, Border, Grid, ScrollContentPresenter etc).

Mais où est l’arbre logique dans tout ça ? Patience il arrive !

Sous WPF pas de question à se poser avec le Helper déjà fourni comme indiqué plus haut, mais sous WinRT il faut le simuler ce qui donne le code suivant :

// Simulation de WPF LogicalTreeHelper.GetParent

public static DependencyObject GetParent ( DependencyObject courant)
{
    if (courant == null )
    {
        throw new ArgumentNullException ("courant" );
    }

    FrameworkElement élément = courant as FrameworkElement ;
    if (élément ! = null )
    {
        return élément.Parent;
    }

    FrameworkContentElement element2 = courant as FrameworkContentElement ;

    si (element2 ! = null )
    {
        return element2.Parent;
    }
    return null ;
}

 

// Simulation du Helper WPF pour UWP

IEnumerable < DependencyObject > GetLogicalTree ( DependencyObject obj)
{
    var liste = new list < DependencyObject > () {obj};
    var RES = obj;
    while ((RES = GetParent (RES)) ! = null )
        liste.Add(RES);
    retur liste. AsEnumerable().Reverse();
}

DependencyObject GetParent ( DependencyObject obj)
{
    var s = obj as FrameworkElement ;
    if (s ! = null )
        return s.Parent;
    return null ;
}

Une fois GetParent simulé ou peut enfin écrire le GetLogicalTree. Et le tour est joué !

Conclusion

XAML nous offre des hiérarchies d’objets qu’on peut voir de deux façons, une logique et une visuelle. WPF et le Framework .NET donne un accès simplifié à ces deux arbres mais pas WinRT. Toutefois en il est possible de simuler la fonction voire de s’en servir pour créer un code portable WPF/WinRT.

La prochaine fois que vous souhaiterez parcourir l’arbre des objets d’une application XAML demandez-vous quel arbre vous devez balayer, et souvenez de cet article !

Bon accrobranche xamélien !

Stay Tuned !

blog comments powered by Disqus