See Sharp
Is it really just a matter of point of view ?

DependencyProperty setters & Binding : You can't always get what you want...

February 6, 2008 14:16 by salfab

... but if you try sometimes, you might find you get what you need !

En effet, le philosophe Jagger avait absolument raison.

 Une particularité intéressante de C# sur nombre de langages concurrents est certainement la notion de propriétés :

Alors qu'en Java, il est nécessaire de créer des méthodes setters & getters pour ne pas exposer les membres privés d'une classe, le langage C# permet d'exposer une sorte de variable publique qui permettra de retourner ou d'assigner une valeur à un membre privé, mais surtout, d'ajouter une logique applicative lors de la manipulation de ce membre. Un exemple typique serait d'incrémenter un compteur qui définirait le nombre de modifications effectuées sur un certain membre privé.

À noter que Python a introduit le concept de propriétés dès sa version 2.2 à condition d'utiliser les new style classes.

Dès .Net 3.0, une nouvelle sorte de propriété à fait son apparition, les DependencyProperties. Elles sont utilisées notamment pour permettre le DataBinding entre des éléments graphiques de l'interface utilisateur décrite en XAML et d'autres objets étant par exemple décrits dans le codebehind. Il est alors très simple grâce à un binding de garder une synchonisation en tout temps entre une textbox et une valeur stockée dans un objet fait maison. (la classe ayant servi à l'instanciation de cet objet maison dérivera de DependencyObject)

Exemple de dataBinding entre une TextBox et une DependencyProperty :

<TextBox Text="{Binding Source={StaticResource myEcwViewInfos}, Path=EcwViewZoom}"/>

À noter que dans notre exemple, myEcwViewInfos est une ressource déclarée dans Window.Resources, et la classe utilisée pour l'instancier contient une DependencyProperty appellée EcwViewZoom.

Cependant, la façon dont sont stockées les propriétés dans l'objet est radicalement différente de l'approche que l'on connait avec les propriétés habituelles : les valeurs sont stockées et retournées grâce aux méthodes SetValue et GetValue, et qui sont directement gérées par la framework.

Afin de rendre ce concept plus transparent, il est possible de wrapper ces appels dans une propriété .Net classique. C'est d'ailleurs ce que fait par défaut le snippet propdp dans visual studio 2008. 

public double EcwViewZoom
{
    get { return (double)GetValue(EcwViewZoomProperty); }
    set
   
{
        m_counter++;

        SetValue(EcwViewZoomProperty, value);
    }
}

// Using a DependencyProperty as the backing store for EcwZoom. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EcwViewZoomProperty =
DependencyProperty.Register("EcwViewZoom", typeof(double), typeof(EcwViewInfos), new UIPropertyMetadata(1.0));

Imaginons maintenant que nous désirions ajouter de la logique applicative pour compter le nombre de lectures d'une DependencyProperty ( DP pour les intimes ) : le premier reflexe serait d'incrémenter un membre privé de type int dans le setter de la DP. 

Si nous établissons un Binding entre une TextBox et cette DP, nous pourrons remarquer que le contenu de la textbox et la DP seront en tout temps synchronisés, mais que jamais, le compteur ne sera incrémenté, quand bien même nous effectuons une multitude de modification de la textbox. ne vous inquiétez pas, l'explication est tout à fait logique.

Comme mentionné précédemment, l'accès à la DP se fait par les méthodes GetValue et SetValue. Le wrapper .Net est là uniquement pour uniformiser l'accès à une DP depuis le codebehind, ainsi, accèder à une Propriété ou une DP se fait syntaxiquement de la même manière, mais la framework, en interne, accède directement aux méàthodes Getvalue / SetValue. Il est alors plus facile de comprendre le comportement sur le Binding : le Wrapper .Net de la DP n'étant jamais appellé par le biais du binding car la framework appelle directement SetValue, le compteur ne se voit jamais incrémenté. Si l'ajout de logique applicative est nécessaire en cas d'assignation de la DP, il convient d'utiliser les RegisteredCallbacks. La méthode Register de DependencyProperty permet de définir une méthode à appeller lors de la modification de la DP.

public static readonly DependencyProperty EcwViewZoomProperty =
DependencyProperty.Register("EcwViewZoom", typeof(double), typeof(EcwViewInfos), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(EcwViewInfos.OnZoomChanged)));

La méthode OnZoomChanged sera alors appellée lorsque la propriété sera modifiée via la méthode SetValue.

public static void OnBoundariesChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
    // ...
}