WPFバインディング:ObjectDataProviderを使用する必要があるのはいつですか?

バインディングのデータソースとして使用されるオブジェクトを作成する方法は多数あります。 多くの人がコードでオブジェクトを作成し、このオブジェクトをウィンドウのDataContextプロパティに割り当てます。 これは一般的に良い方法です。 ほとんどの投稿でWindowクラスのリソースディクショナリにソースオブジェクトを追加したことに気付いたかもしれませんが、これは非常にうまく機能しました。 ただし、データバインディングにはObjectDataProviderクラスがあり、XAMLでソースオブジェクトを作成するためにも使用できます。 この投稿では、ソースオブジェクトをリソースに直接追加することとObjectDataProviderを使用することの違いについて説明します。 タスクを評価し、最適なソリューションを選択する方法についてのガイダンスを提供していただければ幸いです。



これらの可能性を説明するとき、私たちは人々が地球上の自分の体重を示し、木星にどれだけの重さがあるかを知ることができる小さなプログラムを作成します。



ソースオブジェクトをリソースに直接追加すると、データバインディングエンジンは、このオブジェクトのタイプのデフォルトコンストラクターを呼び出します。 リソースディクショナリに追加されたオブジェクトは、x:Keyで定義されたキーを使用します。 このアプローチのサンプルコードを次に示します。

< Window.Resources > < local:MySource x:Key =” source/> (…) </ Window.Resources > * This source code was highlighted with Source Code Highlighter .



  1. < Window.Resources > < local:MySource x:Key =” source/> (…) </ Window.Resources > * This source code was highlighted with Source Code Highlighter .



  2. < Window.Resources > < local:MySource x:Key =” source/> (…) </ Window.Resources > * This source code was highlighted with Source Code Highlighter .



  3. < Window.Resources > < local:MySource x:Key =” source/> (…) </ Window.Resources > * This source code was highlighted with Source Code Highlighter .



  4. < Window.Resources > < local:MySource x:Key =” source/> (…) </ Window.Resources > * This source code was highlighted with Source Code Highlighter .



< Window.Resources > < local:MySource x:Key =” source/> (…) </ Window.Resources > * This source code was highlighted with Source Code Highlighter .





または、ObjectDataProviderクラスのオブジェクトをリソースに追加し、バインドのソースとして使用できます。 ObjectDataProviderクラスは、追加の機能を提供するソースオブジェクトのラッパーです。 次に、ObjectDataProviderの次の注目すべき機能について説明します。



コンストラクターにパラメーターを渡す



ソースオブジェクトをリソースに直接追加すると、WPFは常にこの型の既定のコンストラクターを呼び出します。 ソースオブジェクトを制御できず、追加するクラスにデフォルトのコンストラクターがない場合があります。 たとえば、これはサードパーティライブラリのクラスであり、封印済みとして宣言されています。 この状況では、ObjectDataProviderを次の方法で使用して、XAMLでクラスのインスタンスを作成できます。





  1. < ObjectDataProvider ObjectType =” { x:タイプ local:MySource }” x:キー =” odp1>
  2. < ObjectDataProvider.ConstructorParameters >
  3. < システム:文字列 >木星</ システム:文字列 >
  4. </ ObjectDataProvider.ConstructorParameters >
  5. </ ObjectDataProvider >
*このソースコードは、 ソースコードハイライターで強調表示されました。


このマークアップは、コンストラクターを呼び出して文字列「Jupiter」をパラメーターとして渡すことにより、MySourceクラスのインスタンスを作成します。 同時に、ObjectDataProviderクラスのオブジェクトも作成されます。これは、MySourceクラスのオブジェクトをラップします。



MySourceには、「Planet」と呼ばれるパブリックプロパティがあり、コンストラクタに渡された文字列と名前が一致するPlanetクラスのオブジェクトを提供します。 私たちの場合、これは「Jupiter」です。 このプログラムには、惑星のNameプロパティと通信するラベルが必要です。 サブプロパティのバインドは、「ドット表記」を使用してWPFで実行できます。 一般的に、次のようになります:Path = Property.SubProperty。 これは次のコードで確認できます。





  1. < Label Content =” { Binding Source = { StaticResource odp1 }、 Path = Planet名前 }” グリッドColumnSpan =” 2Horizo​​ntalAlignment =” CenterFontWeight =” BoldForeground =” IndianRedFontSize =” 13Margin =” 5、5、5、15/>
