www.bewise.fr

Recherche

Le pourquoi du comment des Dependency Properties

Par David Catuhe, posté le 26/08/2007

Profil : Développeur | Niveau : Intermédiaire (200)

Tags : WPF | Partager : Partager sur Delicious Partager sur Facebook Partager sur Twitter

1 Introduction

Au-delà du renouveau de l'aspect graphique, WPF introduit de nouvelles fonctionnalités très intéressantes au niveau de la programmation et du code.

Parmi ces nouveautés, le mécanisme de Dependency Properties tire son épingle du jeu.

Nous allons tout au long de cet article, nous intéresser au fonctionnement et à la philosophie de cette technologie.

2 Dependency Properties

2.1 Pourquoi faire ?

WPF introduit de nombreux outils pour construire des interfaces graphiques. Parmi ces outils nous pouvons citer la gestion des styles, le mécanisme d'animations ou bien encore la très riche technologie de Data Binding.

Tous ces outils s'appuient de manière forte sur les dependency properties qui constituent le nouveau service de propriétés évoluées de WPF.

Mais finalement, pourquoi fournir un nouveau service de propriétés ? En effet, le .Net propose déjà une solution simple et élégante pour construire des propriétés sur des objets.

Le principal problème avec les propriétés de .Net, vient du fait qu'elles ne permettent pas nativement de construire la valeur de la propriété en fonction de plusieurs sources.

Or dans le cas de WPF, la propriété d'un objet va à la fois dépendre de sa valeur intrinsèque mais aussi des animations potentielles qui sont branchées dessus ou bien encore d'un éventuel data binding.

De plus, nativement les dependency properties fournissent un support important pour le développement en XAML., pour les notifications.

Finalement, et historiquement, les développeurs de XAML se sont vus confrontés à un problème de taille : l'abondance des propriétés et leur multiplication sur tout l'arbre visuel. Nous verrons par la suite que les dependency properties proposent un mécanisme de stockage centralisé associé à un partage des valeurs le long de l'arbre visuel pour palier à ce problème.

2.2 Mise en oeuvre

WPF fournit un framework complet pour la mise en ouvre des dependency properties (le code en question est du pur .Net 2.0).

Pour ce faire il suffit de déclarer une variable statique de classe System.Windows.DependencyProperty. Dans le constructeur statique de notre classe nous faisons un appel à la méthode Register pour enregistrer auprès du stockage central notre propriété.

Pour des raisons de lisibilité et de facilité, nous pouvons créer une propriété (au sens .Net du terme) qui se chargera de rediriger les appels vers la dependency property. Pour ce faire nous utilisons les méthodes GetValue et SetValue (qui sont héritées de la classe System.Windows.DependencyObject). Il faut toutefois noter que la propriété .Net n'est pas nécessaire puisque les méthodes GetValue et SetValue sont publiques.

D'ailleurs il est très important de ne jamais rajouter d'autres codes dans les propriétés .Net que nous écrirons car le code généré par XAML ne passe pas par les propriétés mais directement par GetValue et SetValue.

   1:  class MonControle : Control
   2:  {
   3:   public static readonly DependencyProperty ValeurProperty;
   4:   
   5:   static MonControle()
   6:   {
   7:       ValeurProperty = DependencyProperty.Register("Valeur", typeof(float), typeof(MonControle));
   8:   }
   9:   
  10:   public float Valeur
  11:   {
  12:       get
  13:       {
  14:             return (float)GetValue(ValeurProperty);
  15:       }
  16:       set
  17:       {
  18:             SetValue(ValeurProperty, value);
  19:       }
  20:   }
  21:  }

Même si le code peut sembler plus lourd qu'une simple propriété il faut voir plusieurs avantages à ce mécanisme :

· Gain de mémoire : le stockage centralisé est optimisé et requiert moins de place surtout lorsque de nombreuses propriétés sont gérées

· Gestion des notifications

