Archives

Posts Tagged 'propriété de dépendance »

DependencyProperty: Manipulation de validation, la coercition et le changement (Partie III: Snippet dépendance Code de la propriété)

15 novembre 2008 Pas de commentaires

Présentation

C'est la dernière partie de la série en trois parties sur la façon de mettre en oeuvre propriété de dépendance avec la validation, la coercition et le concours complet sur WPF et Silverlight. Dans la partie I , j'ai mis en œuvre une propriété de dépendance simple et débogué grâce à démontrer comment la propriété de dépendance travaille sur WPF et le modèle commun pour la mise en œuvre propriété de dépendance sur WPF. En partie , j'ai fait la même sur Silverlight. Parce que Silverlight ne supporte que PropertyChangedCallback, mais pas CoerceValueCallback et ValidateValueCallback, donc la validation, la coercition et le changement de traitement doivent tous être mis en œuvre avec PropertyChangedCallback. Cela provoque PropertyChangedCallback être appelé récursivement lorsque la validation ou la coercition modifications de la valeur efficace de la propriété de dépendance. De plus d'autres limites du système de propriétés Silverlight, il peut faire très délicat à mettre en œuvre correctement les propriétés de dépendance. Ce post, je vais montrer quelques comportements obscures de contrôles Silverlight RangeBase et leurs causes pour démontrer davantage la manière dont cela peut être délicat. Et enfin, je vais fournir un extrait de code qui implémente le modèle complet de la mise en oeuvre propriété de dépendance dont j'ai parlé dans partie , comme une récompense pour ceux qui lisent à travers les séries :-)

Silverlight Contrôles RangeBase

Aperçu

ScrollBar, ProgressBar et hérite du Curseur RangeBase, qui implémente les propriétés de dépendance minimale, maximale et valeur, avec la contrainte coercition minimum <= valeur <= maximum.

RangeBase

Cela paraît tout simple :-) , Mais le comportement des contrôles RangeBase »peut devenir très étrange. Prenons quelques exemples:

  1. <Slider X:Name="sl"/> vous donne un curseur avec un minimum = 0, Valeur = 0, et Maximum = 10, tandis que <Slider x:Name="sl2" Minimum="-1" /> produit un curseur avec un minimum = -1, valeur = 0, et Maximum = 0.
  2. Cliquez sur le pouce faites glisser le curseur sl2 de faire sl2 au point, frappé droite ou de haut, flèche, puis changer sl2.Maximum à un nombre positif 10, sl2.Value changements magie de 0 à 0,1.
  3. Propriété Set sl2 c'est comme valeur à Double.NaN, une valeur non valide, une exception sera jeter, mais sl2.Value est changé Double.NaN toute façon, et aucun événement ValueChanged est déclenché.
Source Code

