Home > C# > Toolkit > UIパターン > Prism > ドキュメント

6: WPFのためのPrism Library 5.0を使用する高度なMVVMの筋書き

新規作成日
最終更新日

原文

先程の話題は、あなたのアプリケーションのユーザー・インターフェイス(UI)を分離することによって、Model-View-ViewModel(MVVM)パターンの基本的な要素を、 どのように、実装するかを説明します。プレゼンテーション・ロジックとビジネス・ロジックを3つの分離したクラス(View、View ModelとModel)に、 (データ結合、コマンドとデータ検証インターフェイスを通して)、そして、構築と接続を処理するための戦略を実装することによって、 それらのクラスの間の相互作用を実装します。この話題は、いくつかの洗練された筋書きを説明し、そして、どのように、MVVMパターンが、それらをサポートするか説明します。 次の項目では、どのように、コマンドが、互いに、子のビューに関連付けて、連結されることができるか、そして、それらが、ユーザー定義した要件をサポートするために、 どのように、拡張されることができるか説明します。 次のセクションでは、続いて、非同期データの要求と後に続くUIの相互作用を、どのように、操作するか、 そして、ビュー間の相互作用の要求とView Modelを、どのように、操作するか、説明します。

高度な構築と接続の項目 (原文リンク)は、 依存関係注入コンテナを使用するとき、Unityアプリケーション・ブロック(Unity)のような、 あるいは、拡張管理フレームワーク(MEF)を使用するとき、構築と接続を扱うための、手引きを提供します。 最後の項目は、あなたのアプリケーションのView ModelとModelクラスとテストする動作のユニット・テストの手引きを提供することによって、 あなたが、MVVMアプリケーションを、どのようにテストすることができるか説明します。

コマンド

Commands

コマンドは、コマンドの実装ロジックをそのUIの表示から分離する方法を提供します。 データ結合や動作は、ビューとコマンドがView Modelで提供される、要素を宣言的に関連付けるための方法を提供します。 MVVMパターンを実装する原文リンク) コマンド(原文リンク)の項目で、 View Modelのコマンド・オブジェクトやコマンド・メソッドが、 どのように、実装できるか、そして、それらが、特定のコントロールによって提供される、組込みCommandプロパティを使用して、 ビューでコントロールを、どのように呼び出すことができるか、説明しました。

WPFのルーティングされたコマンド:

それが、コマンド・オブジェクトとして実装する必要があることに、注意します。あるいは、MVVMパターンのコマンド・メソッドは、 ルーティング・コマンドという名前のWPFの組み込みのコマンドの実装と、 いくぶん異なります。WPFのルーティング・コマンドは、UIツリー内の要素を通じて、それらを送ることによって、コマンドのメッセージを提供します。(特に論理ツリー(原文リンク))。 その結果、コマンド・メッセージは、フォーカスされた要素から、あるいは、明示的に指定された対象とする要素に、UIのツリーの上や下に送られます。; 既定では、それらは、ビューに関連付けられたView Modelのような、UIツリーのコンポーネントの外にに送られません。 しかしながら、WPFのルーティング・コマンドは、View Modelクラスにコマンド呼び出しを転送するために、 ビューの分離コードで定義されるコマンド・ハンドラを使用することができます。

複数の要素で構成されたコマンド

Composite Commands

多くの場合、View Modelによって定義されるコマンドは、関連するビューでコントロールに結合されます。 そのため、ユーザーは、直接、ビュー内からコマンドを呼び出すことができます。 しかしながら、場合によっては、あなたは、アプリケーションのUIで親ビューのコントロールから、 1つ以上のView Modelで、コマンドを呼び出すことができるようにしたいかもしれません。

例えば、あなたのアプリケーションは、ユーザーが、同時に複数の項目を編集できる場合、 あなたは、ユーザーが、アプリケーションのツールバーやリボンのボタンによって、表示される単一のコマンドを使用して、すべての項目を保存したいかもしれません。 この場合、すべてを保存コマンドは、View Modelインスタンスによって、次の図で示すそれぞれの項目のために、実装されるSaveコマンドの各々を呼び出します。

SaveAll複合コマンドを実装する

SaveAll複合コマンドを実装する

Implementing the SaveAll composite command

Prismは、CompositeCommandクラスによる、この筋書きをサポートします。 CompositeCommandクラスは、複数の子のコマンドから構成されるコマンドを示します。 複合コマンドが呼び出されるとき、その子のコマンドの各々は、順番に呼び出されます。 それは、UIの単一のコマンドとして、コマンドのグループを表示する必要がある場所、 あるいは、あなたが、論理コマンドを実装するために、複数のコマンドを呼び出したい場所の状況で、役立ちます。

例えば、CompositeCommandクラスは、buy/sellビューでSubmit Allボタンによって、コマンドが表示した、 SubmitAllOrdersを実装するために、株トレーダーの参考になる実装(株トレーダーRI)で使用されます。 ユーザーが、Submit Allボタンをクリックすると、それぞれのbuy/sellトランザクションによって、 実行される、それぞれのSubmitCommandで定義されます。

CompositeCommandクラスは、子のコマンドのリストを保持します(DelegateCommandインスタンス)。 CompositeCommandクラスのExecuteメソッドは、単純に、それぞれの子のコマンド上で、順番にExecuteメソッドを呼び出します。 同様に、CanExecuteメソッドは、それぞれの子のコマンドのCanExecuteメソッドを呼び出します。 しかし、子のコマンドを、どれも実行することができない場合、CanExecuteメソッドは、falseを返します。 言い換えると、既定では、すべての子のコマンドが、実行することができるとき、CompositeCommandは、実行することだけができます。

子コマンドの登録と解除

Registering and Unregistering Child Commands

子のコマンドは、RegisterCommandとUnregisterCommandメソッドを使用して、登録、 あるいは、未登録されます。株トレーダーRI(のための)の例では、次のコードの例に示すように、 それぞれの売り買いの注文のためのSubmitとCancelコマンドは、 SubmitAllOrdersとCancelAllOrders複合コマンドで登録されます。 (OrdersControllerクラスを参照してください)。


// OrdersController.cs
commandProxy.SubmitAllOrdersCommand.RegisterCommand(
                        orderCompositeViewModel.SubmitCommand );
commandProxy.CancelAllOrdersCommand.RegisterCommand(
                        orderCompositeViewModel.CancelCommand );

備考

前述のcommandProxyオブジェクトは、静的に定義される送信と取消・複合コマンドにインスタンス・アクセスを提供します。 詳細については、クラス・ファイルStockTraderRICommands.csを参照してください。

Active Childビューの上でコマンドを実行する

Executing Commands on Active Child Views

多くの場合、あなたのアプリケーションは、アプリケーションのUIの内で、子のビューのコレクションを表示する必要があります。 それぞれの子のビューは、順番に、その場所で、対応するView Modelを持っています。1つ以上のコマンドを実装しているかもしれません。 複合コマンドは、配置されるアプリケーションのUIとヘルプ内の子のビューで、どのように、それらが、親ビュー内に実装されるか、 実装されるコマンドを表示するために、使用することができます。これらの筋書きに対応するために、 PrismのCompositeCommandとDelegateCommandクラスは、Prism内で動作するように設計されていました。

Prismの領域(項目、領域(原文リンク)、ユーザー・インターフェイスの構成(原文リンク)で記述される)アプリケーションのUIで、 論理プレースホルダに関連付けられる子のビューのために提供されます。 それらは、多くの場合、子のビューの具体的なレイアウトをそれらの論理プレースホルダ、そして、UI内の位置から、分離するために使用されます。 領域は、特定のレイアウト・コントロールに、添付されている、名前を付けられたプレースホルダに基づいています。

