Il existe beaucoup de contrôles qui exposent des propriétés qui ne sont pas des DependencyProperty et qui rendent impossible de placer un binding dessus.
Dans d’autres cas, nous avons juste un getter d’exposé et il est impossible d’accéder à la propriété directement pour y placer notre liaison.
Pour ceux, téméraires qui souhaiterait essayer, vous obtiendrez le message suivant de Visual Studio :
A 'Binding' cannot be set on the 'SetCEDEJDED' property of type 'Tralala'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
Dans cet article, nous allons voir comment contourner ce problème et permettre la liaison sur ce type de propriété.
Il existe plusieurs cas ou cela est nécessaire, par exemple lorsque l’on veut générer dynamiquement les groupes du ruban office. Cela nous serait aussi utile pour lier les paramètres d’un Converter, bien que nous verrons qu’il y a d’autres astuces à appliquer dans ce cas.
L’idée sous-jacente de notre solution est de mettre en place une sorte de proxy qui va lier les deux propriétés à binder. À chaque modification de la propriété source, la propriété cible sera mise à jour.
Voici les différentes briques à mettre en place :
Utilisation dans notre code XAML
Cela fait office de cahier des charges et donne un exemple de l’utilisation que nous souhaitons de la classe de proxy :
<Window x:Class="BindOnNonDependencyProperty.MainWindow" x:Name="Mafenetre"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:us="clr-namespace:BindOnNonDependencyProperty"
Title="WPF-France : BindOnNonDependencyProperty" Height="350" Width="525">
<DockPanel >
<TextBox x:Name="myTextBox" DockPanel.Dock="Top" />
<TextBox x:Name="monTextBlockCible" DockPanel.Dock="Top" />
<us:ExtendedBinding Source="{Binding ElementName=myTextBox,Path=Text,Mode=TwoWay}"
Target="{Binding ElementName=monTextBlockCible,Path=Text,Mode=TwoWay}"
/>
DockPanel>
Window>
Classe de base de notre proxy
Notre proxy, que nous appelons ExtendedBinding, doit au minimum, hériter de DependencyObject afin de pouvoir posséder des DependencyProperty. Cependant, la seule façon de déclarer un DependencyObject dans le code est de l’ajouter comme une ressource. Ce faisant, il n’est plus dans l’arbre des contrôles et ne peut donc plus se binder sur un contrôle bien que l’inverse reste possible, ce n’est pas ce que l’on cherche.
La solution est donc de le faire dériver de UIElement me direz vous ? Et bien, ce n’est toujours pas suffisant car la version actuelle du Framework ne permet pas l’héritage du DataContext et l’utilisation du binding avec ElementName pour les UIElement. Heureusement, la solution existe, il faut faire dériver notre objet de FrameworkElement pour que tout rentre dans l’ordre.
Ajout des DependencyProperty
Il faut maintenant ajouter des DependencyProperty à notre classe de binding avancé. Nous en aurons deux : une cible et une source. Nous allons de plus personnaliser la création de celles-ci à l’aide de la classe FrameworkPropertyMetadata afin de permettre deux choses: que le binding se fasse en mode TwoWay par défaut et que le déclenchement des mises à jour se fasse à la levée de l’événement PropertyChanged.
Fonctionnement du proxy
Le cœur du proxy utilise les fonctionnalités des DependencyObject en surchargeant la méthode OnPropertyChanged. Cette surcharge va nous permettre de mettre à jour la source ou la cible lorsque l’une des deux change de valeur.
Il faut alors faire attention à ne pas se retrouver dans une “boucle” : la modification de la source faisant changer la cible, OnPropertyChanged est appelé une première fois pour le changement de la source et une deuxième fois lorsque nous mettons à jour la cible. Il faut détecter ce cas pour éviter que la mise à jour de la cible n’entraîne la mise à jour de la source et nous entraîne dans un processus sans fin: Cible –> Source –> Cible –> Source …
Code final de la classe ExtendedBinding
1: namespace BindOnNonDependencyProperty
2: {
3: public class ExtendedBinding : FrameworkElement
4: {
5: #region Source DP
6: //On ne connait pas à l'avance le type source: on laisse Object
7: public static readonly DependencyProperty SourceProperty =
8: DependencyProperty.Register("Source", typeof(object), typeof(ExtendedBinding),
9: new FrameworkPropertyMetadata()
10: {
11: BindsTwoWayByDefault = true,
12: DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
13: });
14: public Object Source
15: {
16: get { return GetValue(ExtendedBinding.SourceProperty); }
17: set { SetValue(ExtendedBinding.SourceProperty, value); }
18: }
19: #endregion
20:
21: #region Target DP
22: //On ne connait pas à l'avance le type cible: on laisse Object
23: public static readonly DependencyProperty TargetProperty =
24: DependencyProperty.Register("Target", typeof(object), typeof(ExtendedBinding),
25: new FrameworkPropertyMetadata()
26: {
27: BindsTwoWayByDefault = true,
28: DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
29: });
30: public Object Target
31: {
32: get { return GetValue(ExtendedBinding.TargetProperty); }
33: set { SetValue(ExtendedBinding.TargetProperty, value); }
34: }
35: #endregion
36:
37:
38: protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
39: {
40: base.OnPropertyChanged(e);
41: if (e.Property.Name == ExtendedBinding.SourceProperty.Name)
42: {
43: if (!object.ReferenceEquals(Source, Target))
44: Target = Source;
45:
46: }
47: else if (e.Property.Name == ExtendedBinding.TargetProperty.Name)
48: {
49: if (!object.ReferenceEquals(Source, Target))
50: Source = Target;
51: }
52: }
53:
54: }
55: }
56:
| < Précédent | Suivant > |
|---|





