Prism開発者ガイド-パート5、MVVMパターンの実装

目次
  1. はじめに
  2. Prismアプリケーションの初期化
  3. コンポーネント間の依存関係の管理
  4. モジュラーアプリケーション開発
  5. MVVMパターンの実装
  6. 高度なMVVMシナリオ
  7. ユーザーインターフェイスの作成
    1. ユーザーインターフェイスのガイドライン
  8. ナビゲーション
    1. ビューベースのナビゲーション
  9. 疎結合コンポーネント間の相互作用

Model-View-ViewModel (MVVM、 View-View-View- Model)パターンは、ユーザーインターフェースからビジネスロジックとプレゼンテーションロジックを分離するのに役立ちます。 アプリケーションロジックとUIの間で責任を共有するためのサポートにより、アプリケーションのテスト、保守、開発が容易になります。 また、コードの再利用機能を大幅に向上させ、開発者と設計者がアプリケーションの関連部分の開発でより簡単に共同作業できるようにします。

MVVMパターンを使用すると、アプリケーションユーザーインターフェイス、プレゼンテーションロジック、およびビジネスロジックが3つの個別のクラスに分割されます。 これは、UIとそのロジックをカプセル化するビュー、プレゼンテーションロジックとその状態をカプセル化するビューモデル、およびアプリケーションのビジネスロジックとデータをカプセル化するモデルです。

Prismには、SilverlightまたはWPFアプリケーションでMVVMパターンを実装する方法を示すサンプルおよび実装例が含まれています。 Prismライブラリは、このパターンの実装に役立つ機能も提供します。 これらの機能は、MVVMパターンを実装するための最も一般的な方法を具体化し、Expression BlendおよびVisual Studioとのテスト容易性および互換性を提供するように設計されています。

この章では、MVVMパターンの概要とその実装方法について説明します。 第6章では、Prismライブラリを使用してより複雑なMVVMスクリプトを実装する方法について説明します。

責任とクラスの特徴


MVVMパターンは、 プレゼンテーションモデルパターンの密接なバリエーションであり、データバインディング、データパターン、コマンド、動作などの基本的なWPFおよびSilverlight機能との整合性を高めるために最適化されています。

MVVMパターンでは、ビューはUIとUIロジックをカプセル化し、ビューモデルはプレゼンテーションロジックとその状態をカプセル化し、モデルはビジネスロジックとデータをカプセル化します。 ビューは、データ、コマンド、および通知イベントをバインドすることにより、ビューモデルと対話します。 プレゼンテーションモデルは、モデルにデータを要求するか、変更の通知をサブスクライブし、モデルの状態の更新を調整し、必要に応じてデータを変換、検証、集計してビューに表示します。

次の図は、MVVMパターンの3つの部分とそれらの相互作用を示しています。

MVVMクラスとその相互作用。

すべての共有プレゼンテーションパターンと同様に、MVVMパターンを効率的に使用するための鍵は、アプリケーションコードを正しいクラスに分割する方法を理解し、これらのクラスが異なるシナリオで相互作用する方法を理解することです。 以下のセクションでは、MVVMパターンの各クラスの責任と特性について説明します。

クラスを見る


ビューは、ユーザーが画面に表示するものを決定する役割を果たします。 理想的には、分離コードビューには、 InitializeComponentメソッドを呼び出すコンストラクターのみが含まれています。 場合によっては、分離コードには、複雑なアニメーションなど、XAMLで表現することが困難または非効率的な視覚的動作を実装するUIロジックコードが含まれている場合があります。また、ビューの一部である視覚要素をコードで直接制御する必要がある場合もあります。 ビューにテストが必要なロジックコードを配置することは許可されません。 通常、コードビハインドビューのコードは、UIオートメーションを使用してテストできます。

SilverlightおよびWPFでは、ビュー内のデータバインディング式は、そのデータコンテキストに関連して評価されます。 MVVMでは、プレゼンテーションモデルはプレゼンテーションデータのコンテキストとして設定されます。 ビューモデルは、ビューをバインドできるプロパティとコマンドを実装し、変更通知イベントを通じて状態の変化をビューに通知します。 通常、ビューとそのプレゼンテーションモデルの間には直接的な関係があります。

通常、ビューはControlクラスまたはUserControlクラスから継承されます。 ただし、場合によっては、プレゼンテーションは、オブジェクトを視覚的に表すために使用されるUI要素を定義するデータテンプレートによって表される場合があります。 開発者はデータテンプレートを使用して、プレゼンテーションモデルの視覚化方法を簡単に指定したり、基になるオブジェクトやその動作を直接変更せずにデフォルトの視覚表現を変更したりできます。

データテンプレートは、コードビハインドのないビューと見なすことができます。 UIにマップする必要があるときに、特定の種類のプレゼンテーションモデルにバインドするように設計されています。 実行時に、テンプレートによって定義されたビューが自動的に作成され、対応するビューモデルがそのデータコンテキストによって設定されます。

WPFでは、データテンプレートをアプリケーションレベルのビューモデルに関連付けることができます。 次に、WPFは、UIに表示されるたびに、指定されたタイプのプレゼンテーションモデルのオブジェクトにこのデータテンプレートを自動的に適用します。 これは、暗黙的なデータパターンとして知られています。 Silverlightでは、表示するコントロール内のビューモデルオブジェクトのデータテンプレートを明示的に定義する必要があります。 データテンプレートは、それを使用するコントロールに組み込まれているものとして定義するか、親ビューの外部のリソースディクショナリで定義し、プレゼンテーションリソースディクショナリと宣言的に組み合わせることができます。

要約すると、ビューには次の重要な特性があります。

モデルクラスを表示


MVVMパターンのプレゼンテーションモデルは、プレゼンテーションロジックと表示用のデータをカプセル化します。 彼女は、プレゼンテーションへの直接的なリンク、または実装またはプレゼンテーションの種類に関する知識を持ちません。 ビューモデルは、ビューがデータをバインドできるプロパティとコマンドを実装し、通知イベントを通じて状態の変化をビューに通知します。 ビューモデルが提供するプロパティとコマンドは、UIが依存する機能を決定しますが、ビューはその機能がユーザーにどのように表示されるかを決定します。