*このソースコードは、 ソースコードハイライターで強調表示されました。


このバインディングの定義を見て、それが意味をなさないと考えることができます。 ObjectDataProviderクラスのPlanetプロパティのNameサブプロパティに関連付けられているように見えます。 しかし、上記で、MySourceにはPlanetプロパティがあり、ObjectDataProviderにはないことを述べました。 このコードが機能する理由は、ソースオブジェクトのPathプロパティがラップされる(つまりラップされる)ときに、バインディングエンジンが特別な方法でObjectDataProviderにアクセスするためです。 この特別な処理は、XmlDataProviderまたはCollectionViewSourceにリンクするときにも適用できることに注意してください。



メソッドバインディング



MySourceには、地球上の人の体重を引数として受け取り、コンストラクターに渡された惑星上のその人の体重を計算するメソッドがあります。 XAMLのメソッドに重み値を渡し、その結果に連絡したい。 これは、ObjectDataProviderを使用して実行できます。





  1. < ObjectDataProvider ObjectInstance =” { StaticResource odp1 }” MethodName =” WeightOnPlanetx:Key =” odp2>
  2. < ObjectDataProvider.MethodParameters >
  3. < システム:ダブル > 95 </ システム:ダブル >
  4. </ ObjectDataProvider.MethodParameters >
  5. </ ObjectDataProvider >
*このソースコードは、 ソースコードハイライターで強調表示されました。


ObjectTypeプロパティを設定する代わりに、今回はObjectInstanceプロパティを設定します。 これにより、前のObjectDataProviderで作成したMySourceクラスのインスタンスを再利用できます。 また、MethodNameプロパティを設定し、MethodParametersプロパティを使用してこのメ​​ソッドにパラメーターを渡しました。 メソッドから返された結果の表示は、この2番目のObjectDataProviderでバインディングを作成するのと同じくらい簡単です。





  1. < Label Content =” { Binding Source = { StaticResource odp2 }}” Grid =” 2グリッド =” 1グリッドColumnSpan =” 2/>
*このソースコードは、 ソースコードハイライターで強調表示されました。


これは良い出発点ですが、ユーザーがTextBoxに自分の体重を入力できるようにしたいので、その後、Labelに新しい値が表示されます。 ただし、問題は、このプロパティがDependencyPropertyではないため、MethodParametersプロパティにBindingの説明を配置できないことです。 実際、ObjectDataProviderもDependencyObjectではありません。 思い出してください。ソースは何でも構いませんが、Bindingの目標はDependencyプロパティにする必要があります。 幸いなことに、CLRプロパティをDependencyプロパティに関連付ける場合は、解決方法があります。Dependencyプロパティにバインディングを配置し、Mode BindingプロパティをTwoWayまたはOneWayToSourceに設定することで、ソースとターゲットを交換できます。 このコードは、WeightOnPlanetメソッドに渡された引数を変更するTextBoxを作成する方法を示します。





  1. < Window.Resources >
  2. (...)
  3. < local:DoubleToString x:Key =” doubleToString/>
  4. (...)
  5. </ Window.Resources >
  6. (...)
  7. < TextBox グリッド =” 1グリッド =” 1名前 =” tbスタイル =” { StaticResource tbStyle }” >
  8. < TextBox.Text >
  9. < Binding Source =” { StaticResource odp2 }” Path =” MethodParameters [ 0 ]” BindsDirectlyToSource =” trueUpdateSourceTrigger =” PropertyChangedConverter =” { StaticResource doubleToString }” >
  10. (...)
  11. </ バインディング >
  12. </ TextBox.Text >
  13. </ TextBox >
*このソースコードは、 ソースコードハイライターで強調表示されました。


この状況では、TextBoxクラスのオブジェクトのTextプロパティでバインディングを作成するため、バインディングを双方向にするために何もする必要はありません。 [バインディング]は既にデフォルトで双方向です。 デフォルトでは、バインディングは、ほとんどの依存関係プロパティでは一方向であり、ユーザーがデータを変更すると予想されるプロパティでは双方向になります。 このデフォルトの動作は、BindingのModeプロパティを変更することでオーバーライドできます。



前述したように、ObjectDataProviderでバインディングを作成すると、バインディングエンジンは、ObjectDataProvider自体ではなく、ラップしているソースを自動的に調べます。 この状況では、ObjectDataProviderのMethodParametersプロパティに連絡する必要があるため、これにより問題が発生します。 標準の動作を変更するには、BindsDirectlyToSourceプロパティをtrueに設定する必要があります。