· Comportements intégrés : Lors de l'enregistrement d'une dependency property il est possible de préciser des comportements (remettre à jour l'affichage, support du data binding, .)

· Intégration au XAML

En ce qui concerne l'intégration au XAML, les dependency properties permettent à WPF via les triggers de s'abonner directement au changement d'une valeur sans d'autres interventions du développeur.

Exemple :

   1:  <Trigger Property="Valeur" Value="0">
   2:    <Setter Property="Visibility" Value="Hidden" />
   3:  Trigger>

Dans cet exemple, WPF sera prévenu lorsque la valeur de notre dependecy property aura atteint 0. A ce moment la, il viendra appeler la méthode SetValue(Control.VisibilityProperty, Visibility.Hidden).

2.3 Fonctionnement interne

L'autre grande force des dependency properties réside dans leur capacité à construire leur valeur depuis plusieurs sources.

Prenons comme exemple une fenêtre simple avec un bouton au centre.

image

Le code de cette fenêtre est au moins aussi simple :

   1:  <Window x:Class="WindowsApplication1.Window1"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      Title="WindowsApplication1" Height="300" Width="300"
   5:      >
   6:   <Grid>
   7:    <Button Width="100" Height="100" VerticalAlignment="Center" HorizontalAlignment="Center">
   8:      Hello
   9:    Button>
  10:   Grid>
  11:  Window>

Si nous rajoutons une précision sur la taille des fontes à utiliser au niveau de la fenêtre, le bouton recevra automatiquement cette information et la transmettra au texte qu'il contient :

image

Le code XAML associé :

   1:  <Window x:Class="WindowsApplication1.Window1"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      Title="WindowsApplication1" Height="300" Width="300"
   5:      FontSize="20"
   6:      >
   7:  <Grid>
   8:    <Button Width="100" Height="100" VerticalAlignment="Center" HorizontalAlignment="Center">
   9:      Hello
  10:    Button>
  11:  Grid>
  12:  Window>

On parle ici d'héritage des valeurs de propriétés. La valeur de la propriété FontSize du texte inclus dans le bouton dépend donc de la valeur de la propriété FontSize de la fenêtre. Le nom des dependency properties trouve donc sa raison dans ce comportement.

Pour obtenir la valeur d'une propriété, WPF évalue donc une chaine de sources potentielles avec des priorités différentes.

De la plus prioritaire à la moins prioritaire, voici la liste des sources d'une dependency property :

1. Valeur locale

2. Style triggers

3. Template triggers

4. Style setters

5. Theme style triggers

6. Theme style setters

7. Héritage de valeur : Cette source peut potentiellement être désactivée lors du Register en ne faisant pas passer la constante : FrameworkPropertyMetadataOptions.Inherit.

8. Valeur par défaut

Dans le cadre de notre bouton, la source de priorité 7 (la valeur d'héritage) l'emporte donc. Suivant cette liste de priorité on peut donc déduire que si le bouton avait fixé une valeur à sa propriété FontSize, c'est cette valeur qui aurait été retenue (priorité 1).

Après avoir évalué la valeur d'une propriété WPF passe par 4 étapes avant de fournir la valeur définitive d'une propriété :

1. Evaluation des expressions : Si la valeur est une expression (issue de System.Windows.Expression), alors cette dernière est évaluée à ce moment. Les expressions sont produites par les ressources et les animations.

2. Animations : Les animations peuvent modifier ou remplacer la valeur issue de l'étape précédente

3. Evaluation des contraintes : Lorsqu'un développeur enregistre (via Register) une propriété il peut fournir une méthode qui sera appelée à ce niveau. Cette méthode permet de contraindre (Coerce) la valeur entre des bornes ou appliquer des règles métiers dessus.

4. Validation : Lorsqu'un développeur enregistre (via Register) une propriété il peut fournir une méthode de validation. Cette dernière fournit son aval sur la valeur en retournant true ou false pour laisser passer la valeur ou au contraire lever une exception.

3 Attached Properties

Une attached property est une dependency property qui peut être attachée à postériori à un objet.

Ces dernières permettent notamment à des containers (Grid, Canvas, .) de trouver des propriétés importantes pour leur rendu sur les contrôles qu'ils contiennent.

Prenons par exemple une grille avec 2 lignes :

   1:  <Grid>
   2:    <Grid.RowDefinitions>
   3:      <RowDefinition>RowDefinition>
   4:      <RowDefinition>RowDefinition>
   5:    Grid.RowDefinitions>
   6:  Grid>

Si nous souhaitons positionner un bouton dans la ligne 2, il faut que ce bouton puisse l'indiquer à la grille. Or, de base, un bouton n'a pas ce genre de propriétés (et heureusement, sinon il faudrait fournir des contrôles extrêmement complexes avec toutes les propriétés de tous les containers possibles).

C'est donc ici qu'interviennent les propriétés attachées. En effet, le bouton va s'attacher à une valeur pour une propriété connue et hébergée par la grille, pour que cette dernière puisse construire correctement son layout :

   1:  <Grid>
   2:    <Grid.RowDefinitions>
   3:      <RowDefinition>RowDefinition>
   4:      <RowDefinition>RowDefinition>
   5:    Grid.RowDefinitions>
   6:    <Button Grid.Row="1">
   7:    Button>
   8:  Grid>

En C# le résultat équivalent se ferait aussi simplement :

   1:  bouton.SetValue(Grid.RowProperty, 1);

On voit bien ici que le bouton se comporte comme si il avait une dependency property RowProperty.

Il est aussi intéressant de voir que ces propriétés se comportent comme des dependency properties directes, à savoir qu'elles vont aussi fournir les services d'héritage de valeurs.

En reprenant notre exemple avec la taille des fontes :

   1:  <Window x:Class="WindowsApplication1.Window1"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      Title="WindowsApplication1" Height="300" Width="300"
   5:      >
   6:  <Grid TextElement.FontSize="20">
   7:    <Button Width="100" Height="100" VerticalAlignment="Center" HorizontalAlignment="Center">
   8:      Hello
   9:    Button>
  10:  Grid>
  11:  Window>

Via une propriété attachée, c'est cette fois la grille qui définit la taille des fontes malgré le fait qu'elle ne porte pas de propriétés pour cela. Et pourtant cela fonctionne et le texte du bouton reçoit bien la bonne valeur.

Les propriétés attachées peuvent être issues de n'importe quelle dependency property mais si l'on sait que la propriété que l'on crée va être ultérieurement attachée, il vaut mieux pour des raisons d'optimisations la déclarer ainsi :

   1:  class MonControle : Control
   2:  {
   3:      public static readonly DependencyProperty ValeurProperty;
   4:   
   5:      static MonControle()
   6:      {
   7:          ValeurProperty = DependencyProperty.RegisterAttached("Valeur", typeof(float), typeof(MonControle));
   8:      }
   9:  }

4 Conclusion

Nous l'avons vu, les dependency properties sont un mécanisme très puissant sur lequel s'appuie WPF.

Elles ouvrent de nombreuses possibilités et apportent une facilité de développement vraiment intéressante.

Nous verrons dans un futur article comment WPF a également introduit de nouvelles fonctionnalités au niveau des événements.

Passionné de développement et geek dans l’âme. David dirige Bewise qu’il a fondé en 1999 avec ses acolytes Frédéric Colin et Yann Faure. Il est également très impliqué dans le développement du moteur 3D Nova pour la société Vertice qu’il a fondé en 2002 avec son ami Michel Rousseau. Les sujets qui lui tiennent à cœur sont autour des technologies WPF, Silverlight, Windows Phone 7 ou encore DirectX.

Voir les autres publications de l'auteur


Commentaires