Accueil > Silverlight , WPF > DependencyProperty: Manipulation de validation, la coercition et le changement (Partie I: WPF)

DependencyProperty: Manipulation de validation, la coercition et le changement (Partie I: WPF)

Introduction

De propriété de dépendance est une innovation majeure de WPF. Une propriété de dépendance a l'interface de programmation simple et familier même comme une propriété CLR, mais permet sa valeur de façon dynamique a décidé / changé par de nombreux facteurs, comme d'autres propriétés, des styles, des modèles, de liaison de données, d'animation, de la composition élément, etc héritage de classe , et peut être mis en œuvre pour fournir une valeur par défaut autonome, la validation, la coercition et la logique de concours complet. Silverlight implémente un sous-ensemble de WPF fonctions du système de propriété, de sorte qu'il existe des différences significatives entre WPF et Silverlight. Portage droite entre WPF et Silverlight ne peut pas être possible, les principales modifications du code source peut être nécessaire. Cette série en trois parties après vais utiliser un exemple simple pour démontrer le schéma habituel de la mise en œuvre propriété de dépendance sur WPF et Silverlight, la différence entre les deux systèmes de propriété et la façon dont le port de l'un à l'autre, et puis utilisez la commande NumericUpDown que j'ai écrit pour Silverlight Toolkit pour montrer comment la propriété de dépendance complexe et délicat pouvez obtenir sur Silverlight. Je vais seulement se concentrer sur la validation, la coercition et la manipulation des changements dans cette série. Quand je trouve le temps, je peux écrire sur d'autres aspects de propriétés de dépendance.

Vue d'ensemble WPF propriété de dépendance

