WPF France

  • Augmenter la taille
  • Taille par défaut
  • Diminuer la taille

Comment se binder sur autre chose qu’une DependencyProperty d’un DependencyObject

Envoyer Imprimer PDF
Note des utilisateurs: / 0
MauvaisTrès bien 

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:  
Commentaires (0)
Mise à jour le Mardi, 30 Mars 2010 08:52  

Partagez


Article au hasard

  • Il est relativement courant dans une application de vérifier l'existence d'un fichier avant de l'utiliser. Dans une application riche WPF ou Silverlight, l'utilisation des média rend cela encore plus fréquent.

    Afin d'effectuer ce contrôle, le framework .Net propose l'utilisation des classes File et/ou FileInfo. Mais l'obtention de ce résultat peut parfois être très long et particulièrement trop long pour moi :). Dans cet article nous allons détailler une méthode qui peut être utilisé...
    Lire la suite...