ビューモデルは、ビューと必要なモデルクラスとの相互作用を調整します。 多くの場合、プレゼンテーションモデルとモデルクラスの間には1対多の関係があります。 ビューモデルは、ビュー内のコントロールがデータをモデルに直接バインドできるように、モデルクラスをビューに直接置き換えることができます。 この場合、モデルクラスは、データバインディングおよび関連する変更通知イベントをサポートするように設計する必要があります。 このシナリオの詳細については、このトピックの「データバインディング」セクションを参照してください。

ビューモデルでは、モデルデータを簡単に使用できるように、モデルデータを変換または操作できます。 ビューモデルには、ビューの特定のニーズに合わせて追加のプロパティがあります。 これらのプロパティは通常、モデルの一部にすることはできません。 たとえば、ビューモデルは2つのフィールドの値を組み合わせて表示しやすくしたり、文字列の長さが制限されているフィールドに入力するときに残っている文字数を計算したりできます。 ビューモデルは、データ検証ロジックを実装して一貫性を確保することもできます。

ビューモデルは、UIの視覚的な変更にビューが使用できる論理状態も決定できます。 ビューは、ビューモデルの状態を反映するマークアップまたはスタイルの変更を定義できます。 たとえば、ビューモデルには、データが非同期的にWebサービスに送信されることを示す状態があります。 この状態では、ビューにアニメーションが表示され、ユーザーに視覚的なフィードバックが提供されます。

通常、プレゼンテーションモデルは、UIで表され、ユーザーが呼び出すことができるコマンドまたはアクションを定義します。 典型的な例は、ユーザーがWebサービスまたはリポジトリにデータを渡すことができるSubmitコマンドをビューモデルが提供する場合です。 ビューはこのコマンドをボタンとして表示できるため、ユーザーはこのコマンドをクリックしてデータを転送できます。 通常、コマンドが使用できなくなると、UIに関連付けられた表現が無効になります。 コマンドを使用すると、ユーザーアクションをカプセル化し、視覚的なプレゼンテーションから分離できます。

その結果、プレゼンテーションモデルには次の重要な特徴があります。

ご注意 プレゼンテーションまたはプレゼンテーションモデル?
多くの場合、特定の機能を実装する場所を決定することは明らかではありません。 一般的なルールでは、特定のビジュアルディスプレイに関連し、後でアップグレードできるものはすべて(現在、アップグレードを計画していない場合でも)ビューに入れる必要があります。 アプリケーションロジックにとって重要なものはすべて、ビューモデルに入る必要があります。 さらに、ビューモデルにはビュー内の特定の視覚要素に関する明示的な知識がないため、ビュー内の視覚要素をプログラムで制御するためのコードは、ビューのコードビハインドにあるか、 Behaviorsにカプセル化されている必要があります。 同様に、データバインディングによってビューに表示する必要のあるデータ要素を取得または操作するためのコードは、ビューモデルに存在する必要があります。 たとえば、リストボックスで選択したアイテムのハイライト色はビューで定義する必要がありますが、表示するアイテムのリストと選択したアイテムへのリンクはプレゼンテーションモデルで定義する必要があります。

モデルクラス


MVVMパターンのモデルは、ビジネスロジックとデータをカプセル化します。 ビジネスロジックは、データの一貫性と有効性を確保するためにビジネスルールが満たされていることを確認するために、アプリケーションデータの取得と管理に関係するアプリケーションロジックとして定義されます。 再利用性を最大にするために、モデルにはプレゼンテーション固有の情報、ロジック、または動作を含めないでください。

原則として、モデルはアプリケーションのクライアント部分のサブジェクト領域の本質を表します。 アプリケーションデータモデルと、ビジネスおよび検証ロジックに基づくデータ構造を含めることができます。 モデルはデータとキャッシュにもアクセスできますが、これは通常、個別のデータリポジトリまたは対応するサービスを使用して行われます。 多くの場合、モデルおよびデータアクセス層は、ADO.NET Entity Framework、WCF Data Services、またはWCF RIA Servicesインフラストラクチャの一部として自動的に生成されます。

通常、モデルには、ビューへのバインドを容易にするツールが実装されています。 これは通常、プロパティまたはコレクションへの変更の通知がINotifyPropertyChangedおよびINotifyCollectionChanged介してサポートされることを意味しINotifyCollectionChanged 。 オブジェクトのセットを提供するモデルクラスは、通常、 INotifyCollectionChangedインターフェイスの実装を提供するObservableCollection<T>クラスから継承されます。 モデルは、 IDataErrorInfo (またはINotifyDataErrorInfo )インターフェイスを介したデータ検証とエラーレポートもサポートできます。 これらのインターフェイスにより、バインドされたデータの値が変更されたときに、WPFおよびSilverlight通知を送信してUIを更新できます。 また、UIレベルでのデータ検証サポートとエラーレポートも提供します。
ご注意
, ?
インターフェイスINotifyPropertyChangedINotifyCollectionChangedIDataErrorInfo 、またはINotifyDataErrorInfo実装しないモデルオブジェクトを使用する必要がある場合があります。 これらの場合、ビューモデルはモデルオブジェクトをラップし、ビューに必要なプロパティを提供する必要があります。 これらのプロパティの値は、モデルオブジェクトによって直接提供されます。 ビューモデルは、ビューがデータを簡単にバインドできるように、提供するプロパティに必要なインターフェイスを実装できます。

このモデルには、次の主要な機能があります。

クラスの相互作用


MVVMパターンは、アプリケーションのユーザーインターフェイス、プレゼンテーションロジック、ビジネスロジックとデータの間で責任を明確に分離し、各要素を個別のクラスに分割します。 したがって、MVVMを実装する場合、前のセクションで説明したように、コードを正しいクラスに分割することが重要です。

適切に設計されたプレゼンテーションクラス、プレゼンテーションモデル、およびモデルは、正しいロジックと動作をカプセル化するだけでなく、データバインディング、コマンド、およびデータ検証インターフェイスを通じて簡単に相互作用するように設計されます。

