DependencyPropertyに:検証、強制&変更処理(第III部:依存関係プロパティのコードスニペット)
はじめ
これは、WPFとSilverlightに関する検証、強制およびイベントとの依存関係プロパティを実装する方法について3回シリーズの最後の部分です。 にパートIは 、私は単純な依存関係プロパティを実装し、依存関係プロパティは、WPFとWPFの依存関係プロパティを実装するための共通のパターンにどのように連携するかを示すを通じてデバッグ。 でパート 、私は、Silverlightで同じでした。 Silverlightは、PropertyChangedCallbackプロパティサポートされていますが、すべての処理の検証、強制と変化がPropertyChangedCallbackプロパティを実装する必要がCoerceValueCallbackとValidateValueCallbackではない、ので、ので。 これは、PropertyChangedCallbackプロパティ検証や強制は、依存関係プロパティの有効値を変更したときに再帰的に呼び出されているが発生します。 加えてSilverlightのプロパティシステムの他の制限は、それは正しく依存関係プロパティを実装するために非常にトリッキー得ることができます。 この記事私はこれがいかにトリッキーさらに実証するためのSilverlight RangeBaseコントロールとその原因のいくつかのあいまいな動作が表示されます。 そして最後に、私はで説明した依存関係プロパティの実装の 完全なパターンを実装するコードスニペットを提供されるパーツをシリーズを通して読んでいる人への報酬として、 ![]()
SilverlightのRangeBaseコントロール
概要
強制の制約と、最小値、最大値、および値の依存関係プロパティを実装するRangeBase、、からスクロールバー、プログレスバーやスライダーが継承する最小<=値<=最大値。
これは簡単に聞こえます
しかし、RangeBaseコントロールの振る舞いは非常に奇妙得ることができます。 いくつかの例を取る:
- <Slider x:Name="sl"/>を使用すると、最小とスライダーを与える= 0、値= 0、最大= 10、一方<Slider x:Name="sl2" Minimum="-1" />スライダーを生成します最小= -1、値= 0、最大値= 0で。
- 焦点にSL2を作るためにスライダSL2のドラッグの親指をクリックして、矢印キーが正の数を10にsl2.Maximumを変更し、右または打つ、sl2.Valueは魔法のように0から0.1に変わります。
- double.NaN、無効な値への値のようなSL2のプロパティを設定し、例外がスローされますが、sl2.Valueはとにかくdouble.NaNに変更されておらず、ValueChangedイベントがトリガーされません。
ソースコード
以下は、デバッグとで実験されるコードは次のとおりです。
- Page.xamlを:
- page.xaml.cs:
x:Class ="SLApp2.Page" <UserControlの x:はクラス ="SLApp2.Page" xmlnsは ="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns名前:私 ="CLR名前空間:SLApp2" xmlns名前:X ="http://schemas.microsoft.com/winfx/2006/xaml"> x:Name ="LayoutRoot" Background ="White" > <StackPanelの X:名前 ="のLayoutRoot" 背景 ="ホワイト"> x:Name ="sl" /> < スライダー X:名前 ="SL"/> x:Name ="sl2" Minimum ="-1" /> < 私:スライダ X:名前 ="SL2" 最小 ="-1"/> x:Name ="btn" Content ="Break!" < ボタン X:名前 ="btnの" コンテンツ ="ブレイク!" HorizontalAlignment ="Center" /> クリックして ="btn_Clickは"="センター"/>を HorizontalAlignmentに > </ 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) { } } } {/ /サブクラスのスライダーを簡単にダウンイベントプロパティの変更やキーで改行する パブリッククラススライダ:。。スライダーは{ オーバーライドするvoid OnMinimumChanged( ダブル oldMinimum、 ダブル newMinimum){ ベースが OnMinimumChanged(oldMinimum、newMinimum) 保護 ;} 保護 OnMaximumChanged( ダブルvoidをオーバーライドする oldMaximum、 ダブル newMaximum){ ベース OnMaximumChangedは(oldMaximum、newMaximum);}; System.Windows.Input.KeyEventArgs( 保護されたオーバーライドするvoid OnKeyDownはボイド {。 基底 OnValueChangedを(のOldValue、newValueは)}( 二重のOldValue、 ダブル newValueは)OnValueChangedを上書き保護 E){ ベース OnKeyDownは(E);}} 公共部分クラスのページ:UserControlの{ 公開ページ(){InitializeComponent()の;} / /ボタンのクリックイベントハンドラでは、デバッガ/ /とRangeBase検証実験に侵入する機会を提供します。 、強制および変更処理します private void btn_Click( オブジェクト送信者、RoutedEventArgsのe){}}}
デバッグ&実験
ビルドと簡単なSilverlightアプリケーションの上に実行するには、スクリーンショットの下に表示されます。 つのスライダの間に親指の位置が異なってきますのでご注意ください。
ブレークをクリック! Visual Studioでそのイベントハンドラに侵入するためのボタン、スライダーSLとSL2の値をチェックします。
- <Slider x:Name="sl"/>:SLはMininumの正しい値を持っています=値= 0、最大= 10、一方<Slider x:Name="sl2" Minimum="-1"/> SL2は、最小値=を持っています-1が最大=値= 0;
パートIIを読み、現在の実装パターンに慣れている人のために、これはSL2の奇妙な行動の根本的な原因です。
- デフォルトの値を0にCLRによって初期化される_requestedVal RangeBase葉_initialMax、_initialVal、_requestedMax、。 それデフォルトはとにかくゼロにので、これは、ValueプロパティのためにOKです、しかしそれは、Maximumプロパティのために間違っている、デフォルトは1にそのDependencyProperty.Registerの呼び出しで、その実効値としてデフォルト値を使用します。
- -1〜sl2.Minimum設定すると、OnMinimumPropertyChangedトリガ - 最大値を強制するためにCLRの初期化_requestedMaxを使用する> CoerceMaximumを、。 _requestedMax 1の代わりに最大の電流実効値の0であり、0は-1の最小の現在の値より大きいことが起こるので、その最大値は0に変更です。
- <Slider x:Name="sl">はデフォルトで1の代わりに10の最大値を持つ理由は、デフォルトのテンプレートがProperty="Maximum" Value="10"/> <Setterを持っているということです。 この行には、_requestedMaxが初期化されていないという問題を修正。 sl._requestedMaxは、上記のスクリーンショットが10なのはこのためです。
次に、アプリケーションの実行をできるように、各関数にブレークポイントを設定します。 フォーカスされてそれを作るためにSL2の親指を選択し、右または上矢印キーを押すと、OnKeyDownイベントハンドラに分割されます。 SL2の値を確認してください:_requestedValは0です。base.OnKeyDown(E)を介してのステップは、再びSL2を確認してください:_requestedValは0.1になります。 これはOnValuePropertyChangedトリガ - 最大が0であることからして、0に戻って値を強制> CoerceValueをして、; base.OnKeyDownアップ/右矢印キーストロークを処理すると0.1のSmallIncrementで値を増加しようとするためですが、それはのリクエストを覚えている0.1〜_requestedVal設定することにより、インクリメント。
それでもイミディエイトウィンドウ内の一方で、10にsl2.Maximumを設定します。 これはOnMaxiumPropertyChangedトリガ:
その値は0.1である_requestedValチェックCoerceValueを、、トリガーターンインチ 0.1の範囲[-1、10]になっているので、その値は0.1に変更されます。
最後に、のはdouble.NaNに値を設定できます。 これは期待通りにArgumentExceptionを、トリガされます。
が、値はとにかくdouble.NaNに変更されず、ValueChangedイベントがあるため、ArgumentExceptionのトリガーされません。
依存関係プロパティのコードスニペット
今ではおそらくそれが検証、強制と変更Silverlight上で正しく処理で依存関係プロパティを実装するためにかなりトリッキーであるという考えを持っている。 これを容易にするために、以下に上記の実装パターンのコードのほとんどを提供するコードスニペットです。 このコードスニペットは、もともとテッドGlazaで執筆、と私は完全なパターンを提供するためにそれを変更されました。 あなたが検証、強制、または変更処理を必要としないもののように、単純な依存関係プロパティのために必要のないものを削除することができます。 あなたはまだにPropertyChanged $プロパティ$、または上で$プロパティでisValid $プロパティ$、強制$プロパティ$、$変更などのように変更して機能することにより、独自の検証、強制、および変更の処理ロジックを記入する必要があり、単一の定義を追加するにはどうすればよいプライベートint型_nestLevelの。 しかし、少なくともあなたは、WFPとSilverlightのプロパティシステムの違いのほとんどのことを心配する必要、および検証のみ、強制および変更の処理ロジックそのものに焦点をしないでください。
> < Shortcut > sdp </ Shortcut > < Description > Code snippet for a dependency property with validation, coercion and changed event. </ Description > < Author > Ning検証、強制と変更されたイベントとの依存関係プロパティの依存関係プロパティ</ タイトル > < ショートカット > SDP </ ショートカット > < 説明 >コードスニペット。</ 説明 > < 著者 >寧 > < Default > MyProperty </ Default > </ Literal > < Literal > < ID > type </ ID > < ToolTip > Property type </ ToolTip > < Default > object </ Default > </ Literal > < Literal > < ID > defaultValue </ ID > < ToolTip > Default名前</ ツールヒント > < デフォルト > MyPropertyに</ デフォルト > </ literal>の< リテラル > <ID>タイプ</ ID> < ツールチップ >不動産の種類</ ツールヒント > < デフォルト >オブジェクト</ デフォルト > </ literal>と< リテラル > <ID>はdefaultValue </ ID> < ヒント >デフォルト > /// Gets or sets the value of $property$ dependency property.プロパティpublic $型$ $ $ / / / < 概要 > / / /は$プロパティ$依存関係プロパティの値を取得または設定します。 > public $type$ $property$ { get { return ($type$)GetValue($property$Property); } set { SetValue($property$Property, value); } } /// < summary > /// Identifies the $property$ dependency property. / / / </ 概要 >公共$型$ $プロパティ$ {{戻り値($型$)、GetValueを($プロパティ$プロパティ)取得;}集合{SetValueを($プロパティ$プロパティ、値);}} / / / < 概要 > / / /プロパティ$ $依存関係プロパティを識別します。 > 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. / / / </ 概要 >をpublic static readonly $ SystemWindowsDependencyPropertyプロパティ$ $ $プロパティ= $ SystemWindowsDependencyProperty $。レジスタ("$プロパティ$"、typeof演算($型$)、typeof演算($ $ classnameで)、新しい$ SystemWindowsPropertyMetadata $($ $プロパティ$のPropertyChangedのdefaultValueは$、)); / / / < 概要 > / / / $ハンドラを変更されたプロパティ$ Propertyプロパティ。 > /// < 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. / / / </ 概要 > / / / < パラメータ 名 ="D"> $は$プロパティを変更すること$ $ classnameで。</ PARAM> / / / < パラメータ 名 ="メール">イベントの引数。</ PARAM> $プロパティ$のPropertyChanged($ SystemWindowsDependencyObject $ D、$ SystemWindowsDependencyPropertyChangedEventArgs $ E){$ $ classnameでソース=($ $ classnameで)dの上でプライベート静的ボイド; $型$ newValueに=($型$)e.NewValue; $型$のOldValue =($型$)e.OldValue; / / newValueはを検証する場合{/ / e.OldValue source._nestLevelに戻す+ +(のIsValidプロパティ$ $(newValueは)!); source.SetValue(e.Property、e.OldValue) ; source._nestLevel - - 、/ /は、ArgumentExceptionが("無効な$プロパティ$プロパティの値"、"E")新しいArgumentExceptionをスロースロー;}(source._nestLevel == 0){/ /は、初期状態source._initial $プロパティを覚えていれば$ = OldValueプロパティ。source._requested $プロパティ$ = newValueに;} source._nestLevel + +; / /強制newValueは$型$ coercedValue =($型$)強制$プロパティ$(D、e.NewValue);!場合(newValueに= coercedValue ){/ /常に強制された値のソースに$プロパティ$プロパティを設定する$プロパティ$ = coercedValue;} source._nestLevel - - ;!場合(source._nestLevel == 0&&ソース$プロパティ$ = source._initial $プロパティ$ ){/ /火災には、唯一のルートレベルでイベントを変更し、変更source.Onは$プロパティ$変更(のOldValue、ソース$プロパティ$)が実際に存在する;}} / / / < 概要 > / / / $プロパティ$プロパティ検証ハンドラ。 > /// < param name ="value" > New value of $property$Property. </ param > /// < returns > /// Returns true if value is valid for $property$Property, false otherwise. / / / </ 概要 > / / / < パラメータ 名 ="値"> $プロパティ$プロパティの新しい値。</ PARAM> / / / < リターン > / / /戻り値がtrueの場合には、プロパティ$ $プロパティに対して有効である場合、そうでない場合はfalse。 > private static bool IsValid$property$($type$ value) { return true; } /// < summary > /// $property$Property coercion handler. / / / </ リターン >プライベート静的なブール値のIsValidプロパティ$ $($型$値){trueを返します。} / / / < 概要 > / / / $プロパティ$プロパティ強制ハンドラ。 > /// < 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. / / / </ 概要 > / / / < パラメータ 名 ="D">その$プロパティを変更$ $ classnameで$。</ PARAM> / / / < パラメータ 名 ="値">イベントの引数。</ PARAM> / / / < リターン > / / /入力パラメータの値から$プロパティ$財産の強制実効値。 > private static object Coerce$property$($SystemWindowsDependencyObject$ d, object value) { $classname$ source = ($classname$)d; $type$ newValue = ($type$)value; return newValue; } /// < summary > /// $property$Property changed event. / / / </ リターン >プライベート静的オブジェクトの強制$プロパティ$($ SystemWindowsDependencyObject $ D、オブジェクトの値){$ $ classnameでソース=($ $ classnameで)D; $型$ newValueに=($型$)の値、戻りnewValueに;} / / / < 概要 > / / / $プロパティ$プロパティは、イベントを変更。 > public event RoutedPropertyChangedEventHandler < $type$ > $property$Changed; /// < summary > /// Called by On$property$PropertyChanged static method to fire $property$Changed event. / / / </ 概要 >パブリックイベントRoutedPropertyChangedEventHandler <タイプ$ $> $プロパティを$変更、プロパティ変更イベントを$を$を発射する$プロパティ$のPropertyChanged静的メソッドのによって呼び出されます/ / / < 概要 > / / /。 > /// < 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. / / / </ 概要 > / / / < パラメータ 名 ="OldValueプロパティ"> $プロパティ$の古い値。</ PARAM> / / / < パラメータ 名 ="newValueに"> $プロパティ$の新しい値。< $プロパティ$変更のオン/ パラメータ >保護された仮想無効(タイプ$ $のOldValue、$型$ newValueは){RoutedPropertyChangedEventArgs <$型$> E =新しいRoutedPropertyChangedEventArgs <$型$>(のOldValue、newValueは)、場合($プロパティが変更されました$ != NULL){$プロパティ変更$(これは、e);} $プロパティ$財産の} / / / < 概要 > / / /キャッシュされた以前の値。 > private $type$ _initial$property$ = $defaultValue$; /// < summary > /// Cached originally requested value of $property$Property by user. / / / </ 概要 >プライベート$型$ _initial $プロパティ$ = $ defaultValueは$。/ / / < 概要 >ユーザによる$プロパティ$プロパティの/ / /キャッシュされた要求された元の値。 > private $type$ _requested$property$; #endregion public $type$ $property$ ]] > </ Code > </ Snippet > </ CodeSnippet > </ CodeSnippets > / / / </ 概要 >プライベート$型$ _requested $プロパティ$、#endregion公共$型$ $プロパティ$]]> </ コード > </ スニペット > </ CodeSnippet> </ CodeSnippets>
結論
私はシリーズはWPFとSilverlightの依存関係プロパティを理解し実践あなたに助けた。 WPFとSilverlightの両方が素晴らしいプラットフォームであり、およびWin32は、前にやったものよりもおそらく多くのソフトウェア開発のための基本となります。
いつものように、フィードバック、提案、訂正は歓迎されている。 ありがとう!








最近のコメント