次の図は、それぞれの子のビューが、EditRegionという名前の領域に追加された例を示しています。 そして、UIデザイナーは、その領域の中で、ビューを配置するために、Tabコントロールを使用することを選択しました。

Tabコントロールを使用して、EditRegionを定義する

Tabコントロールを使用して、EditRegionを定義する

Defining the EditRegion using a Tab control

親のビュー・レベルの複合コマンドは、多くの場合、コマンドが子のビュー・レベルで呼び出される座標で使用されます。 場合によっては、あなたは、すべての表示されるビューのためのコマンドが、実行されることを望みます。 Save Allコマンドの例として、以前に解説されたように、他の場合には、あなたは、コマンドが、アクティブ・ビューの上だけで、実行されることを望むでしょう。 この場合、複合コマンドは、アクティブと考えられるビューの上でだけで、子のコマンドを実行します。;それは、アクティブでないビューの上で、子のコマンドを実行しないでしょう。 例えば、それは、次の図に示すように、アプリケーション・ツールバーやリボンの上で、現在、アクティブなアイテムだけを拡大する、Zoomコマンドを実装するといいかもしれません。

Tabコントロールを使用して、EditRegionを定義する

Tabコントロールを使用して、EditRegionを定義する

Defining the EditRegion using a Tab control

この筋書きに対応するために、Prismは、IActiveAwareインターフェイスを提供します。IActiveAwareインターフェイスは、実装者がアクティブであるとき、 trueを返す、IsActiveプロパティを定義し、そして、IsActiveChangedイベントは、アクティブ状態が変更されるたびに発生します。

あなたは、子のビューやView Modelで、IActiveAwareインターフェイスを実装することができます。 それは、主に、領域の中で、子のビューのアクティブな状態を追跡するために使用されます。ビューが、アクティブかどうかは、 特定の領域コントロールの範囲内で、ビューを調整する、領域アダプタで決定されます。 先に示した、Tabコントロールのために、領域アダプタがあります。 例えば、それは、アクティブとして、現在選択されたタブ内のビューを設定します。

また、DelegateCommandクラスは、IActiveAwareインターフェイスを実装しています。CompositeCommandは、 コンストラクタで、monitorCommandActivityパラメータをtrueに指定することによって、 (CanExecuteステータスに加えて)子のDelegateCommandsの有効なステータスを評価するために、 設定することができます。このパラメータが、trueに設定されるとき、CanExecuteメソッドのための戻り値が、決定されるとき、 そして、Executeメソッドの範囲内で、子のコマンドを実行するとき、 CompositeCommandクラスは、それぞれの子のDelegateCommandのアクティブ・ステータスを考慮するでしょう。

monitorCommandActivityパラメータが、trueのとき、CompositeCommandクラスは、次に示す動作を見せます。:

  • CanExecute.
  • すべてのアクティブなコマンドを実行できる場合にのみ、trueを返します。非アクティブである子コマンドは、全く考慮されません。

  • Execute.
  • すべてのアクティブなコマンドを実行します。非アクティブである子コマンドは、全く考慮されません。

あなたは、あなたの子のView Modelで、領域で、子のビューが、アクティブ、あるは、非アクティブになるとき、あなたは、通知される、 IActiveAwareインターフェイスを実装することによって、先程説明された例を実装するために、この機能を使用することができます。 子のビューが、アクティブ状態を変更するとき、あなたは、子のコマンドのアクティブ・ステータスを更新することができます。 続いて、ユーザーが、ズーム複合コマンドを呼び出すとき、子のビューの上のアクティブなZoomコマンドは、呼び出されます。

コレクション内のコマンド

Commands Within Collections

項目のコレクションを表示するとき、ビューの中で、あなたが、多くの場合、遭遇する、他の一般的な筋書きは、 あなたが、それぞれの項目のために、UIを必要とするとき、親のビュー・レベルで、(項目レベルの代わりに)コレクションの中で、コマンドに関連付けられます。

例えば、次の図で示されるアプリケーションにおいて、ビューは、ListBoxコントロールの項目のコレクションを表示します。 そして、それぞれの項目を表示するために使用されるデータ・テンプレートは、Deleteボタンを定義します。 それは、ユーザーが、コレクションから、それぞれの項目を削除できます。

コレクション内の結合コマンド

コレクション内の結合コマンド

Binding commands within collections

View Modelが、Deleteコマンドを実装するため、難問は、それぞれの項目のためのUIに、Deleteボタンを、View Modelで実装されるDeleteコマンドに接続することです。 ListBox内の各々の項目のデータ・コンテクストは、Deleteコマンドを実装する親のView Modelの代わりに、コレクション内の項目を参照するため、困難が発生します。

この問題への1つの方法は、結合が、親のコントロールと比較し、そして、データ・テンプレートと比較しないことを確実に行うために、 ElementNameプロパティを使用して、データ・テンプレートで、親のビューのコマンドに、ボタンを結合することです。次のXAMLは、この技術を説明します。


<Grid x:Name="root">
    <ListBox ItemsSource="{Binding Path=Items}">
        <ListBox.ItemTemplate>
            <DataTemplate>
  <Button Content="{Binding Path=Name}"
          Command="{Binding ElementName=root, Path=DataContext.DeleteCommand}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

データ・テンプレートのボタン・コントロールの内容は、コレクションの中で、項目に関するNameプロパティに結合されます。 しかしながら、ボタンのためのコマンドは、ルート要素のデータ・コンテクストを通じて、Deleteコマンドに結合されます。 これは、ボタンが、項目のレベルの代わりに、親のビュー・レベルで、コマンドに結合できます。 あなたは、コマンドが適用される予定の項目を指定するために、CommandParameterプロパティを使用することができます。 あるいは、あなたは、現在選択された項目を(CollectionViewを通して)操作するために、コマンドを実装することができます。

トリガとコマンドの相互作用

Interaction Triggers and Commands

コマンドへの代わりの方法は、トリガの相互作用とInvokeCommandAction動作のために、Visual Studio 2013のBlendを使用することです。