MSDN a une bonne vue d'ensemble de la propriété de dépendance , donc je vais juste appeler les trois classes de base et deux ensembles importants de méthodes de l'interface de programmation du système de propriétés WPF:

  • DependencyProperty, qui fournit des propriétés comme le nom, ownerType, PropertyType et des méthodes comme registre, RegisterAttached, RegiterReadOnly, RegisterAttachedReadOnly:
     (DependencyPropertyValueSerializer))] [TypeConverter ("System.Windows.Markup.DependencyPropertyConverter, PresentationFramework, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = 31bf3856ad364e35, Custom = null"), ValueSerializer (typeof (DependencyPropertyValueSerializer))] 
    publique DependencyProperty classe scellée
    {
    / / Champs
    publique UnsetValue statique objet en lecture seule;

    / / Méthodes
    publique DependencyProperty AddOwner (ownerType Type);
    publique DependencyProperty AddOwner (ownerType Type, PropertyMetadata typeMetadata);
    public override int GetHashCode ();
    publique PropertyMetadata GetMetadata (Type forType);
    publique PropertyMetadata GetMetadata (DependencyObject DependencyObject);
    publique PropertyMetadata GetMetadata (DependencyObjectType DependencyObjectType);
    ); publique IsValidType bool (valeur de l'objet);
    ); public bool IsValidValue (valeur de l'objet);
    public void OverrideMetadata (Type forType, PropertyMetadata typeMetadata);
    public void OverrideMetadata (Type forType, PropertyMetadata typeMetadata, DependencyPropertyKey clé);
    name, Type propertyType, Type ownerType); public static DependencyProperty Register (string name, propertyType Type, Type de ownerType);
    name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata); public static DependencyProperty Register (string name, propertyType Type, Type de ownerType, PropertyMetadata typeMetadata);
    name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback); public static DependencyProperty Register (string name, propertyType Type, Type de ownerType, PropertyMetadata typeMetadata, ValidateValueCallback ValidateValueCallback);
    name, Type propertyType, Type ownerType); publique DependencyProperty statique RegisterAttached (string name, Type propertyType, ownerType Type);
    name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata); publique DependencyProperty statique RegisterAttached (string name, Type propertyType, ownerType Type, PropertyMetadata defaultMetadata);
    name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback); publique DependencyProperty statique RegisterAttached (string name, Type propertyType, ownerType Type, PropertyMetadata defaultMetadata, ValidateValueCallback ValidateValueCallback);
    name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata); public static DependencyPropertyKey RegisterAttachedReadOnly (string name, propertyType Type, Type de ownerType, PropertyMetadata defaultMetadata);
    name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback); public static DependencyPropertyKey RegisterAttachedReadOnly (string name, Type propertyType, ownerType Type, PropertyMetadata defaultMetadata, ValidateValueCallback ValidateValueCallback);
    name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata); public static DependencyPropertyKey RegisterReadOnly (string name, propertyType Type, Type de ownerType, PropertyMetadata typeMetadata);
    name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback); public static DependencyPropertyKey RegisterReadOnly (string name, Type propertyType, ownerType Type, PropertyMetadata typeMetadata, ValidateValueCallback ValidateValueCallback);
    publique override string ToString ();

    / / Propriétés
    publique PropertyMetadata DefaultMetadata {get;}
    public int GlobalIndex {get;}
    public string Name {get;}
    Type de ownerType publique {get;}
    Type de PropertyType publique {get;}
    public bool ReadOnly {get;}
    ValidateValueCallback ValidateValueCallback publique {get;}
    }

  • DependencyObject, qui fournit des méthodes telles que GetValue, SetValue, ReadLocalValue, CoerceValue, ClearValue pour accéder / modifier la valeur d'une propriété de dépendance:
     , typeof (NameScope))] [TypeDescriptionProvider (typeof (DependencyObjectProvider)), NameScopeProperty ("NameScope", typeof (NameScope))] 
    public class DependencyObject: DispatcherObject
    {
    / / Méthodes
    publique DependencyObject ();
    public void ClearValue (DependencyProperty dp);
    public void ClearValue (DependencyPropertyKey clé);
    public void CoerceValue (DependencyProperty dp);
    obj); public sealed override bool Equals (object obj);
    public sealed override int GetHashCode ();
    GetLocalValueEnumerator LocalValueEnumerator publique ();
    GetValue objet public (DependencyProperty dp);
    public void InvalidateProperty (DependencyProperty dp);
    protected void virtuelle OnPropertyChanged (DependencyPropertyChangedEventArgs e);
    publique objet ReadLocalValue (DependencyProperty dp);
    ); public void SetCurrentValue (DependencyProperty dp, valeur de l'objet);
    ); SetValue public void (DependencyProperty dp, valeur de l'objet);
    ); SetValue public void (DependencyPropertyKey clé, valeur de l'objet);

    / / Propriétés
    DependencyObjectType DependencyObjectType publique {get;}
    set; } interne uint EffectiveValuesCount {[FriendAccessAllowed] get; set privé;}
    public bool IsSealed {get;}
    }

  • PropertyMetadata, qui permet à la valeur par défaut, la coercition et la logique de gestion du changement à préciser lors de votre inscription propriété de dépendance:
      public class PropertyMetadata 
    {
    / / Méthodes
    publique PropertyMetadata ();
    defaultValue); publique PropertyMetadata (objet defaultValue);
    publique PropertyMetadata (PropertyChangedCallback PropertyChangedCallback);
    defaultValue, PropertyChangedCallback propertyChangedCallback); publique PropertyMetadata (objet defaultValue, PropertyChangedCallback PropertyChangedCallback);
    defaultValue, PropertyChangedCallback propertyChangedCallback, CoerceValueCallback coerceValueCallback); publique PropertyMetadata (objet defaultValue, PropertyChangedCallback PropertyChangedCallback, CoerceValueCallback CoerceValueCallback);
    protected void virtuelle de fusion (PropertyMetadata baseMetadata, DependencyProperty dp);
    protected void virtuelle OnApply (DependencyProperty dp, Type targetType);

    / / Propriétés
    CoerceValueCallback CoerceValueCallback publique {get; mis;}
    DefaultValue objet public {get; mis;}
    protégé bool IsSealed {get;}
    publique PropertyChangedCallback PropertyChangedCallback {get; mis;}
    }

  • * Les méthodes et de leurs surcharges DependencyProperty.Register
  • Constructeurs PropertyMetadata

