Au coeur des Dependency Property / Attached Property#

Nous avons parlé dans un précédent post du databinding WPF/Silverlight en expliquant qu’est ce que le databinding, les Dependency Property, Attached Property et DataContext. Dans cet article nous allons nous intéresser plus particulièrement aux Dependency Property et aux Attached Property pour voir comment sont-ils implémentés.

Dependency Properties

Pour ce faire, nous allons commencer par créer une Dependency property, nous disposons pour cela du snippet VS, positionnez vous dans le code behind d’un fichier xaml et tapez propdp puis 2 fois sur la touche TAB.

Voici le code créé :

public int MyProperty
{
      get { return (int)GetValue(MyPropertyProperty); }
      set { SetValue(MyPropertyProperty, value); }
}

// Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyPropertyProperty =
        DependencyProperty.Register("MyProperty", typeof(int), typeof(MyType), new PropertyMetadata(MyPropertyChanged));

Dans le code créé par défaut par ce snippet, j’ai modifié deux petites partie : le type au quel on rajoute la Dependency Property (MyType) et une nouvelle instance de PropertyMetaData à la quelle on passe en paramètre le délégué qui traite le callback de l’évènement DependencyPropertyChanged (MyPropertyChanged). On s’arrête juste un petit instant sur ce point : On avait dit l’article précédent qu’une Dependency Property embarque des moyens de notification, à l’aide de cette méthode on va pouvoir interagir lorsque la propriété est modifiée. Voici la méthode MyPropertyChanged :

 static void MyPropertyChanged(DependencyObject obj,
                        DependencyPropertyChangedEventArgs args)
{

 }

Le premier argument est l’instance de la classe dont la propriété a été modifiée. Le deuxième argument est un objet de type DependencyPropertyChangedEventArgs qui nous met à disposition des données précieuses

image 

NewValue : Valeur de la propriété après modification (de type object)

OldValue : Valeur de la propriété avant modification (de type object)

Property : L’instance de  la Dependency Property

Dans la déclaration le la Dependency Property, nous distinguons 2 parties :

- Une propriété CLR (MyProperty), nous remarquons les méthodes GetValue et SetValue respectivement utilisées dans le Set et le Get de la propriété. Ces deux méthodes sont définies au niveau de Dependency Object c’est pour cela que la classe qui définit une Dependency Property doit être un Dependency Object. Elles servent à récupérer / mettre à jour la valeur effective de la Dependency Property. La valeur n’est donc pas stockée au niveau de l’objet qui définit la Dependency property.

- Une Dependency Property qui, comme l’indique si bien le commentaire généré, fait office de stockage pour la propriété CLR MyProperty. Intéressons nous maintenant à la méthode statique Register de la classe DependencyProperty. Si on fait un petit Reflector sur WindowsBase.dll –> System.Windows –> Dependency Property on trouve la méthode Register. Et quelques appels de méthodes plus loin on se retrouve dans la méthode RegisterCommon qui  nous intéresse. Au début de cette méthode nous retrouvons ces quelques lignes de code :

image

La classe FromKeyName est instanciée pour avoir la clé Key à partir du nom de la propriété et du type qui définit la Dependency Property, ensuite la clé résultat est utilisée pour voir si cette elle est contenue dans PropertyFromName et si c’est le cas on dit que la propriété existe déjà. Donc ce qu’on peut en tirer pour l’instant c’est que PropertyFromName contient toutes les clés des Dependency Properties. Si on regarde PropertyFromName on s’aperçoit que c’est une Hashtable, nous savons qu’une Hashtable est une collection de pairs clé / valeur. Donc on connait déjà la clé qui est la clé des Dependency property de type FromKeyName, il nous manque uniquement la valeur.

Poursuivons maintenant la lecture de la méthode, pour trouver plus loin le stockage de la Dependency Property à l’aide de cette ligne :

image

Où dp est la Dependency Property.

Le stockage des Dependency Properties se fait donc tout simplement dans une Hashtable où la clé est de type FromNameKey et la valeur est la Dependency Property.

Attached Properties

Regardons maintenant comment ça se passe pour une Attached Property. Le snippet VS pour créer une Attached Property est propa voici le code généré :

 

public static int GetTestProperty(DependencyObject obj)
{
      return (int)obj.GetValue(TestPropertyProperty);
}

public static void SetTestProperty(DependencyObject obj, int value)
{
      obj.SetValue(TestPropertyProperty, value);
}

// Using a DependencyProperty as the backing store for TestProperty.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty TestPropertyProperty =
            DependencyProperty.RegisterAttached("TestProperty", typeof(int), typeof(Button), new PropertyMetadata(MyPropertyChanged));

Nous remarquons deux différences :

- Le getter / setter sont deux fonctions statiques au lieu d’une propriété dans le cas d’une Dependency Property. C’est grâce à ça qu’on peut bénéficier des Attached Properties sur tous les objets.

- On utilise la méthode RegisterAttached au lieu de Register. En creusant un peu avec Reflector WindowsBase.dll –> System.Windows –> Dependency Property on trouve la méthode RegisterAttached qui appelle une autre méthode appelée RegisterAttached qui appelle elle même la méthode RegisterCommon appelée lors du Register d’une Dependency Property. On retombe donc sur nos pieds!!!

image