Voici le code que nous allons déboguer et expérimenter avec:

  • page.xaml:
  •   x:Class ="SLApp2.Page" <UserControl x: Class = "SLApp2.Page"
         xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns: my = "clr-namespace: SLApp2"
         xmlns: x = "http://schemas.microsoft.com/winfx/2006/xaml">
         x:Name ="LayoutRoot" Background ="White" > <StackPanel x: Name = "LayoutRoot" Background = "blanc">
             x:Name ="sl" /> <Slider X: Nom = "SL" />
             x:Name ="sl2" Minimum ="-1" /> <Mon: x Slider2: Nom = "sl2" Minimum = "-1" />
             x:Name ="btn" Content ="Break!" Touche <x: Name = "btn" content = "! Break"
                     HorizontalAlignment ="Center" /> Cliquez = "btn_Click" HorizontalAlignment = "center" />
         > </ StackPanel>
     > </ UserControl> 

  • Page.xaml.cs:
  •  Slider2 : Slider { protected override void OnMinimumChanged( double oldMinimum, double newMinimum) { base .OnMinimumChanged(oldMinimum, newMinimum); } protected override void OnMaximumChanged( double oldMaximum, double newMaximum) { base .OnMaximumChanged(oldMaximum, newMaximum); } protected override void OnValueChanged( double oldValue, double newValue) { base .OnValueChanged(oldValue, newValue); } protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e) { base .OnKeyDown(e); } } public partial class Page : UserControl { public Page() { InitializeComponent(); } // the button click event handler provides a chance to break into debugger // and experiment with RangeBase validation, coercion and change handling. private void btn_Click( object sender, RoutedEventArgs e) { } } } {/ / Sous-classe Slider pour rompre facilement au changement de propriété et touche enfoncée évènements public class Slider2:.. {Protected override Curseur nulle OnMinimumChanged (double oldMinimum, newMinimum double) {base OnMinimumChanged (oldMinimum, newMinimum);} protected void remplacer OnMaximumChanged (double oldMaximum, double newMaximum) {base OnMaximumChanged (oldMaximum, newMaximum);.} protected override void OnValueChanged (double oldValue, double newValue) {base OnValueChanged (oldValue, newValue);.} OnKeyDown protégée override void (System.Windows.Input.KeyEventArgs e) {. OnKeyDown de base (e);}} public page classe partielle: UserControl {public Page () {InitializeComponent ();} / / le gestionnaire d'événement bouton de clic donne une chance de percer dans le débogueur / / et d'expérimenter avec la validation RangeBase , la coercition et de manipulation de changement. private void btn_Click (object sender, RoutedEventArgs e) {}}} 

Debug & Expérience

Créez et exécutez-dessus une application Silverlight simple, vous verrez ci-dessous copie d'écran. S'il vous plaît noter la différence emplacement du pouce entre les deux curseurs:

SilverlightApplication1

Cliquez sur la pause! bouton pour percer dans son gestionnaire d'événements dans Visual Studio, vérifiez la valeur du curseur SL et SL2:

  • <Slider X:Name="sl"/>: sl a la valeur correcte de Mininum = Valeur = 0 et Maximum = 10, tandis que <Slider x:Name="sl2" Minimum="-1"/> sl2 a = minimum -1, mais maxi = Valeur = 0;

l'image

Pour ceux qui lisent la partie II et sont maintenant familiers avec le modèle de mise en œuvre, cela est la cause profonde du comportement étrange de SL2:

  • RangeBase feuilles _initialMax, _initialVal, _requestedMax, _requestedVal être initialisé par CLR pour valeur 0 par défaut. C'est OK pour les biens de valeur, car il est par défaut à zéro de toute façon; mais il est faux de la propriété Maximum, qui vaut 1 par défaut dans son appel DependencyProperty.Register et utilise sa valeur par défaut que sa valeur effective:

RangeBase pas initialiser ses champs

  • Réglage sl2.Minimum à -1 déclenche OnMinimumPropertyChanged -> CoerceMaximum, qui utilise le CLR initialisé _requestedMax de contraindre maximum. Depuis _requestedMax est de 0 au lieu du maximum de la valeur effective actuelle de 1 et 0 se trouve être supérieur à la valeur actuelle minimum de -1, alors maximale est de changer à 0:

RangeBase.CoerceMaximum

  • La raison <Slider x:Name="sl"> a une valeur maximale de 10 au lieu de 1 par défaut est que son modèle par défaut a une <Setter Property="Maximum" Value="10"/>. Cette ligne corrige également le problème que _requestedMax n'est pas initialisé. C'est pourquoi sl._requestedMax est de 10 en capture d'écran ci-dessus.

Modèle par défaut Curseur

Ensuite, définir un point de pause à chaque fonction, laisser couler l'application. Sélectionnez le pouce SL2 à le faire au point, a frappé la flèche droite ou vers le haut, il se brise en gestionnaire d'événement OnKeyDown. Vérifier la valeur de SL2: _requestedVal est 0; étape à travers base.OnKeyDown (e), vérifier à nouveau SL2: _requestedVal devient 0,1. C'est parce que la course base.OnKeyDown poignées droite / haut flèche et tente d'augmenter la valeur par SmallIncrement de 0,1, ce qui déclenche OnValuePropertyChanged -> CoerceValue, qui contraint ensuite la valeur à 0, puisque maximale est égale à 0, mais il se souvient de la demande de incrémenter par la mise en _requestedVal à 0,1.

l'image

Alors qu'il était encore à l'intérieur de la fenêtre immédiate, réglez sl2.Maximum à 10. Cela déclenche OnMaxiumPropertyChanged:

l'image

qui à son tour déclenche CoerceValue, qui vérifie _requestedVal, dont la valeur est maintenant de 0,1. Depuis 0.1 est de l'ordre de [-1, 10], alors la valeur est changée à 0,1:

l'image

Dernière, nous allons définir la valeur à Double.NaN. Cela déclenche une ArgumentException, comme prévu:

l'image

mais la valeur est modifiée pour Double.NaN toute façon, et aucun événement ValueChanged est tiré à cause de la ArgumentException:

l'image

Code Snippet propriété de dépendance

A présent, vous avez probablement eu l'idée qu'il est assez difficile à mettre en oeuvre propriété de dépendance avec la validation, la coercition et le changement de traitement correctement sur Silverlight. Pour rendre cela plus facile, ci-dessous est un extrait de code qui fournit la majorité du code pour le modèle de mise en œuvre mentionnées ci-dessus. Cet extrait de code a été écrit par Ted Glaza, et je l'ai modifié pour fournir le modèle complet. Vous pouvez supprimer des choses que vous n'avez pas besoin des propriétés de dépendance simple, comme ceux qui n'ont pas besoin de validation, la contrainte ou la manipulation du changement. Vous n'avez toujours besoin de remplir votre propre validation, la coercition, le changement et la logique de manipulation par des fonctions comme la modification de la propriété IsValid $ $, contraindre $ propriété $, sur la propriété des biens $ $ PropertyChanged, ou sur $ $ etc Changed, et ajouter une définition unique des _nestLevel private int. Mais au moins vous n'avez pas à vous soucier de la plupart des différences entre le PAM et les systèmes de propriétés Silverlight, et se concentrer uniquement sur la validation, la coercition et la logique de gestion des changements eux-mêmes.

 > < Shortcut > sdp </ Shortcut > < Description > Code snippet for a dependency property with validation, coercion and changed event. </ Description > < Author > Ning Propriété de dépendance </ title> <> Raccourci SDP </ Raccourci> <> Description extrait de code pour une propriété de dépendance avec la validation, la coercition et l'événement a changé. </ Description> <Auteur> Ning  > < Default > MyProperty </ Default > </ Literal > < Literal > < ID > type </ ID > < ToolTip > Property type </ ToolTip > < Default > object </ Default > </ Literal > < Literal > < ID > defaultValue </ ID > < ToolTip > Default Nom </ ToolTip> <> Default MyProperty </ Default> </ literal> <literal> <ID> Type </ ID> <ToolTip> Type de bien </ ToolTip> <> Default objet </ Default> </ literal> < literal> <ID> defaultValue </ ID> ToolTip <> par défaut  > /// Gets or sets the value of $property$ dependency property. propriété publique $ type $ $ $ / / / <summary> / / / Obtient ou définit la valeur de $ propriété de dépendance propriété $.  > public $type$ $property$ { get { return ($type$)GetValue($property$Property); } set { SetValue($property$Property, value); } } /// < summary > /// Identifies the $property$ dependency property. / / / </ Summary> public propriété de type $ $ $ $ {get {return ($ type $) GetValue ($ $ Immobilisations des biens);} set {SetValue ($ propriété $ propriété, valeur);}} / / / < summary> / / / Identifie la propriété de $ $ propriété de dépendance.  > public static readonly $SystemWindowsDependencyProperty$ $property$Property = $SystemWindowsDependencyProperty$.Register( "$property$", typeof($type$), typeof($classname$), new $SystemWindowsPropertyMetadata$($defaultValue$, On$property$PropertyChanged)); /// < summary > /// $property$Property property changed handler. / / / </ Summary> public static readonly $ $ $ SystemWindowsDependencyProperty propriété $ Propriété = $ $ SystemWindowsDependencyProperty. Registre ("$ $ de propriété", typeof ($ type $), typeof ($ classname $), de nouveaux SystemWindowsPropertyMetadata $ $ ($ defaultValue $, sur $ propriété $ PropertyChanged)); propriété Propriété / / / <summary> / / / $ propriété $ changé de gestionnaire.  > /// < param name ="d" > $classname$ that changed its $property$. </ param > /// < param name ="e" > Event arguments. </ param > private static void On$property$PropertyChanged($SystemWindowsDependencyObject$ d, $SystemWindowsDependencyPropertyChangedEventArgs$ e) { $classname$ source = ($classname$)d; $type$ newValue = ($type$)e.NewValue; $type$ oldValue = ($type$)e.OldValue; // validate newValue if (!IsValid$property$(newValue)) { // revert back to e.OldValue source._nestLevel++; source.SetValue(e.Property, e.OldValue); source._nestLevel--; // throw ArgumentException throw new ArgumentException("Invalid $property$Property value", "e"); } if (source._nestLevel == 0) { // remember initial state source._initial$property$ = oldValue; source._requested$property$ = newValue; } source._nestLevel++; // coerce newValue $type$ coercedValue = ($type$)Coerce$property$(d, e.NewValue); if (newValue != coercedValue) { // always set $property$Property to coerced value source.$property$ = coercedValue; } source._nestLevel--; if (source._nestLevel == 0 && source.$property$ != source._initial$property$) { // fire changed event only at root level and when there is indeed a change source.On$property$Changed(oldValue, source.$property$); } } /// < summary > /// $property$Property validation handler. / / / </ Summary> / / / <param name = "d"> $ classname $ qui a changé sa propriété $ $. </ Param> / / / <param name = "e"> arguments d'événement. </ Param> privés static void Sur source $ propriété $ PropertyChanged ($ SystemWindowsDependencyObject $ d, $ SystemWindowsDependencyPropertyChangedEventArgs $ e) {$ classname $ = ($ classname $) d; $ type = $ newValue ($ type $) e.NewValue; $ type $ oldValue = ($ type $) e.OldValue; / / valider newValue si {/ / revenir à e.OldValue source._nestLevel + + ($ IsValid propriété $ (newValue)!); source.SetValue (e.Property, e.OldValue) ; source._nestLevel--; / / throw ArgumentException ArgumentException jeter de nouvelles ("Invalid $ $ Valeur de la propriété des biens", "e");} if (source._nestLevel == 0) {/ / Rappelez-vous l'état initial de propriété source._initial $ $ = oldValue; source._requested $ la propriété $ = newValue;} source._nestLevel + +; / / contraindre newValue $ type = $ coercedValue ($ type $) contraindre la propriété $ (d, e.NewValue);! if (newValue = coercedValue ) {/ / toujours définir la propriété des biens $ $ à la source de la valeur des biens sous la contrainte $ $ = coercedValue;.} source._nestLevel--;.! if (source._nestLevel == 0 & & source = $ propriété $ source._initial $ propriété $ ) {/ / le feu a changé seul événement au niveau des racines et quand il est en effet un changement de propriété source.On $ propriété $ Changé (oldValue, source $);.}} / / / <summary> / / / $ $ Immobilisations des biens gestionnaire de validation.  > /// < param name ="value" > New value of $property$Property. </ param > /// < returns > /// Returns true if value is valid for $property$Property, false otherwise. / / / </ Summary> / / / <param name = "value"> La nouvelle valeur de la propriété des biens $ $. </ Param> / / / <returns> / / / Retourne true si la valeur est valide pour la propriété des biens $ $ , faux sinon.  > private static bool IsValid$property$($type$ value) { return true; } /// < summary > /// $property$Property coercion handler. / / / </ Returns> private static bool IsValid $ propriété $ ($ type $ value) {return true;} / / / <summary> / / / $ propriété $ gestionnaire de coercition propriété.  > /// < param name ="d" > $classname$ that changed its $property$. </ param > /// < param name ="value" > Event arguments. </ param > /// < returns > /// Coerced effective value of $property$Property from input parameter value. / / / </ Summary> / / / <param name = "d"> $ classname $ qui a changé sa propriété $ $. </ Param> <param name = "value"> / / / arguments de l'événement. </ Param> / / / <returns> / / / sous la contrainte de valeur effective de la propriété des biens $ $ de la valeur du paramètre d'entrée.  > private static object Coerce$property$($SystemWindowsDependencyObject$ d, object value) { $classname$ source = ($classname$)d; $type$ newValue = ($type$)value; return newValue; } /// < summary > /// $property$Property changed event. / / / </ Returns> private Coercition objet statique $ propriété $ ($ $ d SystemWindowsDependencyObject, valeur de l'objet) {$ classname $ source = ($ classname $) d; $ type = $ newValue ($ type $) valeur; newValue retour ;} / / / <summary> / / / $ $ propriété Propriété changé événement.  > public event RoutedPropertyChangedEventHandler < $type$ > $property$Changed; /// < summary > /// Called by On$property$PropertyChanged static method to fire $property$Changed event. / / / </ Summary> RoutedPropertyChangedEventHandler événement public <$ type $> propriété $ changé; / / / <summary> / / / Appelé par la méthode static $ propriété $ PropertyChanged au feu évènement $ propriété $ Changé.  > /// < param name ="oldValue" > The old value of $property$. </ param > /// < param name ="newValue" > The new value of $property$. </ param > protected virtual void On$property$Changed($type$ oldValue, $type$ newValue) { RoutedPropertyChangedEventArgs < $type$ > e = new RoutedPropertyChangedEventArgs < $type$ > (oldValue, newValue); if ($property$Changed != null) { $property$Changed(this, e); } } /// < summary > /// Cached previous value of $property$Property. / / / </ Summary> / / / <param name = "oldValue"> L'ancienne valeur de la propriété $ $. </ Param> / / / <param name = "newValue"> La nouvelle valeur de la propriété $ $. < / param> protected void virtuelle sur la propriété $ $ Changé ($ type $ oldValue, $ type $ newValue) {RoutedPropertyChangedEventArgs <$ type $> e = new RoutedPropertyChangedEventArgs <$ type $> (oldValue, newValue); if ($ propriété $ Changé ! = null) {$ propriété $ Changé (ce, e);}} / / / <summary> / / / cache la valeur précédente de la propriété des biens $ $.  > private $type$ _initial$property$ = $defaultValue$; /// < summary > /// Cached originally requested value of $property$Property by user. / / / </ Summary> private $ type $ $ _initial propriété $ = $ $ defaultValue; / / / <summary> / / / cache la valeur initialement demandé de la propriété des biens $ $ par utilisateur.  > private $type$ _requested$property$; #endregion public $type$ $property$ ]] > </ Code > </ Snippet > </ CodeSnippet > </ CodeSnippets > / / / </ Summary> private $ type $ $ _requested propriété $; # endregion public $ Type de propriété $ $ $]]> </ code> </ Snippet> </ codesnippet> </ CodeSnippets> 

Conclusion

J'espère que la série vous a aidé dans la compréhension et la mise en œuvre des propriétés de dépendance sur WPF et Silverlight. Les deux WPF et Silverlight sont des plates-formes grand, et sera fondamentale pour le développement de logiciels, peut-être plus que ce que Win32 fait avant.

Comme toujours, commentaires, suggestions, des corrections sont les bienvenues. Merci!