DependencyProperty: Validation, coercition et gérer le changement (Partie I: WPF)
Introduction
la propriété de dépendance est une innovation majeure de WPF. Une propriété de dépendance a la même interface de programmation simple et familier comme une propriété CLR, mais permet à sa valeur de façon dynamique a décidé ou a été modifié par de nombreux facteurs, comme d'autres propriétés, des styles, des modèles, de liaison de données, l'animation, la composition des éléments, la classe d'héritage, etc , et peut être mis en œuvre pour offrir une valeur par défaut autonome, la validation, la coercition et de concours complet de logique. Silverlight implémente un sous-ensemble des fonctions de WPF système de la propriété, donc il ya des différences significatives entre WPF et Silverlight. Hétéro portage entre WPF et Silverlight ne peut pas être possible, des modifications majeures de code source peut être nécessaire. Cette série en trois parties après vais utiliser un exemple simple pour démontrer la structure commune de mise en œuvre de la propriété de dépendance sur WPF et Silverlight, la différence entre les deux systèmes de propriété et la façon de port de l'un à l'autre, et puis utilisez la commande NumericUpDown j'ai écrit pour Silverlight Toolkit pour démontrer comment complexe et délicate de propriété de dépendance peut obtenir sur Silverlight. Je vais seulement se concentrer sur la validation, la coercition et gérer le changement 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 de la propriété de dépendance WPF
MSDN a de bonnes propriétés de dépendance Vue d'ensemble , alors je vais juste appeler les trois classes de base et deux ensembles importants des méthodes de l'interface de programmation du système de la propriété 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))]
public sealed class DependencyProperty
(
/ / Champs
public static readonly objet UnsetValue;
/ / Méthodes
public DependencyProperty AddOwner (ownerType Type);
public DependencyProperty AddOwner (ownerType Type, typeMetadata PropertyMetadata);
public int substituer GetHashCode ();
public PropertyMetadata getMetadata (forType Type);
public PropertyMetadata getMetadata (DependencyObject DependencyObject);
public PropertyMetadata getMetadata (dependencyObjectType DependencyObjectType);
); public bool IsValidType (valeur de l'objet);
); public bool IsValidValue (valeur de l'objet);
public void OverrideMetadata (forType Type, typeMetadata PropertyMetadata);
public void OverrideMetadata (forType Type, typeMetadata PropertyMetadata, DependencyPropertyKey clés);
name, Type propertyType, Type ownerType); public static DependencyProperty registre (string name, propertyType Type, Type ownerType);
name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata); public static DependencyProperty registre (string name, propertyType Type, Type ownerType, typeMetadata PropertyMetadata);
name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback); public static DependencyProperty registre (string name, propertyType Type, Type ownerType, typeMetadata PropertyMetadata, validateValueCallback ValidateValueCallback);
name, Type propertyType, Type ownerType); public static DependencyProperty RegisterAttached (string name, propertyType Type, Type ownerType);
name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata); public static DependencyProperty RegisterAttached (string name, propertyType Type, Type ownerType, defaultMetadata PropertyMetadata);
name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback); public static DependencyProperty RegisterAttached (string name, propertyType Type, Type ownerType, defaultMetadata PropertyMetadata, validateValueCallback ValidateValueCallback);
name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata); public static DependencyPropertyKey RegisterAttachedReadOnly (string name, propertyType Type, Type ownerType, defaultMetadata PropertyMetadata);
name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback); public static DependencyPropertyKey RegisterAttachedReadOnly (string name, propertyType Type, Type ownerType, defaultMetadata PropertyMetadata, validateValueCallback ValidateValueCallback);
name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata); public static DependencyPropertyKey RegisterReadOnly (string name, propertyType Type, Type ownerType, typeMetadata PropertyMetadata);
name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback); public static DependencyPropertyKey RegisterReadOnly (string name, propertyType Type, Type ownerType, typeMetadata PropertyMetadata, validateValueCallback ValidateValueCallback);
public string override ToString ();
/ / Propriétés
public PropertyMetadata DefaultMetadata (get;)
public int GlobalIndex (get;)
Public string Name (get;)
public OwnerType Type (get;)
public PropertyType Type (get;)
public bool ReadOnly (get;)
public ValidateValueCallback ValidateValueCallback (get;)
) - DependencyObject, qui fournit des méthodes comme 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
DependencyObject public ();
public void ClearValue (dp DependencyProperty);
public void ClearValue (clé DependencyPropertyKey);
public void CoerceValue (dp DependencyProperty);
obj); public bool scellés substituer Equals (object obj);
public int scellées override GetHashCode ();
public LocalValueEnumerator GetLocalValueEnumerator ();
objet public GetValue (dp DependencyProperty);
public void InvalidateProperty (dp DependencyProperty);
protégées virtual void OnPropertyChanged (DependencyPropertyChangedEventArgs e);
objet public ReadLocalValue (dp DependencyProperty);
); public void SetCurrentValue (dp DependencyProperty, valeur de l'objet);
); public void SetValue (dp DependencyProperty, valeur de l'objet);
); public void SetValue (clé DependencyPropertyKey, valeur de l'objet);
/ / Propriétés
public DependencyObjectType DependencyObjectType (get;)
set; } uint internes EffectiveValuesCount ([FriendAccessAllowed] obtenir, ensemble privé;)
public bool IsSealed (get;)
) - PropertyMetadata, qui permet à la valeur par défaut, la contrainte et le changement logique de traitement à préciser lors de l'enregistrement de propriété de dépendance:
public class PropertyMetadata
(
/ / Méthodes
public PropertyMetadata ();
defaultValue); PropertyMetadata public (defaultValue objet);
PropertyMetadata public (propertyChangedCallback PropertyChangedCallback);
defaultValue, PropertyChangedCallback propertyChangedCallback); PropertyMetadata public (defaultValue objet, propertyChangedCallback PropertyChangedCallback);
defaultValue, PropertyChangedCallback propertyChangedCallback, CoerceValueCallback coerceValueCallback); PropertyMetadata public (defaultValue objet, propertyChangedCallback PropertyChangedCallback, coerceValueCallback CoerceValueCallback);
protégées virtual void fusion (baseMetadata PropertyMetadata, dp DependencyProperty);
protégées virtual void OnApply (dp DependencyProperty, TargetType Type);
/ / Propriétés
public CoerceValueCallback CoerceValueCallback (get; ensemble;)
objet public DefaultValue (get; ensemble;)
protégées bool IsSealed (get;)
public PropertyChangedCallback PropertyChangedCallback (get; ensemble;)
) - * DependencyProperty.Register méthodes et leurs surcharges
- constructeurs PropertyMetadata
Exemple
L'exemple ci-dessous a une simple classe MyButton, qui hérite de la classe Button et implémente une propriété de dépendance MyValue. La valeur des biens 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), les entrées d'utilisateur (jeu de paramètres sur demande), et la valeur de ses biens IsEnabled héritées de la classe de base. La dépendance dernier est mis en œuvre par CoerceMyValue logique de coercition et la dépendance gestionnaire de changement événement OnIsEnabledChanged (). Dans l'ensemble la mise en œuvre de MyValue démontre le schéma commun des biens dépendant de WPF:
- wrapper CLR: public int MyValue (get; ensemble;)
- identificateur de propriété de dépendance: public static readonly DependencyProeprty MyValueProperty
- Enregistrement auprès système de la propriété: DependencyProperty.Register ()
- logique de validation: IsValidMyValue méthode statique
- Contrainte logique: CoerceMyValue méthode statique
Il est courant et conseillé de mettre en œuvre aussi changé logique de traitement via:
- PropertyChangedCallback OnMyValueChanged méthode statique
- protégées virtual void OnMyValueChanged (oldValue, newValue) pour augmenter l'événement a changé, et pour la sous-classe pour remplacer
- public static readonly RoutedEvent MyValueChangedEvent déroute identifiant l'événement et l'enregistrement des événements
- événement public RoutedPropertyChangedEventHandler <int> MyValueChanged événement 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 = "Window1" height = "300" width = "300"> > <StackPanel> x:Name ="mybtn" Content ="MyButton" <Ma: MyButton x: Name = "mybtn" content = "MyButton" Click ="mybtn_Click" /> MyValueChanged = "mybtn_MyValueChanged" Click = "mybtn_Click" /> > </ StackPanel> > / Fenêtre> <
- Window1.xaml.cs:
using System; utilisant System.Windows; utilisant System.Windows.Controls; namespace WpfApp1 ( / Classe / échantillon pour montrer comment mettre en œuvre une DependencyProperty public class MyButton: Button ( / / DP: wrapper CLR public int MyValue ( )GetValue(MyValueProperty); } get (return (int) GetValue (MyValueProperty);) set (SetValue (MyValueProperty, value);) ) / / DP: identificateur de propriété de dépendance et d'enregistrement public static readonly DependencyProperty MyValueProperty = DependencyProperty.Register ( ( int ), typeof (MyButton), "MyValue", typeof (int), typeof (MyButton), PropertyMetadata nouvelles (0, PropertyChangedCallback nouvelles (OnMyValueChanged), CoerceValueCallback nouvelles (CoerceMyValue)), ValidateValueCallback nouvelles (IsValidMyValue)); / /: Rappel de validation DP ) privé static bool IsValidMyValue (valeur de l'objet) ( ) value ; newValue int = (int) valeur; > NewValue retour = 0 & & <newValue = 10; ) / /: Rappel contrainte DP ) privés objet statique CoerceMyValue (DependencyObject d, valeur de l'objet) ( ctrl MyButton = (MyButton) d; ) value ; newValue int = (int) valeur; retour ctrl.IsEnabled? Math.min (5, newValue): Math.max (6, newValue); ) / / DP: changé de rappel privé static void OnMyValueChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) ( ctrl MyButton = (MyButton) d; )e.OldValue; oldValue int = (int) e.OldValue; )e.NewValue; newValue int = (int) e.NewValue; ctrl.OnMyValueChanged (oldValue, newValue); ) / / RE: identifiant événement a changé et inscription public static readonly MyValueChangedEvent RoutedEvent = EventManager.RegisterRoutedEvent ("MyValueChanged", RoutingStrategy.Bubble, >), typeof (MyButton)); typeof (RoutedPropertyChangedEventHandler int <>), typeof (MyButton)); / / RE: événement a changé > MyValueChanged; événement public RoutedPropertyChangedEventHandler <int> MyValueChanged; / / RE: utiliser une virtuels protégés de soulever événement oldValue, int newValue) protégées virtual void OnMyValueChanged (oldValue int, int newValue) ( RoutedPropertyChangedEventArgs <> int e = >(oldValue, newValue); RoutedPropertyChangedEventArgs nouvelle <> int (oldValue, newValue); e.RoutedEvent = MyValueChangedEvent; RaiseEvent (e); ) / /: Gestionnaire d'événements DP pour déclencher un nouveau calcul de MyValue en raison du changement de dépendance sender, DependencyPropertyChangedEventArgs e) privé static void OnIsEnabledChanged (object sender, DependencyPropertyChangedEventArgs e) ( ctrl MyButton = (MyButton) l'expéditeur; ctrl.CoerceValue (MyButton.MyValueProperty); ) MyButton public () ( IsEnabledChanged + = OnIsEnabledChanged; ) ) / / / <summary> / / Logique d'interaction / pour Window1.xaml / / / / Summary> < public partial class Window1: Fenêtre ( Window1 public () ( InitializeComponent (); ) sender, RoutedEventArgs e) mybtn_Click private void (object sender, RoutedEventArgs e) ( ) sender, RoutedPropertyChangedEventArgs< int > e) private void mybtn_MyValueChanged (object sender, <> int RoutedPropertyChangedEventArgs e) ( oldValue int = e.OldValue; newValue int = e.NewValue; ) ) )
Comment fonctionne la propriété de dépendance sur WPF
Pour 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 les États changement.
Enregistrement
- Lorsque le programme démarre, DependencyProperty.Register est exécuté, ce qui appelle à son tour IsValidMyValue pour valider la valeur 0 par défaut:
La valeur effective
Cliquez sur l'instance MyButton pour déclencher le seuil de gestionnaire d'événements 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, il 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 enveloppes CLR étant juste un wrapper pour GetValue / SetValue et rien de plus.
- mybtn.ReadLocalValue (MyButton.MyValueProperty) des résultats montre qu'il n'y a 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.
Validation
Dans la fenêtre Exécution, mis mybtn.MyValue une valeur non valide -1 en tapant la commande "mybtn.MyValue = -1":
- Le wrapper CLR est appelé, qui appelle à son tour SetValue, qui déclenche la validation de la nouvelle valeur -1.
- retourne IsValidMyValue faux sur la nouvelle valeur -1, système de la propriété WPF lance ensuite ArgumentException:
- 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 l'état Silverlight ne change, même pour les opération de définition non valide, nous devons donc écrire du code pour restaurer l'état, comme je le montrerai dans la deuxième partie de la série.
Contrainte
Encore une fois dans la fenêtre immédiate, essayez de mettre 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, via wrapper CLR et SetValue:
- IsValidMyValue (8) renvoie vrai, alors la logique de coercition CoerceMyValue est appelé, par l'intermédiaire wrapper CLR, SetValue, UpdateEffectiveValue et ProcessCoerceValue:
- CoerceMyValue (8) retourne une valeur différente 5, système de la propriété afin WPF appelle IsValidMyvalue sur la valeur sous la contrainte effective 5:
- IsValidMyValue (5) renvoie true, le système de propriété afin WPF appelle changé la logique OnMyValueChanged, avec oldValue que 0 et newValue que 5. Ces deux valeurs sont des valeurs efficaces, car il n'y avait pas de valeur vieille instance mybtn, et la nouvelle valeur est en fait 8. OnMyValueChanged appel à tour de rôle sa version proctected virtuelle, ce qui soulève MyValueChanged événement routé, et exécute MyValueChanged gestionnaires d'événements, s'il ya lieu.
- Vérifiez la valeur de la propriété MyValue: deux wrapper CLR et GetValue renvoie la valeur nouvelle à compter du 5, mais retourne GetLocalValue 8, se souvenant de la demande de l'utilisateur original. Ceci est très important, comme nous allons le voir plus loin.
Re-calcul dynamique en tant que modifications de dépendance
Maintenant, nous allons mettre à false mybtn.IsEnabled dans la fenêtre Exécution. Cela modifie l'une des dépendances de MyValue, ce qui devrait déclencher un nouveau calcul de la valeur effective de la propriété MyValue, si je l'ai codé à droite (et je n'ai, bien sûr
:
- Tout d'abord, s'il vous plaît noter que le traitement changement IsEnabled est synchrone, c'est à dire, OnIsEnabledChanged est appelée avant "mybtn.IsEnabled = false" renvoie. 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 par la gestion des événements IsEnabledChanged (IsEnabledChanged + = OnIsEnabledChanged dans MyButton ()) et d'appeler CoerceValue de l'intérieur de gestionnaire d'événements OnIsEnabledChanged.
- CoerceValue dans les appels Trun UpdateEffectiveValue, ProcessCoerceValue, et puis notre logique de coercition CoerceMyValue. S'il vous plaît noter que CoerceMyValue est appelée avec newValue de 8, et non la valeur actuelle à compter du 5. Le jeu de paramètres d'origine demande 8 On se souvient par exemple mybtn localement, comme le montre la suite ReadLocalValue avant. Ceci est important, puisque Silverlight ne le fait pas, 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.
- Depuis mybtn.IsEnabled est fausse, la valeur demandée à l'origine 8 devient valide, alors retourne CoerceMyValue 8.
- 8 est différente de la valeur effective précédente de 5, alors UpdateEffectieValue appels NotifyPropertyChange, qui appelle en définitive, notre logique OnMyValueChanged changé avec oldValue du 5 et du 8 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 qui a provoqué le changement IsEnabled 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 c'est tout ce qui se passe de manière synchrone.
- Après la notification de modification de la contrainte, et termine la 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) le même résultat.
- une dernière expérience: 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 provoque à son tour le changement logique de traitement OnMyValueChanged, avec newValue de 0, la nouvelle valeur efficace de dérivés de MyValue propriété par défaut.
Conclusion
Comme on peut le voir d'en haut par exemple et l'expérience, WPF a un puissant système de la propriété, ce qui rend à la fois la mise en œuvre et l'utilisation de propriétés de dépendance très facile. Il est beaucoup plus délicat sur Silverlight bien, parce que Silverlight implémente uniquement un sous-ensemble des fonctionnalités de WPF. Dans la deuxième partie de la série post, je vais re-mettre en œuvre ci-dessus par exemple sur Silverlight, et de démontrer la différence entre WPF et Silverlight. Dans la troisième partie, je vais utiliser RangeBase et NumericUpDown pour démontrer la chasse aux sorcières sur Silverlight, qui est la motivation première de cette série post. Restez à l'écoute!
PS: le premier poste de la série finit beaucoup plus long et plus de temps que j'attendais, donc je vais m'arrêter ici. S'il ya assez 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 des plomberie pour la mise en œuvre de propriété de dépendance sur WPF. Merci!








Les commentaires récents