David Catuhe
Le pourquoi du comment des Dependency Properties
Visite guide de la technologie des Dependency Properties introduite par WPF
Par David Catuhe publié le 27/08/2007 à 01:00, lu 8294 fois, 4 pages
 2 | Dependency Properties
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 pour les notifications et ce support est important pour le développement en XAML.

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.

WPF fournit un framework complet pour la mise en oeuvre 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.

class MonControle : Control

{

    public static readonly DependencyProperty ValeurProperty;

 

    static MonControle()

    {

        ValeurProperty = DependencyProperty.Register("Valeur", typeof(float), typeof(MonControle));

    }

 

    public float Valeur

    {

        get

        {

            return (float)GetValue(ValeurProperty);

        }

        set

        {

            SetValue(ValeurProperty, value);

        }

    }

}

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

  1. 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
  2. Gestion des notifications
  3. 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, ...)
  4. 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 :

<Trigger Property="Valeur" Value="0">

    <Setter Property="Visibility" Value="Hidden" />

</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).

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.
 
/content/ef34d349-d35e-4304-9f5a-fead37866b9e/Capture01.png
 
Le code de cette fenêtre est au moins aussi simple :

<Window x:Class="WindowsApplication1.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="WindowsApplication1" Height="300" Width="300"

    >

    <Grid>

        <Button Width="100" Height="100" VerticalAlignment="Center" HorizontalAlignment="Center">

            Hello

        </Button>

    </Grid>

</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 :
 
/content/ef34d349-d35e-4304-9f5a-fead37866b9e/Capture02.png
 
Le code XAML associé :

<Window x:Class="WindowsApplication1.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="WindowsApplication1" Height="300" Width="300"

    FontSize="20"

    >

    <Grid>

        <Button Width="100" Height="100" VerticalAlignment="Center" HorizontalAlignment="Center">

            Hello

        </Button>

    </Grid>

</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é :

  • 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.
  • Animations : Les animations peuvent modifier ou remplacer la valeur issue de l'étape précédente
  • 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.
  • 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.
 
» Démarrer une discussion
 
Discussion démarée par Loic Berthollet le 02/03/2008 à 11:37, 2 commentaire(s).