<Button Content="Submit" IsEnabled="{Binding CanSubmit}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <i:InvokeCommandAction Command="{Binding SubmitCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

この方法は、あなたが、相互作用トリガに接続することができる、どんなコントロールのためにも使用できます。 それは、あなたが、ICommandSourceインターフェースを実装しない、あるいは、あなたが、既定のイベント以外のイベントで、 コマンドを呼び出したいとき、コマンドをコントロールに接続したい場合、特に役に立ちます。 あらためて、あなたが、コマンドのためのパラメータを指定する必要がある場合、 あなたは、CommandParameterプロパティを使用することができます。

続いて、Blend EventTrigger設定を、ListBoxのSelectionChangedイベントで聞き取るために、 どのように使用するかを示します。このイベントが、発生すると、SelectedCommandは、InvokeCommandActionによって呼び出されます。


<ListBox ItemsSource="{Binding Items}" SelectionMode="Single">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <i:InvokeCommandAction Command="{Binding SelectedCommand}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ListBox>

備考

有効なコントロールのコマンドとビヘイビアとの比較

コマンドをサポートするWPFコントロールは、あなたが、コントロールをコマンドに宣言的にフックすることができます。 これらのコントロールは、指定されたコマンドを呼び出します。ユーザーが、特定の方法のコントロールで相互作用する時、 例えば、ユーザーが、ボタンをクリックするとき、Buttonコントロールのためのコマンドが、呼び出されます。 コマンドに関連付けられるこのイベントは、修正と変更ができません。

また、ビヘイビアは、あなたが、宣言型のスタイルで、コントロールをコマンドにフックできます。 しかしながら、動作は、コントロールによって呼び出される、イベントの領域と関連付けられることができます。 そして、それらは、View Model内で、条件つきで関連するコマンド・オブジェクトやコマンド・メソッドの呼び出しを使用することができます。 言い換えると、動作は、有効なコマンドのコントロールとして、多くの同じ筋書きに対処することができます。 そして、それらは、より大きな度合の柔軟性とコントロールを提供するかもしれません。 あなたは、有効なコマンドのコントロールを使用するとき、そして、どの種類の動作を使用するのと同様に、動作を使用するとき、 選択をする必要があります。あなたが、機能によるビューで、View Model、あるいは、一貫性のために、コントロールを関連付けるために、 一つの仕組みを使用することを好む場合、あなたは、コマンドを本質的にサポートするためのコントロールでさえ、動作の使用について考えるかもしれません。

あなたが、View Modelでコマンドを呼び出すために、有効なコマンドのコントロールだけを使用する必要がある場合、 そして、あなたが、コマンドを呼び出すために、既定のイベントで満足している場合、動作は、必要とされないかもしれません。 同様に、あなたの開発者やUIデザイナーが、Visual Studio 2013のためのBlendを使用していない場合、 あなたは、ブレンドの動作に必要な追加の構文のため、 有効なコマンドのコントロール(または、ユーザー定義した添付動作)を好むかもしれません。

コマンドにEventArgsパラメータを渡す

Passing EventArgs Parameters to the Command

あなたが、ビューに配置されるコントロールによって発生するイベントに応じて、コマンドを呼び出す必要があるとき、 あなたは、PrismのInvokeCommandActionを使用することができます。PrismのInvokeCommandActionは、 2つの方法で、Blend SDK内の同じ名前のクラスと異なります。まず、PrismのInvokeCommandActionは、 コマンドのCanExecuteメソッドの戻り値に基づいて、関連するコントロールの使用可能な状態を更新します。 次に、PrismのInvokeCommandActionは、親のトリガから、それに渡されるEventArgsパラメータを使用します。 CommandParameterが、設定されていない場合、関連するコマンドにそれを渡します。

時には、あなたは、EventTriggerからEventArgsのような、親のトリガから来る、 コマンドにパラメータを渡す必要があります。その筋書きでは、 あなたは、BlendのInvokeCommandAction動作を使用することができません。

次のコードでは、あなたは、PrismのInvokeCommandActionが、TriggerParameterPathを呼び出すプロパティ持っていることを、見ることができます。 それは、コマンド・パラメータとして渡されるパラメータの(ひょっとしたら入れ子にされた)メンバーを指定するために、使用されます。 次の例では、SelectionChanged EventArgsのAddedItemsプロパティは、SelectedCommandコマンドに渡されます。


<ListBox Grid.Row="1" Margin="5" ItemsSource="{Binding Items}" SelectionMode="Single">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <!-- This action will invoke the selected command in the view model and pass the parameters of the event to it. -->
	   <!--この動作は、View Modelで選択されたコマンドを呼び出し、それにイベントのパラメータを渡します。-->
            <prism:InvokeCommandAction Command="{Binding SelectedCommand}" TriggerParameterPath="AddedItems" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ListBox>

非同期相互作用を扱う

Handling Asynchronous Interactions

あなたのView Modelは、多くの場合、同期する代わりに、非同期で情報をやりとりする、あなたのアプリケーション内で、 サービスとコンポーネントと相互作用する必要があります。あなたが、Webサービスと相互作用する場合、 あるいは、ネットワークの上の他のリソースあるいは、あなたのアプリケーションが、計算やI/Oを実行するために、バックグラウンド処理を使用する場合、これは、特に、当てはまります。 これらの操作が、非同期で実行されることを確認すると、あなたのアプリケーションは、応答を維持します。それは、優れたユーザー・エクスペリエンスを提供するために重要です。

ユーザーが、非同期要求やバックグラウンド処理を始動するとき、応答がいつ現れるか(あるいは、たとえそれが現れるとしても)予測することが、難しいです。 そして、極めて頻繁に、それは、スレッドの上に戻るでしょう。UIは、UIスレッドだけで更新することができるため、 あなたは、UIスレッドに要求を送ることによって、頻繁に、UIを更新する必要があります。

データを取り出し、Webサービスで相互作用する

Retrieving Data and Interacting with Web Services

Webサービスや他のリモート・アクセス技術で、相互作用するとき、あなたは、頻繁に、IAsyncResultパターンに遭遇します。 このパターンでは、(GetQuestionnaireのような)メソッドを呼び出す代わりに、 あなたは、BeginGetQuestionnaireとEndGetQuestionnaireの一組のメソッドを使用します。 あなたが、BeginGetQuestionnaireを呼び出す、非同期呼び出しを開始するために、結果と決定を取得するために、 対象とするメソッドを呼び出すとき、例外がある場合、呼び出しが完了しているとき、あなたは、EndGetQuestionnaireを呼び出します。

EndGetQuestionnaireを呼び出すとき、決定するために、あなたは、BeginGetQuestionnaireの呼び出しの間、 完了のためのを得る、あるいは、(おそらく)コールバックを指定することができます。 ここに、示されるように、対象とするメソッドの実行が、完了しているとき、コールバックの方法で、 あなたのコールバック・メソッドは、呼び出され、あなたは、それは、EndGetQuestionnaireを呼び出すことができます。:


IAsyncResult asyncResult = this.service.BeginGetQuestionnaire(GetQuestionnaireCompleted, null // object state, not used in this example);

private void GetQuestionnaireCompleted(IAsyncResult result)
{
   try
   {
     questionnaire = this.service.EndGetQuestionnaire(ar);
   }
   catch (Exception ex)
   {
     // Do something to report the error.
     // エラーを報告するために、何かします。
   }
}

それは、Endメソッドの呼び出し(この場合、EndGetQuestionnaire)のなかで、注意することが重要です。 要求が実行される間、発生した、どんな例外でも、発生します。あなたのアプリケーションは、これらを取り扱う必要があり、 そして、UIによってスレッドを安全な方法で、それらに、報告する必要があるかもしれません。 あなたが、これらを取り扱わない場合、スレッドは、終了し、そして、あなたは、結果を処理することができないでしょう。

応答は、通常、UIスレッドではないため、あなたは、UIに状態に影響を及ぼす何かを修正する予定がある場合、 あなたは、スレッド・ディスパッチャ、あるいは、SynchronizationContextオブジェクトを使用して、 UIスレッドに応答を送る必要があるでしょう。WPFでは、あなたは、一般に、ディスパッチャを使用します。

次のコードの例では、Questionnaireオブジェクトは、非同期で取り出されます。 そして、その次に、それは、QuestionnaireViewのための、データ・コンテクストとして設定されます。 あなたは、あなたが、UIスレッドにいるかどうか、ディスパッチャのCheckAccessメソッドを使用することができます。 そうでない場合は、あなたは、要求をUIスレッドで実行するために、BeginInvokeメソッドを使用する必要があります。


var dispatcher = System.Windows.Deployment.Current.Dispatcher;
if (dispatcher.CheckAccess())
{
    QuestionnaireView.DataContext = questionnaire;
}
else
{
    dispatcher.BeginInvoke(
          () => { Questionnaire.DataContext = questionnaire; });
}

Model-View-ViewModelの参考になる実装(MVVM RI)は、前述の例に同じような、 IAsyncResultに基づくサービス・インターフェイスを利用する方法の例を示します。 また、それは、単純なコールバックの仕組みを提供するために、利用者のために、サービスをラップし、 そして、呼び出したスレッドに、コールバックを送る処理をします。 例えば、次のコードの例は、アンケ-卜用紙の検索を示します。


this.questionnaireRepository.GetQuestionnaireAsync(
    (result) =>
    {
        this.Questionnaire = result.Result;
    });

返された結果のオブジェクトは、発生する可能性があるエラーに加えて、取り出された結果をラップします。 次のコードの例は、エラーが、どのように、評価されるかを示します。


this.questionnaireRepository.GetQuestionnaireAsync(
    (result) =>
    {
        if (result.Error == null) {
          this.Questionnaire = result.Result;
          ...
        }
        else
        {  
          // Handle error. 
        }
    })

ユーザーとの対話処理パターン

User Interaction Patterns

しばしば、アプリケーションは、ユーザーに、イベントの発生を通知する、 あるいは、操作を続行する前に、確認を要求する必要があります。 これらの相互作用は、多くの場合、アプリケーションで、単純に、それらに変更を知らせるために、 あるいは、それらから、単純な応答を得るために、設計された簡潔な相互作用です。 これらの相互作用のいくつかは、ユーザーにとって、ダイアログボックスやメッセージボックスを表示する、 あるいは、それらが、ユーザーに非モーダルに見えるかもしれない時のように、トースト通知、 あるいは、ポップアップウィンドウが表示されるときのような、モーダルに見えるかもしれません。

これらの場合、相互作用するための複数の方法が、ユーザーにありますが、 この方法で、MVVMに基づいたアプリケーションをそれらに実装することは、明確に分離した関係を保つことが、困難な場合があります。 例えば、非MVVMアプリケーションでは、あなたは、多くの場合、UIのコード・ビハインド・ファイルで、 単純にユーザーに応答を促すために、MessageBoxクラスを使用するでしょう。 MVVMアプリケーションでは、ViewとView Modelの間で、それが関係の分離を壊すため、これは適切でありません。

MVVMパターンの観点から、View Modelは、ユーザーと相互作用を開始するための、そして、どんな応答でも、 利用し、そして、処理するための役割を果たします。ユーザーが使用することで、実際に、相互作用を管理するための役割を果たします。 どんな、ユーザー・エクスペリエンスでも適切です。 View Modelに、実装されるプレゼンテーション・ロジック間の関係の分離を維持する。 そして、ユーザー・エクスペリエンスは、ビューによって、実装される。テスタビリティと柔軟性を改善するのを助けます。

MVVMパターンのこれらの種類のユーザーとの相互作用を実装するための2つの一般的な方法があります。 1つの方法は、ユーザーと相互作用を開始する、View Modelによって使用することができるサービスを実装することです。 それによって、ビューの実装で、その独立を維持します。他の方法は、ユーザーと相互作用する目的を表すView Modelで、 これらのイベントに結合し、そして、相互作用の視覚的な測面を管理する、 ビューのコンポーネントと一緒に、呼び出したイベントを使用します。 これらのそれぞれの方法は、次のセクションで説明されています。

相互作用サービスを使用する

Using an Interaction Service

この方法では、View Modelは、メッセージボックスを通して、ユーザーと相互作用を開始するために、相互作用サービスコンポーネントに依存しています。 この方法は、別々のサービスコンポーネントの相互作用で、視覚的な実装のカプセル化による、関係の明確な分離とテスタビリティをサポートします。 一般的に、View Modelは、相互作用サービス・インターフェイス上で依存関係を持っています。 それは、頻繁に、依存関係注入やサービス・ロケーターを通じて、参照を、相互作用サービスの実装に取得します。

View Modelが、相互作用サービスに参照を取得したあと、それは、プログラム上で、必要があるときは、ユーザーとの相互作用を要求することができます。 次の図に示すように、相互作用サービスは、相互作用の視覚的な測面を実装しています。 View Modelで、インターフェイス参照を使用することは、ユーザー・インターフェイスの実装要件によって、使用されるための異なる実装を提供します。 例えば、WPFのための相互作用サービスの実装は、アプリケーションのプレゼンテーション・ロジックを、さらに再使用できます。

相互作用サービスは、相互作用の視覚的な測面を実装しています。

次のコードの例に示すように、ユーザーModalインタラクションで相互作用するために、実行が進行することができる前に、特定の応答を得るために、 ユーザーは、モーダル・ポップアップウィンドウやMessageBoxが存在する場所のような、 相互作用サービスを使用して、ブロッキング・メソッド呼び出しを使用して、同期方法で実装することができます。


var result =
    interactionService.ShowMessageBox(
        "Are you sure you want to cancel this operation?", 
        "Confirm", 
        "あなたは、あなたが、この操作をキャンセルしたいと確認しますか?",
        "確認します",
        MessageBoxButton.OK );
if (result == MessageBoxResult.Yes)
{
    CancelRequest();
}

しかしながら、この方法の1つの欠点は、それが同期プログラミングモデルを強要することです。 代わりの非同期実装は、View Modelのために、相互作用が完了する際に実行するためのコールバックを提供できます。 次のコードは、この方法を説明します。


interactionService.ShowMessageBox(
    "Are you sure you want to cancel this operation?",
    "Confirm",
        "あなたは、あなたが、この操作をキャンセルしたいと確認しますか?",
        "確認します",
    MessageBoxButton.OK,
    result =>
    {
        if (result == MessageBoxResult.Yes)
        {
            CancelRequest();
        }
    });

相互作用サービスを実装するとき、非同期の方法は、実装するために、 モーダルと非モーダル相互作用を提供することによって、さらに大きな柔軟性を提供します。 例えば、WPFでは、MessageBoxクラスは、ユーザーと本当のモーダル相互作用を実装するために、使用することができます。

対話要求オブジェクトを使用する

Using Interaction Request Objects

MVVMパターンで、簡単に、ユーザーとの対話処理を実装する他の方法は、 View Modelが、対話要求オブジェクトとビュー内の動作の結合を通して、 ビューそれ自身に対話要求ディレクトリを作成できます。対話要求オブジェクトは、対話要求の内容、 そして、その応答、そして、ビューイベントを経由した情報のやりとりをカプセル化します。 ビューは、相互作用のユーザー・エクスペリエンス部分を開始するために、これらのイベントに登録します。 次の図に示すように、ビューは、一般的に、相互作用のユーザー・エクスペリエンスを、動作にカプセル化します。 それは、View Modelにより提供される対話要求オブジェクトにデータ結合されています。

ユーザーと相互作用するために対話要求オブジェクトを使用する

ユーザーと相互作用するために対話要求オブジェクトを使用する

Using an interaction request object to interact with the user

この方法は、単純な(さらに、柔軟な)View ModelとViewの間で明確な分離を維持する仕組みを提供します。- ビューを、相互作用の視覚的な測面に、完全に、カプセル化している間、 View Modelは、どんな、必要とされるユーザーとの対話処理でも含まれている、アプリケーションのプレゼンテーション・ロジックをカプセル化できます。 期待されるViewを通してのユーザーとの相互作用が含まれているView Modelの実装は、簡単に、テストできます。 そして、UIデザイナーは、相互作用のために、異なるユーザー・エクスペリエンスをカプセル化する、 異なる動作の使用を通して、ビューの中で相互作用を、どのように実装するか選択する、多くの柔軟性を持っています。

この方法は、View Modelを監視する、そのビューの状態変化を反映でき、 そして、2つのデータの通信の間の双方向のデータ結合を使用して、 MVVMパターンと同一です。対話要求オブジェクト内の相互作用の非ビジュアル要素のカプセル化、 そして、相互作用の対応する動作の使用を管理する視覚的な要素は、 コマンド・オブジェクトの方法に極めて類似しています。そして、コマンドの動作は使用されます。

このアプローチは、Prismで採用されたアプローチです。 Prismライブラリは、IInteractionRequestなインターフェイスとInteractionRequest<T>クラスを通して、 直接、このパターンをサポートしています。IInteractionRequestインターフェイスは、相互作用を開始するイベントを定義します。 ビューの動作は、このインターフェイスを結合し、そして、それが公開するイベントに登録します。InteractionRequest<T>クラスは、 IInteractionRequestインターフェイスを実装しています。 そして、View Modelを提供するために、相互作用を開始するために、そして、要求のために、 そして、必要に応じて、コールバック・デリゲートで、コンテクストを指定するために、2つのRaiseメソッドを定義します。

View modelから対話要求を開始する

Initiating Interaction Requests from the View Model

InteractionRequestクラスは、対話要求の間のビューとView Modelの相互作用を調整します。 Raiseメソッドは、View Modelが、相互作用を開始し、そして、コンテクスト・オブジェクト(T型の)と相互作用が完全なものになったあと、 呼び出されるコールバック・メソッドを指定できます。コンテクスト・オブジェクトは、 ユーザーと相互作用の間使用されるViewに、データと状態を渡すために、View Modelを提供します。 コールバック・メソッドが、指定される場合、コンテクスト・オブジェクトは、View Modelに戻されます。; これは、View Modelに戻される相互作用の間に、ユーザーが、どんな変更も作成できます。


public interface IInteractionRequest
{
    event EventHandler<InteractionRequestedEventArgs> Raised;
}
 
public class InteractionRequest<T> : IInteractionRequest
    where T : INotification
{
    public event EventHandler<InteractionRequestedEventArgs> Raised;
 
    public void Raise(T context)
    {
        this.Raise(context, c => { });
    }

    public void Raise(T context, Action<T> callback)
    {
        var handler = this.Raised;
        if (handler != null)
        {
            handler(
                this, 
                new InteractionRequestedEventArgs(
                    context, 
                    () => { if (callback != null) callback(context); } ));
        }
    }
}

Prismは、一般的な対話要求の筋書きをサポートしている、定義済みのコンテキスト・クラスを提供しています。INotificationインターフェイスは、 すべてのコンテキスト・オブジェクトのための使用されます。それは、アプリケーションにおいて、ユーザーに重要なイベントを通知するために、 対話要求が使用されるとき、使用されます。それは、2つのプロパティを提供します。-タイトルと内容-それは、ユーザーに表示されます。一般的に、通知は一方向です。 このように、それは、ユーザーが、相互作用の間、これらの値を変更するかもしれないことを予期していません。 Notificationクラスは、このインターフェイスの既定の実装です。

IConfirmationインターフェイスは、INotificationインターフェイスを拡張して、第3のプロパティを追加します。 -確認します-それは、ユーザーが、操作を確認、あるいは、否定することを示すために使用されます。IConfirmationの実装により提供されたConfirmationクラスは、 ユーザーが、ユーザーからyes/noの応答を取得したい場所、MessageBoxスタイルの相互作用を実装するために使用されます。あなたは、どんなデータでもカプセル化するために、 INotificationインターフェイスを実装している、ユーザー定義したコンテクスト・クラスを定義することができます。 そして、あなたが必要とする状態は、相互作用をサポートしています。

InteractionRequest<T>クラスを使用するために、View Modelクラスは、InteractionRequest<T>クラスのインスタンスを作成するでしょう。 そして、それに、データ結合するビューを提供するために、読取専用プロパティを定義します。 View Modelが、要求を開始したいとき、それは、Raiseメソッドを呼び出し、コンテクスト・オブジェクト、そして、必要に応じて、コールバック・デリゲートに渡します。


public InteractionRequestViewModel()
{
    this.ConfirmationRequest = new InteractionRequest<IConfirmation>();
    …
    // Commands for each of the buttons. Each of these raise a different interaction request.
    //各々のボタンに命令します。
    // これらの各々は、異なる対話要求を発生させます。
    this.RaiseConfirmationCommand = new DelegateCommand(this.RaiseConfirmation);
    …
}

public InteractionRequest<IConfirmation> ConfirmationRequest { get; private set; }

private void RaiseConfirmation()
{
    this.ConfirmationRequest.Raise(
        new Confirmation { Content = "Confirmation Message", Title = "Confirmation" },
        c => { InteractionResultMessage = c.Confirmed ? "The user accepted." : "The user cancelled."; });
    }
}

インタラクティブ性のクイックスタートは、IInteractionRequestインターフェイスとInteractionRequest<T>クラスが、 ViewとView Modelの間で、ユーザー対話処理の実装を、どのように使用するかを説明します。(InteractionRequestViewModel.csを参照してください)。

相互作用ユーザー・エクスペリエンスを実装する動作を使用する

Using Behaviors to Implement the Interaction User Experience

対話要求オブジェクトが、論理的な相互作用を表すため、相互作用のための正確なユーザー・エクスペリエンスは、ビューで定義されます。動作は、多くの場合、 相互作用のためのユーザー・エクスペリエンスをカプセル化するために、使用されます。;これは、UIデザイナーが、適切な動作を選択し、 そして、View Modelで、それを対話要求オブジェクトに結合することができます。

ビューは、対話要求イベントを見つけるために、そして、その次に、要求のための適切な視覚的な表示を表現するために、設定する必要があります。 トリガは、特定のイベントが発生するたびに、動作を開始するために使用されます。

公開される対話要求オブジェクトへの結合で、View Modelで、Blendにより提供される標準的なEventTriggerは、 対話要求イベントを監視するために使用することができます。 しかしながら、Prismライブラリは、InteractionRequestTriggerという名前のユーザー定義したEventTriggerを定義します。 それは、自動的に、適切な、IInteractionRequestインターフェイスのRaisedイベントを結合します。 これは、必要なExtensible Application Markup Language (XAML)の量を減らします。 そして、不注意で、誤ったイベント名を入力する可能性を減らします。

イベントが呼び出されたあと、InteractionRequestTriggerは、指定された動作を呼び出します。 WPFのために、Prismライブラリは、 PopupWindowActionクラスを提供します。それは、ユーザーにポップアップウィンドウを表示します。ウィンドウが表示されるとき、 そのデータ・コンテクストは、対話要求のコンテクスト・パラメータに設定されます。PopupWindowActionクラスのWindowContentプロパティを使用して、 あなたは、ポップアップウィンドウで、表示されるビューを指定することができます。 ポップアップウィンドウのタイトルは、コンテクスト・オブジェクトのTitleプロパティに結合されます。

備考

既定では、PopupWindowActionクラスで表示されるポップアップウィンドウの特定の型は、 コンテクスト・オブジェクトの型に依存します。Notificationコンテキスト・オブジェクトのための、DefaultNotificationWindowは表示され、 Confirmationコンテキスト・オブジェクトのためのある間、DefaultConfirmationWindowは表示されます。DefaultNotificationWindowは、 通知を表示する単純なポップアップウィンドウを表示します。また、DefaultConfirmationWindowが、 ユーザーの応答を取り込むために、AcceptとCancelボタンを含む間、 PopupWindowActionクラスのWindowContentプロパティを使用して、 ユーザー定義したポップアップウィンドウを指定することによって、あなたは、この動作を上書きすることができます。

次の例は、InteractionRequestTriggerとPopupWindowActionが、インタラクティブ性クイックスタート内で、 ユーザーに、確認ポップアップウィンドウを表示するために、どのように使用するか、示します。


<i:Interaction.Triggers>
    <prism:InteractionRequestTrigger SourceObject="{Binding ConfirmationRequest, Mode=OneWay}">
        <prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True"/>
    </prism:InteractionRequestTrigger>
</i:Interaction.Triggers>

備考

PopupWindowActionは、3つの重要なプロパティを持っています。IsModal、それが、trueに設定されるとき、 モーダルにするためのポップアップを設定します。;CenterOverAssociatedObject、 それは、trueに設定されると、親のウィンドウに中央揃えされるポップアップを表示します。 最後に、WindowContentプロパティは、指定されません。したがって、DefaultConfirmationWindowが、示されるでしょう。

PopupWindowActionは、DefaultNotificationWindowのデータ・コンテクストとして、 NotificationオブジェクトのContentプロパティを表示する、Notificationオブジェクトを設定します。 ユーザーが、ポップアップ・ウィンドウを閉じた後、コールバック・メソッドによって、どんな更新された値でも、 一緒に、コンテクスト・オブジェクトは、View Modelに戻されます。インタラクティブ性クイックスタートの確認の例で、 OKボタンがクリックされると、DefaultConfirmationWindowは、 trueに指定されたConfirmationオブジェクトで、Confirmedプロパティを設定するための役割を果たします。

異なるトリガと動作は、他の相互作用の仕組みをサポートするために、定義することができます。 PrismのInteractionRequestTriggerとPopupWindowActionクラスの実装は、 あなた自身のトリガと動作の開発のための基盤として使用することができます。

高度な構築と接続

Advanced Construction and Wire-Up

うまくMVVMパターンを実装するために、あなたは、適切なクラスで、あなたのアプリケーションのコードを実装することができるようにするため、 完全にView, Model, and ViewModelクラスの役割を理解する必要があります。また、相互作用するために、 (データ結合、コマンド、対話要求、等々を通して)これらのクラスが提供する正しいパターンを実装することは、重要な必要条件です。 最後の手順は、View、View ModelとModelクラスが、実行時に、互いに、どのようにインスタンスを生成し、関連づけるか考えます。

この手順を管理するために、適切な戦略を選択ことは、特に重要です。 あなたが、あなたのアプリケーションの依存関係注入コンテナを使用している場合、 拡張管理フレームワーク(MEF)とUnityアプリケーション・ブロック(Unity)の両方は、View、View ModelとModelクラスの間で、 そして、実行時に、コンテナで、それらを実現しておくために、指定する依存関係の機能を提供します。

一般的に、あなたは、(コンテナを使用して)自動的に、必要とされるView Modelのインスタンスを生成するViewが構築されるときのように、 Viewの依存関係として、View Modelを定義します。 順番に、また、View Modelに依存するどんなコンポーネントやサービスでも、コンテナで、インスタンスを生成するでしょう。 View Modelが、うまくインスタンスを生成したあと、ビューは、続いて、そのデータ・コンテクストとして設定します。

MEFを使用して、ViewとView modelを作成する

Creating the View and View Model Using MEF

MEFを使用して、あなたは、インポート属性を使用して、View ModelのViewの依存関係を指定することができます。 そして、あなたは、エクスポート属性によってインスタンスを生成する、 あなたは、プロパティを経たビュー経由して、または、コンストラクタ引数としてView Modelをインポートすることができますどちらか、 具体的なView Model型を指定することができます。

例えば、StockTraderの参考になる実装のShellビューは、View Modelのために、 インポート属性と共に、書き込み専用プロパティを宣言します。ビューが、インスタンスを生成するとき、 MEFは、適切なエクスポートされたView Modelのインスタンスを作成し、プロパティ値を設定します。 ここに、示されるように、プロパティ・セッターは、ビューのデータ・コンテクストとして、View Modelを割り当てます。:


[Import]
ShellViewModel ViewModel
{
    set { this.DataContext = value; }
}

ここに、示されるように、View Modelは定義され、エクスポートされます。:


[Export]
public class ShellViewModel : BindableBase
{
    ...
}

ここに、示されるように、他のアプローチは、ビュー上で、インポート・コンストラクタを定義することです。:


public Shell()
{
     InitializeComponent();
}

[ImportingConstructor]
public Shell(ShellViewModel viewModel) : this()
{
    this.DataContext = viewModel;
}

View Modelは、続いて、MEFでインスタンスを生成し、引数として、ビューのコンストラクタに渡されます。

備考

あなたは、MEFとUnityの両方で、プロパティ注入やコンストラクタ注入を使用することができます。; しかしながら、あなたは、2つのコンストラクタを維持する必要がないため、あなたは、プロパティ注入が、より単純なことを見つけるかもしれません。 Visual StudioとExpression Blendのような、コントロールを必要とする設計時のツールは、デザイナーで、それらを表示するために、 既定のパラメータのないコンストラクタを持っています。あなたが定義する、どんな追加されたコンストラクタでも、 ビューは、InitializeComponentメソッドによって、適切に初期化することができるため、 既定のコンストラクタが呼び出されることを確認する必要があります。

Unityを使用して、ViewとView modelを作成する

Creating the View and View Model Using Unity

あなたの依存関係注入コンテナとして、Unityを使用することは、MEFを使用することに似ています。 そして、プロパティに基づいた、そして、コンストラクタに基づいた注入の両方は、サポートされています。 主要な違いは、一般的に、実行時に、型が無条件に発見されないということです。; その代わりに、それらは、コンテナで登録する必要があります。

一般的に、あなたは、View Modelでインターフェイスを定義し、それで、View Modelの特定で具体的な型は、 Viewから分離できます。ここに、示されるように、例えば、Viewは、コンストラクタ引数を経由し、View Modelのその依存関係を定義することができます。:


public Shell()
{
    InitializeComponent();
}

public Shell(ShellViewModel viewModel)
: this()
{
    this.DataContext = viewModel;
}

備考

Visual StudioとVisual Studio 2013のためのBlendのような、既定のパラメータのないコンストラクタは、 設計時のツールで動作するために、Viewを提供することが必要です。


public Shell()
{
    InitializeComponent();
}

[Dependency]
public ShellViewModel ViewModel
{
    set { this.DataContext = value; }
}

ここに、示されるように、View Model型は、Unityコンテナで登録されます。:


IUnityContainer container;
container.RegisterType<ShellViewModel>();

ここに、示されるように、ビューは、続いて、コンテナを通して、インスタンスを生成することができます。:


IUnityContainer container;
var view = container.Resolve<Shell>();

外部クラスを使用して、ViewとView modelを作成する

Creating the View and View Model Using an External Class

多くの場合、あなたは、ViewとView Modelクラスのインスタンス生成を統合するための、 コントローラやサービス・クラスを定義するために、それを見つけるでしょう。 この方法は、MEFやUnityのような、あるいは、Viewが、その必要なView Modelを明示的に作成するとき、 依存関係注入コンテナで使用することができます。

あなたのアプリケーションで、ナビゲーションを実装するとき、このアプローチは、特に役に立ちます。 この場合、コントローラは、UIのプレースホルダ・コントロールや領域に関連付けられています。 そして、それは、そのプレースホルダや領域を経由して、構築とビューの配置を調整します。

例えば、サービス・クラスは、コンテナを使用して、ビューを構築するために使用でき、 そして、メインページ内で、それらを表示します。 この例では、ビューは、ビューの名前によって指定されます。この単純な例で示すように、 ナビゲーションは、UIサービス上で、ShowViewメソッドの呼び出しを経由して、開始されます。


private void NavigateToQuestionnaireList()
{
    // Ask the UI service to go to the "questionnaire list" view.
    // 「アンケ-卜用紙リスト」ビューに移動するために、UIサービスに要求します。
    this.uiService.ShowView(ViewNames.QuestionnaireTemplatesList);
}

UIサービスは、アプリケーションのUIで、プレースホルダ・コントロールに関連付けられます。;それは、必要とされるビューの作成をカプセル化します。 そして、UIで、その外観を調整します。UIServiceのShowViewは、コンテナを経由して、ビューのインスタンスを作成します。 (そのため、そのView Modelと他の依存関係は、実現することができます)そして、その次に、ここに、示されるように、それを、適切な場所に表示します。:


public void ShowView(string viewName)
{
    var view = this.ViewFactory.GetView(viewName);
    this.MainWindow.CurrentView = view;
}

備考

Prismは、領域の中のナビゲーションのために、広範囲なサポートを提供します。 領域ナビゲーションが、使用する仕組みは、前述のアプローチに極めて似ています。 ただし、領域マネージャは、インスタンス生成と特定の領域のビューの配置を調整する責任があります。 詳細については、ナビゲーション内のビューに基づいたナビゲーションの項目を参照してください。

MVVMアプリケーションをテストする

Testing MVVM Applications

MVVMアプリケーションからModelとViewModelをテストすることは、他のクラスと同じツールと技術をテストすることと同じです。 -ユニット・テストとモックアップのフレームワークのような、-使用することができます。 しかしながら、いくつかのテスト・パターンは、ModelとViewModelクラス、そして、標準的なテストの技術の恩恵を得ることができます。 そして、ヘルパー・クラスをテストするのが一般的です。

INotifyPropertyChangedの実装をテストする

Testing INotifyPropertyChanged Implementations

INotifyPropertyChangedインターフェイスを実装することは、Viewが、ModelとViewModelから生じる変更に反応できます。 これらの変更は、コントロールで示されるドメイン・データに限定されません。; また、それらは、View Modelの状態のような、Viewを制御するために使用されます。 アニメーションが開始される、あるいは、コントロールが無効にされることが原因です。

簡単な事例

Simple Cases

プロパティは、テスト・コードをテストすることによって、イベントハンドラをPropertyChangedイベントに添付することによって、 直接、更新することができます。そして、プロパティのために、新しい値が設定された後、イベントが発生するかどうか、確認します。 PropertyChangeTrackerクラスのような、ヘルパー・クラスは、ハンドラを接続し、そして、結果を集めるために使用できます。; テストを記述するとき、これは反復的なタスクを避けます。次のコードの例は、この種のヘルパー・クラスを使用して、テストを示します。


var changeTracker = new PropertyChangeTracker(viewModel);

viewModel.CurrentState = "newState";

CollectionAssert.Contains(changeTracker.ChangedProperties, "CurrentState");

一般的に、テストする必要がない、モデル・デザイナーで作成されるコードのような、 コード生成プロセスの結果として得られるプロパティは、 INotifyPropertyChangedインターフェイスが実装されていることを保証します。

計算された、そして、設定できないプロパティ

Computed and Non-Settable Properties

プロパティが、テスト・コードを設定できないとき、-非公開セッターと一緒のプロパティや読取専用、計算されたプロパティのような、 -テスト中でオブジェクトを刺激するテスト・コードの必要性は、プロパティとその対応する通知において変更をさせます。 しかしながら、テストの構造は、より単純な事例のそれと同じです。 次のコードの例に示すように、モデル・オブジェクトの変更が変更するView Modelでプロパティをさせるところ。


var changeTracker = new PropertyChangeTracker(viewModel);

var question = viewModel.Questions.First() as OpenQuestionViewModel;
question.Question.Response = "some text";

CollectionAssert.Contains(changeTracker.ChangedProperties, "UnansweredQuestions");

全部のオブジェクト通知

Whole Object Notifications

あなたが、INotifyPropertyChangedインターフェイスを実装するとき、 それは、オブジェクトのために、オブジェクトのすべてのプロパティが変更されるかもしれない、 変更されたプロパティの名前を示すために、nullや空の文字列でPropertyChangedイベントを発生させることができます。 これらの事例は、ちょうど、それぞれのプロパティの名前に通知する事例のように、テストすることができます。

INotifyDataErrorInfoの実装をテストする

Testing INotifyDataErrorInfo Implementations

IDataErrorInfoインターフェイスを実装する、そして、INotifyDataErrorInfoインターフェイスを実装するプロパティが設定する例外を投げるような、 結合を有効にするために、入力の確認を実行するために、利用できるいくつかの仕組みがあります。INotifyDataErrorInfoインターフェイスを実装することは、はるかな洗練を提供します。 なぜなら、それは、プロパティごとに、複数のエラーと非同期の実行、そして、プロパティ間の妥当性検証をサポートします。;このように、また、それは最も多くのテストを必要とします。

INotifyDataErrorInfoの実装を検証することは、2つの測面があります。:GetErrorsメソッドのための結果が異なって、 満たされる時、ErrorsChangedイベントを発生させることのような、妥当性検証規則が正しく実装されている、 そして、インターフェイスの実装のための必要条件がテストされていることをテストします。

妥当性検証規則をテストする

Testing Validation Rules

妥当性検証ロジックは、一般的に、出力が、入力に依存する場所の自己完結型のプロセスであるため、通常、テストすることが簡単です。 関連するそれぞれプロパティと妥当性検証規則のために、GetErrorsメソッドと有効な値、無効な値、境界値、 等々のための検証されたプロパティ名を呼び出す結果を検証する必要があります。妥当性検証ロジックが共有される場合、 宣言的にデータの注釈の妥当性検証属性を使用する、妥当性検証規則を表すように、 より徹底的なテストは、共有された妥当性検証ロジックに集中することができます。 一方で、ユーザー定義した妥当性検証規則は、完全に検証する必要があります。


// Invalid case
var notifyErrorInfo = (INotifyDataErrorInfo)question;

question.Response = -15;

Assert.IsTrue(notifyErrorInfo.GetErrors("Response").Cast<ValidationResult>().Any());

// Valid case
var notifyErrorInfo = (INotifyDataErrorInfo)question;

question.Response = 15;
Assert.IsFalse(notifyErrorInfo.GetErrors("Response").Cast<ValidationResult>().Any());

プロパティ間の妥当性検証規則は、同じパターンに準拠しています。 一般的に、異なるプロパティの値の組合せに対応するために、より多くの検証を必要としています。

INotifyDataErrorInfoの実装のための必要条件をテストする

Testing the Requirements for INotifyDataErrorInfo Implementations

GetErrorsメソッドのための正しい値を生成する以外に、INotifyDataErrorInfoインターフェイスの実装は、ErrorsChangedイベントが、 GetErrorsのための結果が、いつ異なるかのような、適切に発生することを確認する必要があります。 さらに、HasErrorsプロパティは、インターフェイスを実装するオブジェクトのすべてのエラーの状態を反映する必要があります。

INotifyDataErrorInfoインターフェイスを実装することは、必須の方法ではありません。 しかしながら、実装は、妥当性検証エラーを格納するオブジェクトに依存します。 そして、必要な通知が実行されることは、一般的に、好まれます。なぜなら、それらは、テストするのが、より簡単です。 このような理由で、それは、確かめる必要がありません。INotifyDataErrorInfoインターフェイスのすべてのメンバーのための必要条件は、 それぞれの検証されたプロパティの上で、それぞれの妥当性検証規則のために満たされています。 (もちろん、エラー管理オブジェクトが適切に検証されている限り)。

インターフェイスの必要条件を検証することは、少なくとも次に示す検証が含まれる必要があります。:

  • HasErrorsプロパティは、オブジェクトの全体的なエラーの状態を反映します。 他のプロパティが、それでも、無効な値を持つ場合、前もって、無効なプロパティを有効な値に設定することは、 このプロパティの変更の結果になりません。
  • GetErrorsメソッドのための結果の変更によって、反映されるように、プロパティの変更がエラー状態のとき、 ErrorsChangedイベントは、発生します。エラー状態の変更は、有効な状態(つまり、エラーでない)から無効な状態、 そして、逆もまた同様に、移行することができます。 あるいは、それは、無効な状態から異なる無効な状態へ移行できます。GetErrorsのための更新された結果は、 ErrorsChangedイベントのハンドラのために利用できます。

INotifyPropertyChangedインターフェイスのための実装をテストするとき、MVVMサンプル・プロジェクトのNotifyDataErrorInfoTestHelperクラスのような、 ヘルパー・クラスは、反復的な管理操作と標準的な検査を取り扱うことによって、通常、より簡単なINotifyDataErrorInfoインターフェイスの実装のために、書き込みテストを作成します。 いくつかの種類の再使用できるエラー・マネージャに依存することなく、インターフェイスが実装されるとき、それらは、とても便利です。次のコードの例は、この種のヘルパー・クラスを示します。


var helper = 
    new NotifyDataErrorInfoTestHelper<NumericQuestion, int?>(
        question, 
        q => q.Response);

helper.ValidatePropertyChange(
    6, 
    NotifyDataErrorInfoBehavior.Nothing);
helper.ValidatePropertyChange(
    20, 
    NotifyDataErrorInfoBehavior.FiresErrorsChanged 
    | NotifyDataErrorInfoBehavior.HasErrors 
    | NotifyDataErrorInfoBehavior.HasErrorsForProperty);
helper.ValidatePropertyChange(
    null,
    NotifyDataErrorInfoBehavior.FiresErrorsChanged
    | NotifyDataErrorInfoBehavior.HasErrors
    | NotifyDataErrorInfoBehavior.HasErrorsForProperty);
helper.ValidatePropertyChange(
    2,
    NotifyDataErrorInfoBehavior.FiresErrorsChanged);

非同期サービスコールをテストする

Testing Asynchronous Service Calls

MVVMパターンを実装するとき、View Modelは、通常、多くの場合、非同期で、サービスに関する操作を呼び出します。 実際のサービスのための代替として、コードのためのテストは、一般的に、モックやスタブを使用する、これらの操作を呼び出します。

非同期動作を実装するために使用される、標準パターンは、そのステータスの操作が発生するための通知のスレッドに関して異なる保証を提供します。 それにもかかわらず、イベントに基づいた非同期デザインパターンは、イベントが、アプリケーションのために適切な、スレッドの上で呼び出されるためのハンドラを保証します。 IAsyncResultデザインパターンは、UIスレッドに投稿されたViewに影響を及ぼす、 どんな変更でも確認するために、呼び出しを始める、View Modelコードを強制するそのような保証を提供しません。

スレッドに関係する処理は、より複雑で、そして、したがって、通常、検証するのがより難しいコードを要求します。 また、それは、通常、非同期であることを、テスト自体に要求します。通知が、UIスレッドで発生すると保証されるとき、 いずれかの理由で、標準的なイベントに基づく非同期パターンは、使用されます。あるいは、なぜなら、View Modelは、 適切なスレッドに通知を整理するために、サービス・アクセス層に依存しています。 テストは、簡素化することができ、そして、基本的に、「UIスレッドのためのディスパッチャ」の役割を実行することができます。

サービスの方法は、それらの操作を実装するために使用される、非同期イベント・パターンに依存する、モックアップを作ります。 メソッドに基づいたベース・パターンが使用される場合、 サービス・インターフェイスのためのモックは、通常十分な標準的なモックアップのフレームワークを使用して作成されます。 しかし、ユーザー定義したクラスのモックに基づいて、イベントに基づくパターンが使用される場合、 それは、通常、好まれるサービス・イベントのための追加と除去操作のためのメソッドを実装しています。

次のコードの例は、サービスのためのモックを使用したUIスレッドで、非同期動作の通知の正常に完了した適切な動作のテストを示します。 この例では、それが、非同期サービスコールを作成するとき、テスト・コードは、View Modelで、指定されるコールバックを捕まえます。 テストは、続いて、コールバックを呼び出すことで、テストで、後に呼び出した完了をシミュレーションします。 このアプローチは、コンポーネントを検証できます。それは、あなたのテストを非同期で作成する複雑さのない、非同期サービスを使用します。


    .Setup(
        r => 
            r.SubmitQuestionnaireAsync(
                It.IsAny<Questionnaire>(), 
                It.IsAny<Action<IOperationResult>>()))
    .Callback<Questionnaire, Action<IOperationResult>>(
        (q, a) => callback = a);
 
uiServiceMock
    .Setup(svc => svc.ShowView(ViewNames.QuestionnaireTemplatesList))

    .Callback<string>(viewName => requestedViewName = viewName);
submitResultMock
    .Setup(sr => sr.Error)
    .Returns<Exception>(null);
CompleteQuestionnaire(viewModel);
viewModel.Submit();
// Simulate callback posted to the UI thread.
// UIスレッドに投稿されるコールバックをシミュレーションします。
callback(submitResultMock.Object);
// Check expected behavior – request to navigate to the list view.
// 期待される動作を確認します-リストViewを操作するために要求します。
Assert.AreEqual(ViewNames.QuestionnaireTemplatesList, requestedViewName);

備考

この検証の方法を使用することは、テストの下で、オブジェクトの機能の能力を働かせるだけです。; コードが、安全なスレッドであるかは、テストをしません。

詳細情報

More Information

論理ツリーの詳細については、MSDNのWPFツリーについて(原文リンク)を参照してください。

添付プロパティの詳細については、MSDNの添付プロパティ概要(原文リンク)を参照してください。

MEFの詳細については、MSDNのManaged Extensibility Frameworkの概要(原文リンク)を参照してください。

Unityの詳細については、MSDNのUnityアプリケーション・ブロック(原文リンク)を参照してください。

DelegateCommandの詳細については、MVVMパターンを実装する(原文リンク)を参照してください。

Microsoft Expression Blendの動作を使用する詳細については、MSDNの組み込みの動作とその動作(原文リンク)を参照してください。

Microsoft Expression Blendで、ユーザー定義した動作を作成する詳細については、MSDNの上でユーザー定義したビヘイビアを作成する(原文リンク)を参照してください。

Microsoft Expression Blendで、作成しているユーザー定義したトリガと動作の詳細については、MSDN上でユーザー定義したトリガと動作を作成する(原文リンク)を参照してください。

WPFで、ディスパッチャを使用することに関する詳細は、MSDNのスレッディング・モデル(原文リンク)とディスパッチャ・クラス(原文リンク)を参照してください。

領域ナビゲーションの詳細については、ナビゲーション(原文リンク)内のビューに基づいたナビゲーションの項目(原文リンク)を参照してください。

イベントに基づいた非同期パターンの詳細については、MSDNのイベントに基づいた非同期パターン概要(原文リンク)を参照してください。

IAsyncResultデザインパターンの詳細については、MSDNの非同期プログラミング概要(原文リンク)を参照してください。

このエントリーをはてなブックマークに追加

Home PC C# Illustration

Copyright (C) 2011 Horio Kazuhiko(kukekko) All Rights Reserved.
kukekko@gmail.com
ご連絡の際は、お問い合わせページのURLの明記をお願いします。
「掲載内容は私自身の見解であり、所属する組織を代表するものではありません。」