プレゼンテーションとそのプレゼンテーションモデルとの相互作用はおそらく最も重要ですが、モデルクラスとプレゼンテーションモデルとの相互作用も重要です。 以下のセクションでは、これらの相互作用のさまざまなタイプについて説明し、MVVMパターンを実装してそれらを開発する方法について説明します。

データバインディング


データバインディングは、MVVMパターンで非常に重要な役割を果たします。 WPFおよびSilverlightは、強力なデータバインディング機能を提供します。 プレゼンテーションモデルと(理想的には)モデルクラスは、データバインディングをサポートするように設計する必要があります。 通常、これは、正しいインターフェースを実装する必要があることを意味します。

SilverlightおよびWPFデータバインディングは、さまざまなモードをサポートしています。 一方向のデータバインディングを使用すると、UIコントロールをビューモデルに関連付けて、レンダリング時に基になるデータの値を反映させることができます。 双方向データバインディングは、ユーザーがUIでデータを変更すると、基になるデータを自動的に更新します。

プレゼンテーションモデルでデータが変更されたときにUIが更新されるようにするには、適切な変更通知インターフェイスを実装する必要があります。 データをバインドできるプロパティを定義する場合は、 INotifyPropertyChangedインターフェイスを実装する必要があります。 ビューモデルがコレクションを表す場合、 INotifyCollectionChangedを実装するか、このインターフェイスを実装するObservableCollection<T>クラスから継承する必要があります。 これらのインターフェースは両方とも、基礎となるデータが変更されるたびに生成されるイベントを定義します。 これらのイベントが生成されると、関連するコントロールが自動的に更新されます。

多くの場合、ビューモデルにはオブジェクトを返すプロパティが含まれています(オブジェクトには、追加のオブジェクトを返すプロパティが含まれている場合があります)。 データバインディングWPFおよびSilverlightは、 Pathプロパティを介したネストされたプロパティへのバインディングをサポートしています。 したがって、プレゼンテーションモデルは、他のクラスのプレゼンテーションモデルまたはモデルへの参照を返す傾向があります。 プレゼンテーションに使用できるすべてのプレゼンテーションモデルクラスとモデルは、 INotifyPropertyChangedまたはINotifyCollectionChanged実装する必要がありINotifyCollectionChanged

以下のセクションでは、MVVMパターン内のデータバインディングをサポートするために必要なインターフェイスを実装する方法について説明します。

INotifyPropertyChangedの実装

ビューモデルまたはモデルのクラスにINotifyPropertyChangedインターフェイスを実装すると、値が変更されたときにビューの関連コントロールに通知できます。 次の例に示すように、このインターフェイスの実装は比較的単純です( Basic MVVM QuickStartの Questionnaireクラスを参照)。

 public class Questionnaire : INotifyPropertyChanged { private string favoriteColor; public event PropertyChangedEventHandler PropertyChanged; ... public string FavoriteColor { get { return this.favoriteColor; } set { if (value != this.favoriteColor) { this.favoriteColor = value; if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs("FavoriteColor")); } } } } } 

ビューモデルの多くのクラスでのINotifyPropertyChangedインターフェイスの実装は、プロパティ名を定義する必要があるために繰り返され、エラーが発生しやすくなります。 Prismライブラリは、以下に示すように、 INotifyPropertyChangedセーフな方法でINotifyPropertyChangedインターフェイスを実装するビューモデルクラスを継承できる便利な基本クラスを提供します。

 public class NotificationObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; ... protected void RaisePropertyChanged<T>( Expression<Func<T>> propertyExpression) {...} protected virtual void RaisePropertyChanged(string propertyName) {...} } 

継承されたビューモデルクラスは、以下に示すように、特定のプロパティ名でRaisePropertyChangedを呼び出すか、プロパティにアクセスするラムダ式を使用して、プロパティ変更イベントを生成できます。

 public string CurrentState { get { return this.currentState; } set { if (this.currentState != value) { this.currentState = value; this.RaisePropertyChanged(() => this.CurrentState); } } } 

ご注意
この方法でラムダ式を使用すると、クエリごとにラムダ式を評価する必要があるため、パフォーマンスがわずかに低下します。 , , . , , . , , .

ご注意
Resharper , .

多くの場合、モデルまたはビューモデルには、モデルまたはビューモデルの他のプロパティに応じて値が計算されるプロパティが含まれます。プロパティ変更イベントを生成するときは、依存プロパティの通知イベントも必ず生成してください。あなたが最初の2つのフィールドの合計を表すフィールドPrice1、Price2とSumPriceを持っている場合には、フィールドのセッターPrice1とは、Price2上記のすべてのメソッドと呼ばれるべきですRaisePropertyChanged(“SumPrice”)

INotifyCollectionChangedの実装

ビューモデルのクラスまたはモデルは、要素のコレクションにすることも、コレクションを返す1つ以上のプロパティを持つこともできます。それ以外の場合は、おそらく、またはItemsControlなどの相続人のいずれかでコレクションを表示することができます。これらのコントロールは、プロパティを介してコレクションを返すコレクションまたはプロパティを提供するビューモデルに関連付けることができますListBoxDataGridItemSource

 <DataGrid ItemsSource="{Binding Path=LineItems}" /> 

オブジェクトのコレクションへの変更に関する通知要求を適切にサポートするには、ビューモデルのクラスまたはコレクションであるモデルは、インターフェイスをINotifyCollectionChangedインターフェイスに加えてINotifyPropertyChanged実装する必要があります。ビューモデルまたはモデルのクラスが、コレクションへの参照を返すプロパティを定義する場合、返されるコレクションクラスはinterfaceを実装する必要がありますINotifyCollectionChanged

ただし、INotifyCollectionChangedコレクション内でアイテムが追加、削除、移動、または変更されたときに通知を送信する必要があるため、インターフェイスの実装はかなり複雑です。多くの場合、インターフェイスを直接実装する代わりに、直接使用するか、既に実装されているコレクションクラスから継承する方が簡単です。クラスObservableCollection<T>このインターフェイスを実装し、通常は基本クラスとして、または要素のコレクションを表すプロパティで使用されます。

