Si vous ne savez pas du tout ce qu’est une Dependency Property, je vous invite à chercher sur MSDN. Rapidement, c’est un nouveau concept de propriété de classe dont :
-
la valeur est héritable par tous les objets enfants dans l’arbre des objets WPF.
-
on peut lier la valeur à la propriété d’un autre objet WPF.
Cet article va se focaliser sur l’aspect binding (liaison) à un objet Button,
en C#. Un petit bout de XAML se trouve tout à la fin pour l’utilisation du
Binding uniquement. Les propriétés attachées ne sont pas traitées.
Les conditions: Pour définir une liaison entre les propriétés de deux objets distincts (l’un sera la source, l’autre la cible), il faut que :
-
L’objet source dérive de la classe
DependencyObject(qui implémente les méthodesSetValueetGetValue). -
L’objet cible dérive de la classe
FrameworkContentElement(qui implémente la méthodeSetBinding).
A ma connaissance, tous les contrôles WPF héritent de FrameworkContentElement,
la seconde condition ne posera donc jamais de réel doute. Par contre, si vous
voulez lier une propriété d’une classe personnelle, il faut qu’elle dérive de
DependencyObject (assembly WindowsBase, espace de nom System.Windows).
Créer sa propre Dependency Property :
Voici le code, pour créer une Dependency Property:
1[...]
2using System.Windows; // Ne pas oublier la référence à l'assembly WindowsBase (WPF Framework).
3
4public class MaClasse : DependencyObject
5 {
6 /// <summary>
7 /// Lié à la propriété Text
8 /// </summary>
9 public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(MaClasse));
10
11 /// <summary>
12 /// Texte de ma classe
13 /// </summary>
14 public string Text
15 {
16 get
17 {
18 return (string)GetValue(TextProperty);
19 }
20 set
21 {
22 SetValue(TextProperty, value);
23 }
24 }
25 }
C’est tout ce qu’il faut pour créer une Dependency Property. Ce n’est donc pas
beaucoup plus chargé que créer une propriété standard liée à un champ simple.
Voici le code C# non WPF équivalent si, au lieu d’une propriété de dépendance,
nous en avions une standard:
1public class MaClasse
2 {
3 /// <summary>
4 /// Lié à la propriété Text
5 /// </summary>
6 private string _text;
7
8 /// <summary>
9 /// Texte de ma classe
10 /// </summary>
11 public string Text
12 {
13 get
14 {
15 return _text;
16 }
17 set
18 {
19 _text = value;
20 }
21 }
22 }
Revenons au premier code présenté: La ligne 2 importe l’espace de nom
WindowsBase qui est obligatoire pour utiliser les objets de base de WPF.
La ligne 4 fait dériver notre objet de l’objet DepenencyObject, obligatoire
pour contenir des propriétés de dépendance.
La ligne 9 fait deux choses :
-
Elle crée l’objet
DependencyProperty(TextProperty). Notez qu’il s’agit d’un objet statique. -
Elle inscrit cette propriété de dépendance grâce à la méthode
Register(). Le premier paramètre est le nom de la propriété (utilisable dans du code XAML). Le second le type de valeur (si on essaie d’affecter une valeur d’un type incompatible, une exception sera levée). Le troisième le type de la classe qui encapsule cette propriété.
Cela pourrait suffire mais obligerait alors d’utiliser les méthodes SetValue()
et GetValue() pour accéder à notre propriété. Pour l’encapsuler dans une
propriété standard et retrouver un typage fort (la valeur d’une propriété de
dépendance étant de type object), les lignes 14 à 24 créent la propriété Text
dont le setter et le getter font appel aux deux méthodes citées ci-dessus.
Ainsi, affecter la valeur de la propriété MaClass.Text affecte bien
TextProperty.
Lier le contenu d’un contrôle à une Dependency Property :
Le code C# suivant est le contenu du fichier Window1.xaml.cs. Il s’agit d’une
fenêtre WPF qui contient uniquement un Button nommé button1:
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Text;
5using System.Windows;
6using System.Windows.Controls;
7using System.Windows.Data;
8using System.Windows.Documents;
9using System.Windows.Input;
10using System.Windows.Media;
11using System.Windows.Media.Imaging;
12using System.Windows.Navigation;
13using System.Windows.Shapes;
14
15namespace WpfApplicationExample
16{
17 /// <summary>
18 /// Logique d'interaction pour Window1.xaml
19 /// </summary>
20 public partial class Window1 : Window
21 {
22 public Window1()
23 {
24 InitializeComponent();
25
26 MaClasse classeTest = new MaClasse();
27 Binding bind = new Binding();
28 bind.Source = classeTest;
29 bind.Path = new PropertyPath(MaClasse.TextProperty);
30 button1.SetBinding(Button.ContentProperty, bind);
31 classeTest.Text = "Nouveau texte";
32 }
33 }
34}
Vous voyez que l’important se trouve entre les lignes 26 à 31:
-
La ligne 26 instancie notre classe personnelle qui contient une Dependency Property.
-
La ligne 27 instancie une nouvelle liaison
Binding. -
La ligne 28 définit comme source de la liaison notre classe personnelle. C’est dans cette classe que la liaison récupérera la valeur liée.
-
La ligne 29 définit le chemin de la propriété de dépendance (il s’agit d’un champ statique).
-
Enfin, la ligne 30 met en place la liaison proprement dite: la propriété
button1.Content(contenu du bouton) est liée à la propriété de dépendanceMaClasse.TextProperty(qui est référencée par les propriétésbind.Sourceetbind.Path).
A chaque modification de la propriété classeTest.Text, le texte du bouton sera
automatiquement rafraichi! Notez que si vous ne définissez pas au moins une fois
la propriété de dépendance, sa valeur est null et le bouton n’affichera aucun
contenu.
Ce nouveau concept est donc plutôt sympa à utiliser puisqu’il évite d’avoir à se soucier du rafraichissement du bouton. Ce qu’on fait généralement au travers d’un événement (mais pas nécessairement). Il faut bien avoir conscience que le mécanisme des Dependency Properties gère en interne un événement, mais l’implémentation est a priori robuste et fiable.
Et en XAML ?
XAML n’est pas l’objet de ce petit article mais voici l’utilisation de Binding
entre deux contrôles WPF, un ScrollBar et un Button:
1<Grid>
2 <Grid.RowDefinitions>
3 <RowDefinition Height="20*"/>
4 <RowDefinition Height="80*"/>
5 </Grid.RowDefinitions>
6
7 <ScrollBar Grid.Row="1" Name="scroll" Value="50" Maximum="100" />
8 <Button Grid.Row="0" Height="23" Name="button1" VerticalAlignment="Top" Content="{Binding ElementName=scroll, Path=Value}"/>
9 </Grid>
Comme vous le voyez, c’est encore plus concis qu’en C# (bien qu’on ne fasse pas
exactement la même chose): le binding est créé par l’attribut de la ligne 8:
Content="{Binding ElementName=scroll, Path=Value}"
Lorsque vous déplacez le ScrollBar, sa valeur apparait dans le texte du
bouton.