Le stockage d’une Attached Property se fait de la même manière que pour une Dependency Property c’est à dire dans une Hashtable.

c# | c#3 | Silverlight | Silverlight 3 | WPF
Thursday, November 26, 2009 10:49:42 AM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Databinding : Dependency property, Attached property, DataContext#

Le data binding lie un composant UI qui hérite de System.Winwdows.FrameworkElement à une source de données, pour cela il y a deux conditions fondamentales :

- Il faut que le composant cible du data binding soit un FrameworkElement

- Il faut que la propriété cible soit une DependencyProperty

Nous avons vu dans ce post comment on arrive à lier une grille à une source de données, pour rappel nous avons utilisé cette ligne de code :

<Data:DataGrid x:Name="TasksGrid" ItemsSource="{Binding Tasks}">
</Data:DataGrid>

A voir cette ligne, nous pouvons penser que c’est magique, pour comprendre comment ça marche derrière, nous allons écrire la même chose par code. Ça donne ça :

TasksViewModel tasksvm = new TasksViewModel();

Binding bind = new Binding("Tasks");
TasksGrid.SetBinding(DataGrid.ItemsSourceProperty, bind); 

//Partie existante
this.DataContext = tasksvm;
tasksvm.FillTasks();

Si nous traduisons les 2 lignes qui nous intéressent, nous commençons par instancier un objet de type Binding en lui donnant en paramètre le nom de la propriété du type de l’objet au quel la grille va être liée. Ensuite nous disons à la grille qu’elle doit utiliser l’objet bind de type Binding pour la liaison de sa propriété ItemSource. Donc en fait ce que nous pouvons en tirer c’est que la liaison entre la grille et la source de données ne se fait pas automatiquement mais qu’il y a un objet de type Binding qui régit cette liaison. Cet objet va notifier le moteur de binding à chaque fois qu’il y a un changement dans la source de données, ce moteur va mettre à jour la Dependency property correspondante (ItemSource dans notre exemple) et va ordonner un rafraîchissement du composant graphique.

Depenedency property

La dependency property est une propriété .NET étendue qui embarque des moyens de notification et de liaison avec une source de données et dont la valeur n’est connue qu’à l’exécution (Runtime). Elle peut enrichir n’importe quelle classe du moment que cette dernière est un Dependency object mais elle est majoritairement utilisée dans les composants graphiques.

DataContext

Le DataContext est une propriété que l’on retrouve dans tous les contrôles bindable à une source de données. Elle est héritée du contrôle parent et peut être redéfinie au niveau de l’enfant. Elle est utilisée essentiellement pour le databinding, elle représente la source de données et contient en général une instance d’une entité. Si on reprend notre exemple ci-dessus :

<Data:DataGrid x:Name="TasksGrid" ItemsSource="{Binding Tasks}">

Avec {Binding Tasks} on dit que la propriété ItemsSource du datagrid est une propriété liée à la propriété Tasks de l’objet de liaison, mais où est ce qu’on définit l’objet de liaison? En effet, l’objet de liaison n’est rien d’autre que le DataContext, donc il suffit de peupler le DataContext avec l’objet contenant la propriété Tasks et le moteur de binding ira chercher les données de cet emplacement lorsqu’il le faudra.

Attached Property

Une Attached Property c’est le même principe qu’une Dependency Property à une exception près : Tous les Dependency Object peuvent bénéficier d’une Attached Property. Vous allez me dire, mais à quoi ça sert ??? En effet, dans certains cas, une propriété d’un élément graphique ne prend sens que dans un certain contexte, donc pour éviter que tous les éléments graphiques embarquent toutes les propriétés possibles et imaginables dans tous les différents contextes qui existent, les attached property ont été créés . C’est encore flou :) Prenons un exemple : considérons un DockPanel qui contient des éléments graphiques.

<DockPanel>
         <TextBox DockPanel.Dock="Left"/>
</DockPanel>

Le contrôle TextBox ne possède pas la propriété Dock mais DockPanel possède une AttachedProperty appelée Dock et comme on a dit que tous les éléments peuvent bénéficier d’une Attached Property, tous les éléments graphiques peuvent utiliser cette propriété, c’est ce qui fait que TextBox peut renseigner DockPanel.Dock à Left. La propriété Dock ne prend sens pour un TextBox que lorsque celui-ci est contenu dans un DockPanel et son utilisation hors de ce contexte est obselète. Une question qui nous vient à l’esprit tout de suite : mais si la propriété est appelée dans un mauvais contexte ? Si le contexte d’appel est inapproprié (une textbox qui n’est pas contenue dans un DockPanel et qui renseigne DockPanel.Dock) la propriété est ignorée.

Comme les Dependency Property, les Attached Property bénéficient de l’héritage hierarchique, elle peut donc être définie au sein d’un élément parent pour être répercutée sur tous ses éléments enfants comme elle peut être redéfinie au niveau d’un élément enfant.

Conclusion

Les concepts énoncés dans cet article sont fondamentaux pour toute personne voulant travailler avec des technologies comme Silvertlight ou WPF. Mais comment ça marche derrière ? Comment sont stockés les valeurs renseignées pour les Attached Property ? C’est ce que nous allons voir dans un prochain post.

c# | c#3 | Silverlight | Silverlight 3 | WPF
Wednesday, November 18, 2009 11:44:40 AM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

All content © 2012, Zied Nemili