Exemple

L'exemple ci-dessous présente une simple classe MyButton, qui hérite de la classe Button et met en œuvre une MyValue propriété de dépendance. La valeur de la propriété MyValue doit être comprise entre 0 à 10, forcée par sa logique de validation IsValidMyValue (). La valeur effective de la propriété MyValue dépend de sa valeur par défaut (0), l'entrée d'utilisateur (paramètre de demande de consigne), et la valeur de sa propriété IsEnabled hérité de la classe de base. La dépendance est la dernière mise en œuvre par CoerceMyValue logique de coercition et de gestionnaire d'événements de dépendance changement OnIsEnabledChanged (). Dans l'ensemble la mise en œuvre de la MyValue illustre le modèle commun de la propriété dépend de WPF:

  • Wrapper CLR: public int MyValue {get; mis;}
  • Identificateur de propriété de dépendance: public static readonly DependencyProeprty MyValueProperty
  • Enregistrement auprès système de la propriété: DependencyProperty.Register ()
  • La logique de validation: IsValidMyValue méthode statique
  • La logique de coercition: CoerceMyValue méthode statique

Il est commun et conseillé de mettre en œuvre aussi logique de gestion modifié via:

  • PropertyChangedCallback OnMyValueChanged méthode statique
  • protected void virtuelle OnMyValueChanged (oldValue, newValue) pour déclencher l'événement changé, et pour la sous-classe pour remplacer
  • public static readonly RoutedEvent MyValueChangedEvent identificateur d'événement routé et enregistrement des événements
  • événement public <int> RoutedPropertyChangedEventHandler événement MyValueChanged pour le client

