DependencyProperty: Validation, Nötigung & Change Handling (Teil III: Dependency Property Code Snippet)
Einführung
Dies ist der letzte Teil der dreiteiligen Serie, wie Sie Abhängigkeitseigenschaft mit Validierung, Nötigung und Vielseitigkeit auf WPF und Silverlight umzusetzen. In Teil I , implementierte ich eine einfache Abhängigkeitseigenschaft und debugged bis hin zu demonstrieren, wie Abhängigkeitseigenschaft WPF und das gemeinsame Muster für die Umsetzung Abhängigkeitseigenschaft WPF funktioniert. In Part , ich habe das gleiche auf Silverlight. Da Silverlight unterstützt nur PropertyChangedCallback, aber nicht CoerceValueCallback und ValidateValueCallback, so Validierung, Nötigung und Änderung Handhabung haben alle mit PropertyChangedCallback umgesetzt werden. Dies führt dazu, PropertyChangedCallback wird rekursiv, wenn die Validierung oder Zwang der effektive Wert der Abhängigkeitseigenschaft Änderungen genannt. Zuzüglich der sonstigen Beschränkungen der Silverlight-Eigenschaftensystem, kann es sehr schwierig zu Abhängigkeitseigenschaft korrekt umzusetzen. Dieser Beitrag werde ich zeigen einige obskure Verhalten von Silverlight RangeBase Kontrollen und deren Ursachen weiter zeigen, wie schwierig das sein kann. Und endlich, werde ich einen Code-Schnipsel, die das vollständige Muster der Abhängigkeitseigenschaft Umsetzung ich in diskutiert implementiert Teil als Belohnung für diejenigen, die durch die Serie lesen ![]()
Silverlight RangeBase Kontrollen
Überblick
ScrollBar, ProgressBar und Slider erbt von RangeBase, die Minimum, Maximum und Value Abhängigkeitseigenschaften implementiert, mit der Einschränkung, dass Zwang Minimum <= Wert <= max.
Das klingt einfach
, Aber RangeBase steuert das Verhalten kann sehr seltsam. Nehmen Sie ein paar Beispiele:
- <Slider X:Name="sl"/> gibt Ihnen ein Schieberegler mit Minimum = 0, Value = 0, und Maximum = 10, während <Slider x:Name="sl2" Minimum="-1" /> erzeugt einen Schieberegler mit Minimum = -1, Value = 0, und Maximum = 0 ist.
- Klicken Sie auf Schieberegler sl2 die Drag Daumen sl2 im Fokus zu machen, drücken Sie rechts oder Pfeiltaste nach oben, dann ändern Sie sl2.Maximum eine positive Zahl 10, sl2.Value magisch Änderungen von 0 bis 0,1.
- Set sl2 Eigentum wie Value to Double.NaN, einen ungültigen Wert, wird eine Ausnahme werfen, aber sl2.Value geändert wird sowieso Double.NaN und keine ValueChanged Ereignis ausgelöst wird.
Source Code
Unten ist der Code debuggen und wir experimentieren mit:
- page.xaml:
- page.xaml.cs:
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 = "White"> x:Name ="sl" /> <Slider x: Name = "sl" /> x:Name ="sl2" Minimum ="-1" /> <My: Slider2 x: Name = "SL2" Minimum = "-1" /> x:Name ="btn" Content ="Break!" <Taste x: Name = "btn" Content = "! Break" HorizontalAlignment ="Center" /> Click = "btn_Click" HorizontalAlignment = "center" /> > </ StackPanel> > </ UserControl>
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) { } } } {/ / Unterklasse Slider einfach auf Eigenschaft zu ändern und Taste gedrückt Ereignisse brechen public class Slider2:.. Slider {protected override void OnMinimumChanged (double oldMinimum-, Doppel-newMinimum) {base OnMinimumChanged (oldMinimum, newMinimum);} protected override void OnMaximumChanged (double oldMaximum-, Doppel-newMaximum) {base OnMaximumChanged (oldMaximum, newMaximum);.} protected override void OnValueChanged (double oldValue-, Doppel-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 ();} / / auf die Schaltfläche klicken Event-Handler bietet eine Chance, in Debugger / / und experimentieren Sie mit RangeBase Validierung Pause , Nötigung und Änderung Handhabung. private void btn_Click (object sender, RoutedEventArgs e) {}}}
Debug & Experiment
Erstellen und Ausführen über einfache Silverlight-Anwendung, werden Sie unter Screenshot zu sehen. Bitte beachten Sie die Daumen Lage Unterschied zwischen den beiden Schiebern:
Klicken Sie auf das Break! Taste, um in ihre Event-Handler in Visual Studio zu brechen, prüfen Sie den Wert des Schiebers sl und SL2:
- <Slider X:Name="sl"/>: sl hat den richtigen Wert von Mininum = Value = 0 und Maximum = 10, während <Slider x:Name="sl2" Minimum="-1"/> sl2 hat Minimum = -1, aber Maximum = Value = 0;
Für diejenigen, die Teil II lesen und sind jetzt vertraut mit der Umsetzung Muster, das ist die Ursache des seltsamen Verhaltens sl2 ist:
- RangeBase Blätter _initialMax, _initialVal, _requestedMax, _requestedVal von CLR zum ursprünglichen Wert 0 initialisiert werden. Das ist OK für Value-Eigenschaft, da sie standardmäßig sowieso Null auf, aber es ist falsch für Maximum-Eigenschaft, die standardmäßig auf 1 in seiner DependencyProperty.Register anrufen und seinen Standardwert verwendet als effektive Wert:
- Einstellen sl2.Minimum auf -1 löst OnMinimumPropertyChanged -> CoerceMaximum, die die CLR initialisiert _requestedMax auf Maximum zu zwingen verwendet. Da _requestedMax ist 0 statt der aktuellen effektiven Maximum den Wert von 1 und 0 passiert, größer zu sein als Minimum den aktuellen Wert von -1 wird so Maximum auf 0 ändern:
- Der Grund <Slider x:Name="sl"> hat einen maximalen Wert von 10 statt 1 ist standardmäßig, dass die Standard-Vorlage einen <Setter Property="Maximum" Value="10"/> hat. Diese Linie behebt auch das Problem, dass _requestedMax nicht initialisiert ist. Aus diesem Grund ist sl._requestedMax 10 in obigen Screenshot.
Anschließend legen Sie einen Haltepunkt in jeder Funktion, lassen Sie die Anwendung ausführen. Wählen Sie sl2 Daumen, um es in Fokus zu machen, schlug rechts oder Pfeiltaste nach oben, zerfällt es in OnKeyDown Event-Handler. Überprüfen Sie sl2 den Wert: _requestedVal ist 0; Schritt durch base.OnKeyDown (e); überprüfen sl2 wieder: _requestedVal wird 0,1. Dies liegt daran, base.OnKeyDown Griffe rechts / Pfeil nach oben Tastendruck und versucht to Value von SmallIncrement von 0,1 zu erhöhen, dies löst OnValuePropertyChanged -> CoerceValue, die dann zwingt Wert auf 0 zurück, da Maximum ist 0, aber es erinnert sich an die Anfrage der Inkrement, indem _requestedVal bis 0,1.
Während noch im Direkt-Fenster, stellen sl2.Maximum bis 10. Dies löst OnMaxiumPropertyChanged:
was wiederum löst CoerceValue, die _requestedVal, dessen Wert nun 0,1 überprüft. Seit 0.1 ist im Bereich von [-1, 10], so Wert ist auf 0,1 geändert:
Zum Schluß will eingestellten Wert zu Double.NaN. Dies löst eine ArgumentException, wie erwartet:
aber Wert auf Double.NaN sowieso geändert und keine ValueChanged Veranstaltung ist wegen der ArgumentException ausgelöst:
Dependency Property Code Snippet
Inzwischen haben Sie wahrscheinlich die Idee gekommen, dass es ziemlich schwierig ist, Abhängigkeitseigenschaft mit Validierung, Nötigung und Änderung Handhabung korrekt auf Silverlight umzusetzen. Um dies zu erleichtern, unten ist ein Code-Snippet, dass der Großteil des Codes stellt für og Mehrverbrauch. Dieser Codeausschnitt wurde ursprünglich von Ted Glaza verfasst, und ich modifiziert habe, um das vollständige Muster liefern. Sie können entfernen Zeug nicht für einfachere Abhängigkeitseigenschaften tun müssen, wie jene, die keine Validierung, Nötigung, oder ändern Handhabung. Sie müssen noch in der eigenen Validierung, Nötigung, und ändern Handhabung Logik, indem Sie Funktionen wie IsValid $ property $, zwingen $ property $, Am $ property $ PropertyChanged oder On $ property $ Changed etc zu füllen, und fügen Sie eine einheitliche Definition der private int _nestLevel. Aber zumindest müssen Sie nicht über die meisten Unterschiede zwischen WFP und Silverlight Eigentums-Systeme kümmern, und konzentrieren uns nur auf die Validierung, Nötigung und Änderung Umgang mit Logik sich.
> < Shortcut > sdp </ Shortcut > < Description > Code snippet for a dependency property with validation, coercion and changed event. </ Description > < Author > Ning Dependency Property </ title> <Shortcut> sdp </ Shortcut> <Beschreibung> Code-Snippet für eine Abhängigkeitseigenschaft mit Validierung, Nötigung und verändert Ereignis. </ Description> <Autor> Ning > < Default > MyProperty </ Default > </ Literal > < Literal > < ID > type </ ID > < ToolTip > Property type </ ToolTip > < Default > object </ Default > </ Literal > < Literal > < ID > defaultValue </ ID > < ToolTip > Default name </ ToolTip> <Default> MyProperty </ Default> </ Literal> <Literal> <ID> Art </ ID> <ToolTip> Objektart </ ToolTip> <Default> Objekt </ Default> </ Literal> < Literal> <ID> defaultValue </ ID> <ToolTip> Default > /// Gets or sets the value of $property$ dependency property. public $ typ $ $ property $ / / / <summary> / / / Ruft den Wert von $ Eigenschaft $ Abhängigkeitseigenschaft. > public $type$ $property$ { get { return ($type$)GetValue($property$Property); } set { SetValue($property$Property, value); } } /// < summary > /// Identifies the $property$ dependency property. / / / </ Summary> public $ type $ $ $ Eigentum {get {return ($ type $) GetValue ($ property $ Property);} set {SetValue ($ property $ Property, value);}} / / / < summary> / / / Gibt das $ property $ Abhängigkeitseigenschaft. > 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 $ $ $ Eigentum Property = $ SystemWindowsDependencyProperty $. Register ("$ property $", typeof ($ type $), typeof ($ classname $), new $ SystemWindowsPropertyMetadata $ ($ defaultValue $, Am $ property $ PropertyChanged)); / / / <summary> / / / $ property $ Property-Eigenschaft geändert Handler. > /// < 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 $, dass seine $ property $. Geändert </ param> / / / <param name = "e"> Event Argumente. </ P> 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 {/ / revert wieder auf e.OldValue source._nestLevel + + (IsValid $ property $ (newValue)!); source.SetValue (e.Property, e.OldValue) ; source._nestLevel--; / / throw ArgumentException throw new ArgumentException ("Invalid $ property $ Property value", "e");} if (source._nestLevel == 0) {/ / erinnern Ausgangszustand source._initial $ property $ = oldValue; source._requested $ property $ = newValue;} source._nestLevel + +; / / zwingen newValue $ type $ coercedValue = ($ type $) Nötige $ property $ (d, e.NewValue); if (newValue = coercedValue ) {/ / immer $ property $ Property zu zwingen Wert Quelle eingestellt $ property $ = coercedValue;.} source._nestLevel--;.! if (source._nestLevel == 0 & & source $ property $ = source._initial $ property $ ) {/ / Feuer geändert Ereignis nur bei Root-Ebene, und wenn es tatsächlich eine Veränderung source.On $ property $ Changed (oldValue, source $ Eigenschaft $);.}} / / / <summary> / / / $ property $ Property Validierung Handler. > /// < 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"> Neuer Wert von $ property $ Property. </ Param> / / / <returns> / / / Gibt true zurück, wenn der Wert für $ property $ Property gültig , sonst false. > private static bool IsValid$property$($type$ value) { return true; } /// < summary > /// $property$Property coercion handler. / / / </ Returns> private static bool IsValid $ property $ ($ type $ value) {return true;} / / / <summary> / / / $ property $ Property Zwang Handler. > /// < 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 $, dass seine $ Eigenschaft geändert $. </ Param> / / / <param name = "value"> Event Argumente. </ P> / / / <returns> / / / Erzwungene effektive Wert von $ $ Eigentum Eigentum von Input-Parameter-Wert. > 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 static Objekt Nötige $ property $ ($ SystemWindowsDependencyObject $ d, Objekt value) {$ classname $ source = ($ classname $) d; $ type $ newValue = ($ type $) Wert; Rückkehr newValue ;} / / / <summary> / / / $ property $ Property geändert Veranstaltung. > public event RoutedPropertyChangedEventHandler < $type$ > $property$Changed; /// < summary > /// Called by On$property$PropertyChanged static method to fire $property$Changed event. / / / </ Summary> public event RoutedPropertyChangedEventHandler <$ type $> $ property $ Changed / / / <summary> / / / von On $ property $ PropertyChanged statische Methode aufgerufen, um $ property $ Changed-Ereignis ausgelöst. > /// < 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"> Der alte Wert von $ property $. </ Param> / / / <param name = "newValue"> Der neue Wert von $ 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> / / / Cache vorherigen Wert von $ property $ Property. > private $type$ _initial$property$ = $defaultValue$; /// < summary > /// Cached originally requested value of $property$Property by user. / / / </ Summary> private $ type $ _initial $ property $ = $ $ defaultValue; / / / <summary> / / / Cache ursprünglich angeforderten Wert von $ $ Eigentum Eigentum durch den Benutzer. > private $type$ _requested$property$; #endregion public $type$ $property$ ]] > </ Code > </ Snippet > </ CodeSnippet > </ CodeSnippets > / / / </ Summary> private $ type $ _requested $ property $; # endregion public $ typ $ $ $ Eigentum]]> </ code> </ Snippet> </ CodeSnippet> </ CodeSnippets>
Abschluss
Ich hoffe, die Serie hat Ihnen geholfen zu verstehen und umzusetzen Abhängigkeitseigenschaften für WPF und Silverlight. Beide WPF und Silverlight sind große Plattformen und sind Grundlage für Software-Entwicklung, möglicherweise mehr als das, was Win32 zuvor.
Wie immer, Feedback, Anregungen, Korrekturen willkommen. Vielen Dank!








Recent Comments