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

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

新規作成日 2017-11-19
最終更新日

原文「Advanced MVVM Scenarios Using the Prism Library for WPF

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

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

コマンド。

Commands

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

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

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

Composite Commands

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

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

Save Allコマンドは、各々の項目のView Modelのインスタンスで、各々の実装されるSaveコマンドを呼び出します。

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コントロールを使用することを選択をしました。

各々の子ビューが、EditRegionという名前の領域に追加される例

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

アプリケーションのツールバーやリボン上で、現在、アクティブな項目だけを拡大するZoomコマンドを実装する

この筋書きに対応するために、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ボタンを定義します。

ListBoxコントロールの項目のコレクション

View Modelが、Deleteコマンドを実装するため、難問は、View Modelで、それぞれの項目のUIのために、Deleteボタンを、実装される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>

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

Command-Enabled Controls vs. Behaviors

コマンドをサポートする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メソッドの戻り値に基づいて、関連するコントロールの使用可能な状態を更新します。次に、CommandParameterが、設定されていない場合、EventArgsパラメータで使用するPrismのInvokeCommandActionは、親のトリガから、それに渡され、関連するコマンドにそれを渡します。

時には、あなたは、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. -->
            <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を呼び出すことができます。:

// object state, not used in this example
// オブジェクトの状態は、この例では使われません;
IAsyncResult asyncResult = this.service.BeginGetQuestionnaire(GetQuestionnaireCompleted, null );

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.
        }
    });

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

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

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

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

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

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

Using an Interaction Service

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

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

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

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

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により提供される対話要求オブジェクトにデータ結合されています。

ビューは、一般的に、相互作用のユーザー・エクスペリエンスを、動作にカプセル化します。

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

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

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

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

Initiating Interaction Requests from the View Model

InteractionRequest<T>クラスは、対話要求の間のビューと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インターフェイスは、すべてのコンテキスト・オブジェクトのための使用されます。それは、アプリケーションにおいて、ユーザーに重要なイベントを通知するために、対話要求が使用されるとき、使用されます。それは、ユーザーに表示される、TitleとContentの2つのプロパティを提供します。一般的に、通知は一方向です。それで、それは、ユーザーが、相互作用の間、これらの値を変更するかもしれないことを予期していません。Notificationクラスは、このインターフェイスの既定の実装です。

IConfirmationインターフェイスは、INotificationインターフェイスを拡張して、ユーザーが、操作を確認、あるいは、否定することを示すために使用される第3のプロパティConfirmedを追加します。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イベントを結合します。これにより、必要なXAML(Extensible Application Markup Language)の量が削減され、間違ったイベント名を誤って入力する可能性が減少します。

イベントが呼び出されたあと、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をViewにインポートすることができます。

例えば、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を提供することが必要です。

他の方法として、ここに、示されるように、あなたは、View上で、書き込み専用のView Modelプロパティを定義することができます。:ビューがインスタンスを生成したあと、Unityは、必要となるView Modelのインスタンスを生成し、そして、プロパティ・セッターを呼び出すでしょう。

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は、領域の中のナビゲーションのために、広範囲なサポートを提供します。領域ナビゲーションが、使用する仕組みは、前述のアプローチに極めて似ています。領域マネージャーが、インスタンス化を調整する、そして、特定の領域でViewを設置するための役割を果たすことを除きます。詳細については、ナビゲーション内のビューに基づいたナビゲーションの項目を参照してください。

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());

一般的に、異なるプロパティの値の組合せに対応するために、より多くの検証を必要としています。

一般的に、異なるプロパティの値の組合せに対応するために、より多くの検証を必要としています。

Testing the Requirements for INotifyDataErrorInfo Implementations

GetErrorsメソッドの正しい値を生成する一方で、GetErrorsの結果が異なるときような、INotifyDataErrorInfoインターフェイスの実装は、ErrorsChangedイベントが、適切に発生するか確認する必要があります。さらに、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で、指定されるコールバックを捕まえます。テストは、続いて、コールバックを呼び出すことで、テストで、後に呼び出した完了をシミュレーションします。このアプローチは、あなたのテストを非同期で作成する複雑さのない、非同期サービスを使用するコンポーネントを検証できます。

questionnaireRepositoryMock
    .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

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

Home PC C# Illustration

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