ビューにデータをバインドするコレクションを作成する必要があり、アイテムのユーザー選択を追跡したり、コレクション内のアイテムのフィルタリング、並べ替え、グループ化をサポートする必要がない場合は、インスタンスへの参照を返すプロパティをビューモデルに定義するだけObservableCollection<T>です。

 public class OrderViewModel : INotifyPropertyChanged { public OrderViewModel(IOrderService orderService) { this.LineItems = new ObservableCollection<OrderLineItem>( orderService.GetLineItemList()); } public ObservableCollection<OrderLineItem> LineItems { get; private set; } } 

コレクションクラスへの参照を(たとえば、実装していない別のコンポーネントまたはサービスからINotifyCollectionChangedObservableCollection<T>取得するIEnumerable<T>場合どちらかをパラメーターとして取るコンストラクターの1つを使用して、このコレクションをインスタンスでラップできますList<T>

ICollectionViewの実装(コレクションビュー)

前のコード例は、ビューの関連コントロールを介して表示できる要素のコレクションを返すビューモデルの単純なプロパティを実装する方法を示しています。ObservableCollection<T>インターフェースを実装しているためINotifyCollectionChanged、ビューのコントロールは要素が追加または削除されると自動的に更新されます。

多くの場合、ビューでの要素のコレクションの表示方法をより正確に制御するか、ビューのモデル内で表示されている要素のコレクションとのユーザーの対話を追跡する必要があります。たとえば、プレゼンテーションモデルに実装されたプレゼンテーションロジックに従って要素のコレクションをフィルター処理または並べ替えたり、ビューで現在選択されている要素を追跡して、プレゼンテーションモデルに実装されたコマンドが選択された要素のみに影響を与えるようにすることができます。

WPFおよびSilverlightは、インターフェイスを実装するさまざまなクラスを提供することにより、このようなシナリオをサポートします。ICollectionView。このインターフェイスは、コレクションのフィルター処理、並べ替え、またはグループ化を可能にするプロパティとメソッドを提供し、現在選択されているアイテムを追跡または変更することもできます。 SilverlightとWPFはどちらもこのインターフェイスの実装を提供し、Silverlightはクラスを提供し、PagedCollectionViewWPF クラスを提供しますListCollectionView

コレクションビュークラスは、選択されたアイテムを自動的に追跡し、並べ替え、フィルター、およびアラートできるように、アイテムの基本コレクションをラップすることによって機能します。これらのクラスのインスタンスは、クラスを使用して、プログラムまたはXAMLで宣言的に作成できますCollectionViewSource
ご注意
WPF CollectionView , . Silverlight , ICollectionViewFactory .

ビューモデルは、コレクションビュークラスを使用して、ベースコレクションの重要な状態情報を追跡し、ビューのUIとモデルの基になるデータとの間の職務の分離をサポートできます。実際、これらCollectionViewsはコレクションをサポートするために特別に設計されたプレゼンテーションモデルです。

したがって、ビューモデル内からコレクション内のアイテムの選択をフィルター処理、並べ替え、グループ化、または追跡する必要がある場合、ビューモデルは、ビューに提供されるコレクションごとにコレクションビュークラスのインスタンスを作成する必要があります。その後、次のような選択イベントにサブスクライブできます。CurrentChanged、またはビューモデル内のコレクションビュークラスメソッドを使用して、フィルタリング、並べ替え、またはグループ化を管理します。

ビューモデルは、参照を返す読み取り専用プロパティを実装ICollectionViewして、ビュー内のコントロールがコレクションビューにデータをバインドし、それと対話できるようにする必要があります。基本クラスからのすべてのWPFおよびSilverlightコントロールは、ItemsControl自動的にクラスと対話できますICollectionView

次の例では、PagedCollectionViewSilverlightを使用して、現在選択されているクライアントを追跡します。

 public class MyViewModel : INotifyPropertyChanged { public ICollectionView Customers { get; private set; } public MyViewModel(ObservableCollection<Customer> customers) { Customers = new PagedCollectionView(customers); Customers.CurrentChanged += new EventHandler(SelectedItemChanged); } private void SelectedItemChanged(object sender, EventArgs e) { Customer current = Customers.CurrentItem as Customer; ... } } 

ビューでは、次のItemsControlようにListBox、property などを使用して、などCustomersのビューモデルのプロパティ関連付けることができItemsSourceます。

 <ListBox ItemsSource="{Binding Path=Customers}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding Path=Name}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> 

ユーザーがUIでクライアントを選択すると、プレゼンテーションモデルに通知され、選択したクライアントのみに関係するコマンドを実行できます。次の例に示すように、ビューモデルは、コレクションビューオブジェクトのメソッドを呼び出すことにより、プログラムでUIの現在の選択を変更することもできます。

 Customers.MoveCurrentToNext(); 

コレクションビューで選択したアイテムが変更されると、UIが自動的に更新され、選択したアイテムの状態が視覚的に示されます。実装はWPFに似ていますPagedCollectionViewが、前の例では、通常、クラスListCollectionView、またはに置き換えられますが、BindingListCollectionView以下に示します。

 Customers = new ListCollectionView(_model); Customers.CurrentChanged += new EventHandler(SelectedItemChanged); 

チーム


ビューで表示または編集されるデータへのアクセスを提供することに加えて、ビューモデルは、ユーザーが実行できる1つ以上のアクションまたは操作を定義する可能性があります。 WPFおよびSilverlightでは、ユーザーがUIを介して実行できるアクションまたは操作は通常、コマンドとして定義されます。コマンドは、UIのコントロールに簡単に結び付けることができるアクションまたは操作を表示する便利な方法を提供します。これらは、必要なアクションを実装するコードをカプセル化し、視覚的表現から分離するのに役立ちます。

コマンドは、さまざまな方法でユーザーが視覚的に提示および呼び出すことができます。ほとんどの場合、マウスクリックによってトリガーされますが、キーストローク、タッチジェスチャ、またはその他の入力イベントによってトリガーすることもできます。ビュー内のコントロールはビューモデルのコマンドに関連付けられているため、ユーザーはコントロールで定義された入力イベントを使用してそれらを呼び出すことができます。ビュー内のUIコントロールとチームの間の相互作用は双方向になります。この場合、ユーザーがUIを操作することでコマンドがトリガーされ、基本的なコマンドがオンまたはオフになると、コントロールが自動的にロック解除またはロックされる場合があります。

ビューモデルはコマンドを次のように実装できますCommand Method、またはCommand Object(インターフェイスを実装するオブジェクトICommand)として。いずれの場合でも、ビューとコードビハインドの複雑なイベント処理コードを必要とせずに、ビューとチームの相互作用を宣言的に定義できます。たとえば、WPFおよびSilverlightの特定のコントロールはコマンドをサポートし、ビューモデルのCommandtypeプロパティICommand関連付けられるプロパティ提供します。その他の場合、CommandBehaviorコントロールをプレゼンテーションモデルのコマンドメソッドまたはコマンドオブジェクトに関連付けるために使用できます
ご注意
ビヘイビアは、対話ロジックとビヘイビアをカプセル化するために使用できる強力で柔軟な拡張性メカニズムであり、ビュー内のコントロールに宣言的に関連付けることができます。CommandBehaviorコマンドオブジェクトまたはメソッドを、チームと対話するように特別に設計されていないコントロールに関連付けるために使用できます。

次のセクションでは、メソッドまたはコマンドオブジェクトとしてビューにコマンドを実装する方法、およびそれらをビュー内のコントロールに関連付ける方法について説明します。

コマンドオブジェクトの実装

コマンドオブジェクトは、インターフェイスを実装するオブジェクトですICommand。このインターフェイスはExecute、必要なアクションをカプセル化するメソッドCanExecuteと、特定の時間にコマンドを呼び出すことができるかどうかを示すメソッド定義します。これらのメソッドは両方とも、コマンドへのパラメーターとして単一の引数を受け入れます。ロジックをコマンドオブジェクトにカプセル化するということは、テストと保守が簡単になることを意味します。

インターフェイスの実装はICommand比較的簡単です。ただし、アプリケーションで簡単に使用できるこのインターフェイスの実装は多数あります。たとえばActionCommand、Expression Blend SDKのクラス、またはDelegateCommandPrismが提供するクラス使用できます

クラスDelegateCommandビューモデルクラス内に実装されたメソッドをそれぞれ参照する2つのデリゲートをカプセル化します。これらのデリゲートを呼び出すことにより、DelegateCommandBaseメソッドExecuteCanExecuteインターフェイスを実装するクラスから継承しますICommandクラスコンストラクターのビューモデルメソッドでデリゲートを定義しますDelegateCommand。これは次のようになります。

 public class DelegateCommand<T> : DelegateCommandBase { public DelegateCommand(Action<T> executeMethod, Func<T,bool> canExecuteMethod) :base((o) => executeMethod((T)o), (o) => canExecuteMethod((T)o)) { ... } } 

たとえば、次のコードは、プレゼンテーションモデルメソッドおよびにデリゲートを作成することによりDelegateCommand、コマンドを表すインスタンスどのようにSubmit作成されるかを示していますコマンドは、へのリンクを返す読み取り専用プロパティを介してビューに渡されますOnSubmitCanSubmitICommand

 public class QuestionnaireViewModel { public QuestionnaireViewModel() { this.SubmitCommand = new DelegateCommand<object>( this.OnSubmit, this.CanSubmit); } public ICommand SubmitCommand { get; private set; } private void OnSubmit(object arg) {...} private bool CanSubmit(object arg) { return true; } } 

ExecuteオブジェクトDelegateCommandメソッドが呼び出されると、呼び出しは、コンストラクターで定義したデリゲートを介して、ビューモデルクラスのメソッドにリダイレクトされます。同様に、メソッドCanExecuteが呼び出されると、ビューモデルクラスの対応するメソッドが呼び出されます。CanExecuteコンストラクター内のメソッドへのデリゲートはオプションです。デリゲートが定義されていない場合は、DelegateCommand必ず戻りますtrueCanExecute

クラスDelegateCommandはジェネリック型です。 typeパラメーターは、渡されるコマンドパラメーターのタイプExecutemethodsを決定ますCanExecute。前の例では、コマンドパラメーターのタイプはobjectです。クラスのユニバーサルバージョンではありませんDelegateCommandPrismは、コマンドパラメーターが不要な場合に使用するためにも提供されています。

ビューモデルは、オブジェクトのCanExecuteメソッドRaiseCanExecuteChanged呼び出すことによりコマンドメソッドの状態の変化を示すことができますDelegateCommand。これにより、イベントがトリガーされCanExecuteChangedます。チームに関連付けられているUIのコントロールは、状態を更新して、関連付けられたチームの可用性を反映します。

他のインターフェース実装は利用可能ですICommand。クラスActionCommandSDKのExpression Blendの中には、プリズムのクラスに似ているDelegateCommand、先に説明したが、それは、単一のデリゲートメソッドをサポートしていますExecute。 Prismは、実行のためにCompositeCommandグループ化できるクラスも提供しますDelegateCommands。クラスの使用の詳細についてはCompositeCommand 、第6章「高度なMVVMシナリオ」の「複合コマンド」を参照してください。

ビューからコマンドオブジェクトを呼び出す

ビュー内のコントロールをビューモデルによって提供されるコマンドオブジェクトに関連付けるには、多くの方法があります。要素WPFとSilverlight特にから継承された4制御、ButtonBaseなどButtonRadioButtonHyperlink、またはから継承はMenuItem、容易性を介してコマンドオブジェクトに接続することができますCommandWPFはへのバインドもサポートしICommandていKeyGestureます。

 <Button Command="{Binding Path=SubmitCommand}" CommandParameter="SubmitOrder"/> 

さらに、プロパティを使用してコマンドパラメータを設定できますCommandParameter。期待されるパラメーターのタイプは、ターゲットメソッドExecuteおよびで決定されますCanExecute。ユーザーがこのコントロールを操作すると、コントロールは自動的にターゲットコマンドを呼び出し、コマンドパラメーターがあれば、Executeコマンドメソッドにパラメーターとして渡されます。前の例では、ボタンをクリックすると自動的に呼び出されSubmitCommandます。さらに、ハンドラーが定義されているCanExecute場合、ボタンがCanExecute戻るfalse自動的に無効になり、が有効になるとボタンが有効になりますtrue

別のアプローチは、Expression Blendの相互作用トリガーと動作を使用することInvokeCommandActionです。

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

このアプローチは、相互作用トリガーをアタッチできるコントロールに使用できます。これは、子孫ButtonBaseはないコントロールにコマンドを添付する場合、またはクリックイベント以外のイベントでコマンドを呼び出す場合に特に便利です。コマンドのパラメーターを渡す必要がある場合は、プロパティを使用できることを思い出してくださいCommandParameter

コマンドに直接関連付けることができるコントロールとは異なり、コマンドInvokeCommandActionの値に基づいてコントロールを自動的に有効または無効にすることはありませんCanExecute。この動作を実装するには、プロパティをバインドする必要がありますIsEnabled 上記のように、ビューモデルの適切なプロパティにコントロールするか、コマンドの可用性に応じて、コントロールを無効にする独自の特別な動作を記述します。
ご注意
WPFおよびSilverlight 4のコマンドサポートコントロールを使用すると、コントロールを宣言的にコマンドにバインドできます。これらのコントロールは、ユーザーが操作すると指定されたコマンドを呼び出します。たとえば、コントロールのButton場合、ユーザーがボタンをクリックするとコマンドが呼び出されます。コマンドに関連付けられたこのイベントは修正されており、変更できません。

また、ビヘイビアを使用すると、コントロールを宣言的な方法でコマンドオブジェクトに接続できます。ただし、ビヘイビアーはコントロールによって生成されたさまざまなイベントに関連付けることができ、指定された条件が満たされたときにプレゼンテーションモデル内の関連付けられたコマンドオブジェクトまたはコマンドメソッドを呼び出すために使用できます。つまり、ビヘイビアは、サポートされているコマンドコントロールと同じシナリオの多くを実装でき、柔軟性と制御の度合いを高めることができます。

チームがサポートするコントロールを使用するタイミング、ビヘイビアを使用するタイミング、および使用するビヘイビアの種類を選択する必要があります。ビューのコントロールをビューモデルの機能とリンクするため、または一貫性のために単一のメカニズムを使用する場合は、コマンドをサポートするコントロールであっても、動作の使用を検討してください。

コマンドをサポートするコントロールのみを使用する必要があり、標準のコマンド呼び出しイベントに満足している場合は、これを行う必要さえないかもしれません。同様に、開発者またはUI開発者がExpression Blendを使用しない場合、Expression Blendの動作を使用するために必要な追加の構文のため、コマンド互換のコントロール(またはカスタムアタッチされた動作)のみを選択できます。

ビューからコマンドメソッドを呼び出す

オブジェクトとしてコマンドを実装する別のアプローチICommandは、単にビューモデルのメソッドとしてコマンドを実装し、ビヘイビアを使用してこれらのメソッドをビューから直接呼び出すことです。

これは、前のセクションで示した動作からコマンドを呼び出すのと同様の方法で実現できます。ただし、を使用する代わりにInvokeCommandActionが使用されCallMethodActionます。次の例ではSubmit、ベースビューモデルのメソッド(パラメーターなし)呼び出します

 <Button Content="Submit" IsEnabled="{Binding CanSubmit}"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <i:CallMethodAction TargetObject="{Binding}" Method="Submit"/> </i:EventTrigger> </i:Interaction.Triggers> </Button> 


TargetObject式を使用するときに、基になるデータコンテキスト(ビューモデル)にバインドします{Binding}パラメーターMethodは、呼び出すメソッドを定義します。
ご注意
CallMethodAction . , , InvokeCommandAction , CallMethodAction , .


プレゼンテーションモデルまたはモデルは、多くの場合、データを検証し、プレゼンテーションのデータの正確性のエラーを通知して、ユーザーが修正できるようにする必要があります。

SilverlightとWPFは、ビュー内のコントロールに関連付けられた個々のプロパティが変更されたときに発生するデータ検証エラーの管理をサポートします。コントロールに関連付けられている個々のプロパティの場合、ビューモデルまたはモデルはプロパティのsetメソッド内でデータ検証エラーを通知し、着信する不正な値を拒否して例外をスローします。ValidatesOnExceptionsデータバインディングオブジェクトのプロパティが等しい場合true、WPFおよびSilverlightのデータバインディングメカニズムは例外を処理し、検証エラーの存在を視覚的に示します。

ただし、プロパティによる例外のスローは、可能な場合は避ける必要があります。別のアプローチは、ビューモデルまたはモデルのクラスのIDataErrorInfoいずれかでインターフェイスを実装することINotifyDataErrorInfoです。これらのインターフェイスにより、ビューモデルまたはモデルは1つ以上のプロパティの値を検証し、エラーメッセージをビューに返して、ユーザーに通知することができます。

IDataErrorInfoの実装

インターフェイスIDataErrorInfoは、データの検証とエラーメッセージの基本的なサポートを提供します。これは、2つの読み取り専用プロパティを定義しますErrorインデクサーのプロパティ(インデクサーのパラメーターとしてのプロパティの名前)とproperty です。両方のプロパティは文字列値を返します。

インデクサープロパティにより、ビューモデルまたはモデルクラスは、名前付きプロパティに固有のエラーメッセージを提供できます。空の文字列またはnullの戻り値は、変更されたプロパティ値が有効であることをビューに示します。 Errorプロパティを使用すると、ビューモデルまたはモデルクラスでオブジェクト全体のエラーメッセージを提供できます。現在、このプロパティはSilverlightまたはWPFデータバインディングエンジンによって呼び出されないことに注意してください。

インデクサープロパティIDataErrorInfoデータ関連のプロパティが表示されたとき、およびその後変更されたときに呼び出されます。インデクサーのプロパティは、変化するすべてのプロパティに対して呼び出されるため、データ検証が可能な限り高速で効率的であることを保証するために、すべてを行う必要があります。

ビュー内のコントロールをプロパティにバインドする場合、インターフェイスを介してコントロールをテストし、データバインドのIDataErrorInfoプロパティをValidatesOnDataErrors設定しますtrueこれにより、データバインディングエンジンが関連するプロパティからエラー情報を要求するようになります。

 <TextBox Text="{Binding Path=CurrentEmployee.Name, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True }"/> 

INotifyDataErrorInfoの実装

インターフェースはINotifyDataErrorInfo、インターフェースよりも柔軟ですIDataErrorInfoプロパティの複数のエラー、データの非同期検証、およびオブジェクトのエラー状態の変更をビューに通知する機能をサポートしています。ただし、INotifyDataErrorInfo現在はSilverlight 4でのみサポートされており、WPF 4では使用できません。
ご注意
このインターフェイスは、.NET Framework 4.5のWPFでもサポートされるようになりました。

インターフェイスINotifyDataErrorInfoHasErrors、ビューモデルがプロパティにエラー(または複数のエラー)が存在するかどうかを示すことを可能にするプロパティと、ビューモデルがGetErrors特定のプロパティのエラーメッセージのリストを返すことを可能にするメソッド定義します。

インターフェースINotifyDataErrorInfoはイベントも定義しますErrorsChanged。非同期検証スクリプトをサポートし、ビューモデルまたはビューがイベントを介して特定のプロパティのエラー状態の変更を通知できるようにしますErrorsChanged。プロパティ値は、データバインディングだけでなく、たとえば、Webサービスリクエストやバックグラウンド計算の結果として、さまざまな方法で変更できます。イベントErrorsChangedデータ検証エラーが識別されるとすぐに、ビューモデルがビューにエラーを報告できるようにします。

サポートのためにINotifyDataErrorInfo、各プロパティのエラーのリストを保持する必要があります。Model-View-ViewModel参照実装(MVVM RI)ErrorsContainerは、オブジェクトのすべての検証エラーを追跡するコレクションクラス使用してこれを行う1つの方法を示しています。また、エラーリストが変更された場合に通知イベントを生成します。次の例は、クラスを使用した(DomainObjectモデルのルートオブジェクト)と実装INotifyDataErrorInfoを示していErrorsContainerます。

 public abstract class DomainObject : INotifyPropertyChanged, INotifyDataErrorInfo { private ErrorsContainer<ValidationResult> errorsContainer = new ErrorsContainer<ValidationResult>( pn => this.RaiseErrorsChanged(pn)); public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; public bool HasErrors { get { return this.ErrorsContainer.HasErrors; } } public IEnumerable GetErrors(string propertyName) { return this.errorsContainer.GetErrors(propertyName); } protected void RaiseErrorsChanged(string propertyName) { var handler = this.ErrorsChanged; if (handler != null) { handler(this, new DataErrorsChangedEventArgs(propertyName)); } } ... } 

Silverlightでは、ビューモデルのプロパティに関連付けられたコントロールデータは、イベントに自動的にサブスクライブしINotifyDataErrorInfo、プロパティにエラーが含まれている場合、コントロールにエラー情報を表示します。

作成と集約


MVVMパターンは、UIをプレゼンテーション、ビジネスロジック、およびデータから分離するのに役立つため、適切なコードを適切なクラスに配置することは、このパターンを効果的に使用するための重要な最初のステップです。データとコマンドをバインドすることにより、プレゼンテーションモデルのクラスとプレゼンテーションの間の相互作用を管理することも、考慮する重要な側面です。次のセクションでは、実行時にビュー、ビューモデル、モデルクラスがどのように作成され、相互作用するかを示します。
ご注意
, . Managed Extensibility Framework (MEF) Unity Application Block (Unity) , , . , . 6, « MVVM».

通常、ビューとそのプレゼンテーションモデルの間には直接的な関係があります。プレゼンテーションモデルとプレゼンテーションは、プレゼンテーションデータのコンテキストプロパティによって疎結合されています。これにより、ビューの視覚的な要素と動作を、ビューモデルのプロパティ、コマンド、およびメソッドに関連付けることができます。ビューのモデルクラスの作成と、DataContext実行時のプロパティ介したビューとそれらの関連付けの作成を制御する方法を決定する必要があります。

また、プレゼンテーションとプレゼンテーションモデルを作成して接続するときは、弱い結合が残るように注意する必要があります。前のセクションで述べたように、プレゼンテーションモデルはビューの特定の実装に依存するべきではありません。同様に、プレゼンテーションは、プレゼンテーションモデルの特定の実装に依存すべきではありません。
ご注意
ただし、ビューは、データバインディングにより、ビューモデルの特定のプロパティ、コマンド、およびメソッドに暗黙的に依存することに注意してください。ビューモデルが必要なプロパティ、コマンド、またはメソッドを実装していない場合、実行時に例外が生成され、デバッグ中にVisual Studioの出力ウィンドウに表示されます。

ビューとプレゼンテーションモデルを実行時に作成およびリンクする方法は多数あります。アプリケーションに最適な方法を選択するかどうかは、ビューまたはプレゼンテーションモデルを最初に作成するかどうか、およびプログラムで作成するか宣言的に作成するかによって大きく異なります。次のセクションでは、実行時にプレゼンテーションクラスとプレゼンテーションモデルを作成し、相互にリンクする一般的な方法について説明します。

XAMLを介してビューモデルを作成する


おそらく、提示する最も簡単なアプローチは、XAMLで対応する表現モデルを宣言的に作成することです。ビューが作成されると、対応するビューモデルオブジェクトも作成されます。ビューモデルがビューデータのコンテキストとして設定されるようにXAMLで指定することもできます。

XAMLベースのアプローチはQuestionnaireView.xaml、Basic MVVM QuickStartのファイル示されています。その例では、次のようにインスタンスQuestionnaireViewModelがXAML QuestionnaireView定義されています。

 <UserControl.DataContext> <my:QuestionnaireViewModel/> </UserControl.DataContext> 

ときにQuestionnaireView作成され、コピーがQuestionnaireViewModel自動的に作成され、データコンテキスト表現としてインストールされます。このアプローチでは、ビューモデルにデフォルトコンストラクター(パラメーターなし)が必要です。

ビューによるプレゼンテーションモデルの宣言的な作成と割り当てには、シンプルであり、Microsoft Expression BlendやMicrosoft Visual Studioなどのデザイン時ツールでうまく機能するという利点があります。このアプローチの欠点は、ビューが対応するプレゼンテーションモデルを認識していることです。

プログラムでプレゼンテーションモデルを作成する


ビューは、コンストラクターでプログラムによってビューモデルの対応するインスタンスを作成します。次の例に示すように、データコンテキストとして設定できます。

 public QuestionnaireView() { InitializeComponent(); this.DataContext = new QuestionnaireViewModel(); } 


コードビハインドビュー内でビューモデルをプログラムで作成して割り当てることには、この方法が簡単で、Expression BlendやVisual Studioなどのデザイン時ツールでうまく機能するという利点があります。このアプローチの欠点は、ビューがビューモデルの適切なタイプを知る必要があり、分離コードビューのコードを必要とすることです。UnityやMEFなどの依存性注入コンテナーを使用すると、ビューモデルとビューの間に弱いリンクを提供できます。詳細については、第3章「コンポーネント間の依存関係の管理」を参照してください。

データテンプレートとして定義されたビューの作成


ビューはデータテンプレートとして定義でき、ビューモデルのタイプに関連付けられます。データテンプレートは、ビューモデルを表示するコントロール内のリソースまたは埋め込みとして定義できます。コントロールの「コンテンツ」はプレゼンテーションモデルのインスタンスであり、データテンプレートを使用して視覚化します。 WPFおよびSilverlightは、データテンプレートを自動的に作成し、そのデータコンテキストを実行時にビューモデルのインスタンスに設定します。このメソッドは、リプレゼンテーションモデルが最初に作成され、次にリプレゼンテーションモデルが作成される状況の例です。

データテンプレートは柔軟で軽量です。 UI開発者はこれらを使用して、複雑なコードなしでプレゼンテーションモデルの視覚的表現を簡単に定義できます。データテンプレートは、UI(分離コード)ロジックを必要としないビューに限定されます。データテンプレートの視覚的なデザインと編集には、Microsoft Expression Blendを使用できます。

次の例はItemsControl、顧客リストに関連付けられているものを示しています。基本コレクションの各コンシューマオブジェクトは、プレゼンテーションモデルのインスタンスです。クライアントのプレゼンテーションは、組み込みのデータテンプレートによって決定されます。次の例では、各コンシューマプレゼンテーションモデルのプレゼンテーションStackPanelName、プレゼンテーションモデルのプロパティに関連付けられたラベルとテキストボックスで構成されています

 <ItemsControl ItemsSource="{Binding Customers}"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock VerticalAlignment="Center" Text="Customer Name: " /> <TextBox Text="{Binding Name}" /> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> 


データテンプレートをリソースとして指定することもできます。次の例は、リソースとして定義され、マークアップ拡張機能を介してコントロールに適用されるデータテンプレートを示していますStaticResource

 <UserControl ...> <UserControl.Resources> <DataTemplate x:Key="CustomerViewTemplate"> <local:CustomerContactView /> </DataTemplate> </UserControl.Resources> <Grid> <ContentControl Content="{Binding Customer}" ContentTemplate="{StaticResource CustomerViewTemplate}" /> </Grid> </Window> 

ここで、データテンプレートは特定のタイプのビューをラップするため、ビューで分離コードの動作を定義できます。したがって、テンプレート化されたデータエンジンを使用して、プレゼンテーションとプレゼンテーションモデルの関係を表面的に提供できます。前の例ではresources UserControlテンプレートを示していますが、多くの場合、アプリケーションリソースに配置したりResourceDictionary、再利用したりします。データテンプレートを使用した例では、ビューが作成され、QuickStart MVVM ファイルでビューモデルに関連付けられていることがわかりますQuestionnaireView.xaml

重要な決定


アプリケーションの作成時にMVVMパターンを使用することにした場合、後で変更するのが難しい特定の設計上の決定を行う必要があります。通常、これらはアプリケーション全体のソリューションであり、アプリケーション全体で一貫して使用することで、開発者とデザイナーの生産性が向上します。以下は、MVVMパターンを実装する際の最も重要な決定事項です。


WPFでデータバインディングの詳細については、MSDNに«データバインディング»参照:http://msdn.microsoft.com/en-us/library/ms750612.aspx

:Silverlightでのデータバインディングについては、MSDNの«データバインディング»チェックhttp://msdn.microsoft.com/en-us/library/cc278072(VS.95).aspx

:WPFでのコレクションへのリンクの詳細については、MSDNに«概要データバインディング»で«コレクションへの結合»参照http://msdn.microsoft.com/en-us/library/ms752347.aspx

:Silverlightでのコレクションへのリンクの詳細については、MSDNに«データバインディング»で«コレクションへの結合»参照http://msdn.microsoft.com/en-us/library/cc278072(VS.95).aspx

プレゼンテーションモデルの詳細については、Martin FowlerのWebサイトの「プレゼンテーションモデル」を参照してください。http //www.martinfowler.com/eaaDev/PresentationModel.html

データテンプレートの詳細については、MSDNの「データテンプレートの概要」を参照してください。 :http :

//msdn.microsoft.com/en-us/library/ms742521.aspx MEFの詳細については、MSDNの「Managed Extensibility Framework Overview」を参照してください http : //msdn.microsoft.com/en-us /library/dd460648.aspx

ユニティの詳細については、MSDNの«ユニティアプリケーションブロック»参照:http://www.msdn.com/unityの

詳細については、DelegateCommandCompositeCommand一部9を参照してください"疎結合コンポーネント間の通信。」

Source: https://habr.com/ru/post/J176867/


All Articles