MethodParametersプロパティはIListです。この状況では、WeightOnPlanetメソッドがパラメーターを1つしか受け取らないため、リストの最初の要素にバインドします。 C#コードの場合と同様に、インデクサーを使用してこれを行うことができます。



UpdateSourceTriggerプロパティをPropertyChangedに設定しているため、メソッドが呼び出されると、ユーザーがTextBoxに何かを入力するたびに新しい値を取得します。 UpdateSourceTriggerプロパティのその他の可能な値は、「明示的」(バインディングでUpdateSource()メソッドを明示的に呼び出す必要があります)および「LostFocus」(コントロールがフォーカスを失うとソースが更新されます)であり、これがデフォルトです。



double型のプロパティにバインドしている場合、バインドエンジンはText TextBoxのプロパティをdouble型に自動的に変換しようとします。 ただし、メソッドにアタッチされているため、独自のコンバーターを作成する必要があります。 これがないと、バインディングはWeightOnPlanetメソッドを呼び出そうとします。このメソッドは文字列をパラメーターとして受け取り、エラーになります。 そのような方法は存在しません。 Visual Studioの[出力]ウィンドウを見ると、渡すパラメーターを受け入れるメソッドが見つからなかったというデバッグメッセージが表示されます。 このコンバーターのコードは次のとおりです。





  1. パブリック クラス DoubleToString:IValueConverter
  2. {
  3. パブリック オブジェクトの 変換オブジェクト 、タイプtargetType、 オブジェクトパラメーター、System.Globalization.CultureInfoカルチャ)
  4. {
  5. ifvalue != null
  6. {
  7. 戻り .ToString();
  8. }
  9. nullを 返し ます
  10. }
  11. パブリック オブジェクト ConvertBack( オブジェクト 、タイプtargetType、 オブジェクトパラメーター、System.Globalization.CultureInfoカルチャ)
  12. {
  13. string strValue = value as string ;
  14. if (strValue!= null
  15. {
  16. 二重結果;
  17. bool Converted = Double.TryParse(strValue、 out result);
  18. if (変換済み)
  19. {
  20. 結果を返す ;
  21. }
  22. }
  23. nullを 返し ます
  24. }
  25. }
*このソースコードは、 ソースコードハイライターで強調表示されました。


一部のユーザーは、このコンバーターに少し困惑している可能性があります。StringToDoubleまたはDoubleToStringのどちらでしょうか。 Convertメソッドは、データがソース(double)からターゲット(string)に転送されるときに呼び出され、ConvertBackメソッドは、転送が反対方向にあるときに呼び出されます。 したがって、DoubleToStringコンバーターが必要であり、他のコンバーターは必要ありません。



また、ユーザーが間違った体重値を入力するとどうなりますか? 彼は負の数、または数字だけでなく文字列を入力することも、まったく入力しないこともできます。 そして、状況がこれである場合、バインディングからソースへのデータ転送を開始させたくありません。 バインディングがデータを送信しないようにするだけでなく、ユーザーが入力した値が間違っていることをユーザーに警告する独自のロジックを作成します。 これは、データバインディングの検証機能を使用して実行できます。 値を検証するValidationRuleを作成し、次の方法でプロパティに追加しました。





  1. < Binding Source =” { StaticResource odp2 }” Path =” MethodParameters [ 0 ]” BindsDirectlyToSource =” trueUpdateSourceTrigger =” PropertyChangedConverter =” { StaticResource doubleToString }” >
  2. < Binding.ValidationRules >
  3. < ローカル:WeightValidationRule />
  4. </ Binding.ValidationRules >
  5. </ バインディング >
*このソースコードは、 ソースコードハイライターで強調表示されました。


WeightValidationRuleはValidationRuleの子孫であり、必要なロジックを説明したValidateメソッドをオーバーライドします。





  1. パブリック クラス WeightValidationRule:ValidationRule
  2. {
  3. パブリック オーバーライド ValidationResult Validate( オブジェクト 、System.Globalization.CultureInfo cultureInfo)
  4. {
  5. //値は文字列ではありません
  6. string strValue = value as string ;
  7. if (strValue == null
  8. {
  9. //起こらない
  10. 新しい ValidationResultを返し ますfalse 、「無効な重み-値 文字列で はありません」);
  11. }
  12. //値はdoubleに変換できません
  13. 二重結果;
  14. bool Converted = Double.TryParse(strValue、 out result);
  15. if (!変換)
  16. {
  17. 新しい ValidationResultを返し ますfalse 、「無効な重量-有効な数字を入力してください」);
  18. }
  19. //値は0から1000の間ではありません
  20. if ((結果<0)||(結果> 1000))
  21. {
  22. 新しい ValidationResultを返し ますfalse 、「無効な体重-体重が軽いか重いか」);
  23. }
  24. return ValidationResult.ValidResult;
  25. }
  26. }