Ci-dessous le code source de l'exemple:

  • Window1.xaml:
      x:Class ="WpfApp1.Window1" <Fenêtre x: Class = "WpfApp1.Window1"
         xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns: x = "http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns: my = "clr-namespace: WpfApp1"
         Height ="300" Width ="300" > Titre = "Fenêtre1" height = "300" width = "300">
         > <StackPanel>
             x:Name ="mybtn" Content ="MyButton" <Mon: x MyButton: Nom = "myBtn" content = "MyButton" 
                          Click ="mybtn_Click" /> MyValueChanged = "mybtn_MyValueChanged" Click = "mybtn_Click" />
         > </ StackPanel>
     > </ Window> 
  • Window1.xaml.cs:
     System.Windows; using System.Windows.Controls; namespace WpfApp1 { // sample class to demonstrate how to implement a DependencyProperty public class MyButton : Button { // DP: CLR wrapper public int MyValue { get { return ( int )GetValue(MyValueProperty); } set { SetValue(MyValueProperty, value ); } } // DP: dependency property identifier & registration public static readonly DependencyProperty MyValueProperty = using System; utilisant System.Windows; utilisant System.Windows.Controls; espace de noms WpfApp1 {/ / l'échantillon classe pour montrer comment mettre en œuvre un DependencyProperty public class MyButton: Button {/ / DP: CLR emballage public int MyValue {get {return (int ) GetValue (MyValueProperty);} set {SetValue (MyValueProperty, valeur);}} / / DP: identificateur de la propriété de dépendance et un enregistrement public static readonly DependencyProperty MyValueProperty =  IsValidMyValue( object value ) { int newValue = ( int ) value ; return newValue >= 0 && newValue <= 10; } // DP: coercion callback private static object CoerceMyValue(DependencyObject d, object value ) { MyButton ctrl = (MyButton)d; int newValue = ( int ) value ; return ctrl.IsEnabled ? DP: la validation de rappel private static bool IsValidMyValue (valeur de l'objet) {int newValue = (int) valeur; retour newValue> = 0 && newValue <= 10;} / / DP: la contrainte de rappel privée objet statique CoerceMyValue (DependencyObject d, valeur de l'objet) {MyButton ctrl = (MyButton) d; int newValue = (int) valeur; retour ctrl.IsEnabled?  OnMyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { MyButton ctrl = (MyButton)d; int oldValue = ( int )e.OldValue; int newValue = ( int )e.NewValue; ctrl.OnMyValueChanged(oldValue, newValue); } // RE: changed event identifier & registration public static readonly RoutedEvent MyValueChangedEvent = EventManager.RegisterRoutedEvent( "MyValueChanged" , RoutingStrategy.Bubble, typeof (RoutedPropertyChangedEventHandler< int >), typeof (MyButton)); // RE: changed event public event RoutedPropertyChangedEventHandler< int > MyValueChanged; // RE: use a protected virtual to raise event protected virtual void OnMyValueChanged( int oldValue, int newValue) { RoutedPropertyChangedEventArgs< int > e = new RoutedPropertyChangedEventArgs< int >(oldValue, newValue); e.RoutedEvent = MyValueChangedEvent; RaiseEvent(e); } // DP: event handler to trigger re-calculation of MyValue because of dependency change private static void OnIsEnabledChanged( object sender, DependencyPropertyChangedEventArgs e) { MyButton ctrl = (MyButton)sender; ctrl.CoerceValue(MyButton.MyValueProperty); } public MyButton() { IsEnabledChanged += OnIsEnabledChanged; } } /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { public Window1() { InitializeComponent(); } private void mybtn_Click( object sender, RoutedEventArgs e) { } private void mybtn_MyValueChanged( object sender, RoutedPropertyChangedEventArgs< int > e) { int oldValue = e.OldValue; int newValue = e.NewValue; } } } Math.Min (5, newValue): Math.Max ​​(6, newValue);} / / DP: changé de rappel private void statique OnMyValueChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) {MyButton ctrl = (MyButton) d; oldValue int = (int ) e.OldValue; int newValue = (int) e.NewValue; ctrl.OnMyValueChanged (oldValue, newValue);} / / RE: identificateur d'événement changé et enregistrement public static readonly RoutedEvent MyValueChangedEvent = EventManager.RegisterRoutedEvent ("MyValueChanged", RoutingStrategy. Bubble, typeof (RoutedPropertyChangedEventHandler <> int), typeof (MyButton)); / / RE: changé RoutedPropertyChangedEventHandler événement événement public <> int MyValueChanged; / / RE: utiliser une machine virtuelle protégée pour élever événement protected void virtuelle OnMyValueChanged (int oldValue, int newValue) {RoutedPropertyChangedEventArgs <> int e = new RoutedPropertyChangedEventArgs <> int (oldValue, newValue); e.RoutedEvent = MyValueChangedEvent; RaiseEvent (e);} / / DP: gestionnaire d'événements pour déclencher un nouveau calcul de MyValue en raison du changement de dépendance privée static void OnIsEnabledChanged (object sender, DependencyPropertyChangedEventArgs e) {MyButton ctrl = (MyButton) l'expéditeur; ctrl.CoerceValue (MyButton.MyValueProperty);} public MyButton () {IsEnabledChanged + = OnIsEnabledChanged;}} / / / <summary> / / / logique d'interaction pour Window1.xaml / / / </ summary> public partial class Window1: Fenêtre {public Window1 () {InitializeComponent ();} private void mybtn_Click (object sender, RoutedEventArgs e) {} public void mybtn_MyValueChanged (object sender, RoutedPropertyChangedEventArgs <> int e) {int oldValue = e.OldValue; int newValue = e.NewValue;}}} 

Comment le travail de propriété de dépendance sur WPF

Pour démontrer comment la propriété de dépendance travaille sur WPF, j'ai mis un point d'arrêt sur la plupart des fonctions, et utilisé la fenêtre de débogage immédiat de vérifier les valeurs et changer d'état.

Inscription

  • Lorsque le programme démarre, DependencyProperty.Register est exécuté, ce qui appelle à son tour IsValidMyValue pour valider la valeur par défaut 0:

    validation called for default value

Valeur efficace

Cliquez sur l'instance MyButton pour déclencher le point de rupture gestionnaire d'événement mybtn_Click, puis utilisez la fenêtre Exécution de vérifier sur la propriété MyValue. Comme indiqué ci-dessous:

  • Même si mybtn.MyValue n'est pas attribuer une valeur, elle a une valeur effective de 0, la valeur par défaut de cette propriété de dépendance, comme le montrent les résultats de mybtn.MyValue et mybtn.GetValue.
  • Il est important que le wrapper CLR et GetValue / SetValue appels devraient toujours avoir le même effet. Ceci est fait par les wrappers CLR étant juste un wrapper pour GetValue / SetValue et rien de plus.
  • mybtn.ReadLocalValue (MyButton.MyValueProperty) des résultats montre qu'il n'ya pas de stockage local de la propriété sur l'instance MyValue myBtn.
  • Lorsque mybtn.MyValue prend la valeur initiale, il n'ya pas de notification de changement, comme OnMyValueChanged n'est pas appelée au démarrage du programme.

    DP has default value, no local storage

Validation

Dans la fenêtre immédiate, mis mybtn.MyValue à une valeur non valide -1 en tapant la commande "mybtn.MyValue = -1":

  • L'enveloppe CLR est appelé, qui appelle à son tour SetValue, ce qui déclenche la validation sur la nouvelle valeur -1.

    validation called for setting value
  • IsValidMyValue retourne false sur la nouvelle valeur -1, le système de propriétés WPF lance alors ArgumentException:

    validation failure triggers argument exception in setting
  • Vérifiez la valeur de MyValue l'intermédiaire de trois façons: wrapper CLR, GetValue, ReadLocalValue: rien n'a changé. Ceci est important, puisque sur Silverlight état change, même pour les invalides opération d'ensemble, nous devons donc écrire du code pour restaurer l'état, comme je le montrerai dans la deuxième partie de la série.

    No state change after invalid set operation

Coercition

Encore une fois dans la fenêtre immédiate, essayez mis mybtn.MyValue à 8, qui est valable, mais parce que mybtn.IsEnabled est vrai, la valeur effective doit être inférieur ou égal à 5:

  • Tout d'abord, la validation est appelée sur la nouvelle valeur 8, par l'intermédiaire d'emballage CLR et SetValue:

    validation called during setting
  • IsValidMyValue (8) retourne true, si la logique de coercition CoerceMyValue est appelé, par l'intermédiaire d'emballage CLR, SetValue, UpdateEffectiveValue, et ProcessCoerceValue:

    Coercion called during setting
  • CoerceMyValue (8) retourne une valeur différente 5, donc système de propriétés WPF appelle IsValidMyvalue sur la valeur forcée à compter du 5:

    Coerced value goes through validation as well
  • IsValidMyValue (5) retourne true, si le système de propriétés WPF appelle la logique changé OnMyValueChanged, avec oldValue que 0 et newValue que 5. Les deux valeurs sont des valeurs efficaces, car il n'y avait pas de valeur ancienne par exemple myBtn, et la nouvelle valeur est en fait 8. OnMyValueChanged à tour de rôle appeler sa version virtuelle proctected, ce qui soulève MyValueChanged événement routé, et exécute les gestionnaires d'événements MyValueChanged, s'il ya lieu.
    Changed callback to raise changed event
  • Vérifiez la valeur de la propriété MyValue: à la fois wrapper CLR et GetValue renvoie la nouvelle valeur à compter du 5, mais GetLocalValue retourne 8, se souvenant de la demande de l'utilisateur d'origine. Ceci est très important, comme nous allons le voir par la suite.

    Coercion causes local value and effective value difference

Re-calcul dynamique sous forme de modifications de dépendance

Maintenant nous allons ensemble du mybtn.IsEnabled à false dans la fenêtre immédiate. Cela modifie l'une des dépendances pour MyValue, ce qui devrait déclencher un nouveau calcul de la valeur efficace de la propriété MyValue, si je l'ai codé droit (et je l'ai fait, bien sûr :-) :

  • Tout d'abord, s'il vous plaît noter que IsEnabled changement de traitement est synchrone, c'est à dire, OnIsEnabledChanged est appelée avant "mybtn.IsEnabled = false" retours. Ceci est important car la manipulation changement IsEnabled est asynchrone sur Silverlight.
  • Nous avons pour déclencher le recalcul depuis le système de la propriété ne sais pas MyValue dépend IsEnabled. Cela se fait en gestion de l'événement IsEnabledChanged (IsEnabledChanged + = OnIsEnabledChanged dans MyButton ()) et d'appeler de l'intérieur CoerceValue gestionnaire d'événements OnIsEnabledChanged.
  • CoerceValue à Trun appelle UpdateEffectiveValue, ProcessCoerceValue, puis notre logique de coercition CoerceMyValue. S'il vous plaît noter que CoerceMyValue est appelée avec newValue de 8, et non pas la valeur effective actuelle 5. Le paramètre jeu original demande 8 On se souvient par exemple myBtn localement, comme le montre par la suite ReadLocalValue avant. Ceci est important, puisque Silverlight ne prend pas cela, ce qui rend la mise en œuvre de coercition très délicate, comme on le verra dans la partie 3 de la série blog avec des bugs dans Silverlight RangeBase et ses sous classes comme Slider et ScrollBar.

    Dependency change triggers coercion
  • Depuis mybtn.IsEnabled est maintenant faux, la valeur demandée à l'origine 8 devient valide, c'est-retours CoerceMyValue 8.
  • 8 est différent de la valeur efficace précédente de 5, de sorte UpdateEffectieValue appelle NotifyPropertyChange, qui appelle finalement les logiques changé OnMyValueChanged avec oldValue de 5 et 8 de newValue. Cela se produit même si aucune opération n'est effectuée nouvelle série de la propriété MyValue. C'est le changement de dépendance IsEnabled qui a causé le changement MyValue valeur effective, qui tourne dans les déclencheurs notification de changement et de la gestion des événements de la propriété MyValue, et tout cela est passe de manière synchrone.

    Coercion triggers change notification
  • Après la coercition, notification de changement, et les finitions de gestion des événements, vérifiez la valeur de la propriété MyValue: désormais tous les trois méthodes (mybtn.MyValue, mybtn.GetValue, mybtn.ReadLocalValue) retourner le même résultat.

    Effective value and local value the same
  • une expérience dernier: mybtn.ClearValue type (MyButton.MyValueProperty) dans la fenêtre immédiate pour effacer la valeur locale (8) de la propriété de dépendance, ce qui déclenche à son tour la logique de manipulation changement OnMyValueChanged, avec newValue de 0, la nouvelle valeur efficace du dérivé de MyValue propriété par défaut.

    ClearValue triggers change handling

Conclusion

Comme on peut le voir d'en haut par exemple et l'expérience, WPF a un système de propriété puissante, ce qui rend à la fois la mise en œuvre et l'utilisation des propriétés de dépendance très facile. Il est beaucoup plus délicat sur Silverlight mais, parce que Silverlight implémente uniquement un sous-ensemble de fonctionnalités WPF. Dans la deuxième partie de la série après, je vais re-mettre en œuvre exemple ci-dessus sur Silverlight, et de démontrer la différence entre WPF et Silverlight. Dans la troisième partie, je vais utiliser RangeBase et NumericUpDown de démontrer la chasse aux sorcières sur Silverlight, qui est la motivation première de cette série après. Restez à l'écoute!

PS: le premier message de la série a fini par beaucoup plus de temps et plus de temps que je m'y attendais, je vais donc m'arrêter ici. S'il ya suffisamment d'intérêt, je peux télécharger le projet pour vous d'expérimenter, et un extrait de code qui fournit la plupart de la plomberie pour la mise en œuvre propriété de dépendance sur WPF. Merci!

Technorati Tags: ,
  1. Andrew
    15 février 2010 à 13:10 | # 1

    ces lignes sont impossibles, et ne fonctionnent pas:

    int oldValue = (int) e.OldValue;
    int newValue = (int) e.NewValue;
    ctrl.OnMyValueChanged (oldValue, newValue);

  1. Aucun trackback pour l'instant.