*このソースコードは、 ソースコードハイライターで強調表示されました。


現在、間違った値を入力すると、TextBoxの周りに赤い境界線が表示されます。 ただし、ValidationResultに含まれるメッセージをユーザーに通知する必要があります。 さらに、ユーザーが何か間違ったことをすると、ツールチップにエラーメッセージが表示されます。 これはすべて、スタイルとトリガーを使用して、XAMLで実行できます。





  1. < スタイル x:Key =” tbStyleTargetType =” { x:Type TextBox }” >
  2. < Style.Triggers >
  3. < トリガー プロパティ =” 検証HasError =” true>
  4. < Setter プロパティ =” ToolTip
  5. =” { Binding RelativeSource = { RelativeSource Self }、 Path =( Validation。Errors )[ 0 ]。 ErrorContent }” />
  6. </ トリガー >
  7. </ Style.Triggers >
  8. </ スタイル >
*このソースコードは、 ソースコードハイライターで強調表示されました。


Validation.HasErrorは、少なくとも1つのValidationRuleがエラーを返すたびにtrueになる添付されたDependencyプロパティです。 Validation.Errorsは、特定の要素のエラーのリストを含むネストされたDependencyプロパティでもあります。 この場合、TextBoxにはエラーが1つしか存在しないことがわかっています(ルールが1つしかないため)。つまり、ToolTipをこのリストの最初のエラーに安全に関連付けることができます。 「{RelativeSource Self}」は、単にバインディングのソースがTextBox自体であることを意味します。 括弧はPathプロパティの説明で使用されていることに注意してください。ネストされたDependencyプロパティにバインドするときは常に括弧を使用する必要があります。 ロシア語では、「Path =(Validation.Errors)[0] .ErrorContent」は、ソース(つまり、TextBox)で添付プロパティValidation.Errorsを探していることを意味し、そこからリストの最初の要素を取得し、受信したValidationErrorのサブプロパティErrorContent 'a。



TextBoxに0から1000の範囲の数値以外を入力しようとすると、エラーメッセージのあるツールチップが表示されます。



検証の他の側面を示す例を作成しました(SDKに含まれています)。 この機能についてはまだ説明がありますが、以降の投稿についてはさらに詳細な説明を残します。



ソースオブジェクトの置き換え



すべてのバインディングの現在のソースを他のオブジェクトに置き換える必要がある場合に必要になることがあります。 リソースディクショナリに、バインディング中にソースとして使用されるオブジェクトがある場合、このオブジェクトを他のオブジェクトに置き換えて、すべてのバインディングを更新する方法はありません。 リソースディクショナリからこのオブジェクトを削除し、同じx:キーを持つ新しいオブジェクトを追加しても、バインディングにこのことは通知されません。



この場合、ObjectDataProviderを使用することを決定できます。 ObjectTypeプロパティを変更すると、このObjectDataProviderへのすべてのバインディングに、ソースオブジェクトが更新され、データを更新する必要があることが通知されます。



要素ツリーの上位にある要素のDataContextプロパティを割り当てる場合、ソースオブジェクトはプログラムであることに注意してください。別のオブジェクトを割り当てると、すべてのバインディングも更新されます。



ソースオブジェクトの非同期作成



ObjectDataProviderにはIsAsynchronousというプロパティがあり、データをプログラムと同じストリームに読み込むか、別のストリームに読み込むかを制御できます。 デフォルトでは、ObjectDataProviderの場合、このプロパティはfalseに設定され、XmlDataProviderの場合はtrueです。



これについては、今後の投稿で詳しく説明する予定ですので、準備してください。



この例のソースコードを使用して、ユーザーが惑星を選択し、体重を入力し、選択した惑星の体重を調べることができるWPFプログラムを作成できます。 実際、非常に簡単です。









ここでは、記事で使用されたコードを含むVisual Studioのプロジェクトを見つけることができます。



All Articles