Model-View-ViewModel パターン
Note
この電子ブックは 2017 年春に発行されたもので、その後は改訂されていません。 このブックの内容の多くは現時点でも有用ですが、一部の記載内容は古くなっています。
Xamarin.Forms 開発者エクスペリエンスでは、通常、XAML でユーザー インターフェイスを作成してから、そのユーザー インターフェイスで動作するコードビハインドを追加します。 アプリが変更され、そのサイズとスコープが拡大するにつれ、メンテナンス上の複雑な問題が発生することがあります。 これらの問題には、UI コントロールとビジネス ロジックとの密結合が含まれます。これにより、UI の変更を行うコストが増加し、このようなコードを単体テストすることが困難になります。
Model-View-ViewModel (MVVM) パターンを利用すると、アプリケーションのビジネスおよびプレゼンテーション ロジックを、そのユーザー インターフェイス (UI) から明確に分離できます。 アプリケーション ロジックと UI 間の明確な分離を維持することは、開発に関する多くの問題に対処しやすくするとともに、アプリケーションのテスト、保守、進化を容易にします。 これはまた、コード再利用の機会を大幅に増やし、開発者や UI デザイナーがアプリのそれぞれの担当部分を開発する際の共同作業を、より簡単に行なえるようにします。
MVVM パターン
MVVM パターンには、モデル、ビュー、ビュー モデルの 3 つの主要なコンポーネントがあります。 それぞれが異なる目的を果たします。 図 2-1 は、3 つのコンポーネント間の関係性を示しています。
図 2-1: MVVM パターン
各コンポーネントの役割を理解することに加え、それらが互いにどのようなやりとりを行なうのかを理解することも重要です。 大まかに言うと、ビューはビュー モデルを "認識" し、ビュー モデルはモデルを "認識" しますが、モデルはビュー モデルを認識しておらず、ビュー モデルはビューを認識していません。 したがって、ビュー モデルではビューをモデルから分離させ、ビューとは独立してモデルを進化させることができます。
MVVM パターンを使用する利点は次のとおりです。
- 既存のビジネス ロジックをカプセル化するモデル実装が既に存在している場合、それを変更することには、困難やリスクを伴う場合があります。 このようなシナリオでは、ビュー モデルがモデル クラスのアダプターとして機能することで、モデル コードには大きな変更を加えなくても良いようになります。
- 開発者は、ビューを使用せずに、ビュー モデルとモデルの単体テストを作成できます。 ビュー モデルの単体テストでは、ビューで使用されるのとまったく同じ機能を実行できます。
- ビューが XAML で完全に実装されている場合は、コードに触れることなくアプリ UI を再設計できます。 したがって、新しいバージョンのビューは、既存のビュー モデルで動作する必要があります。
- 開発のプロセス中、デザイナーと開発者は、それぞれのコンポーネントについて独立して、または同時に作業ができます。 デザイナーはビューに集中でき、開発者はビュー モデルとモデルのコンポーネントに取り組むことができます。
MVVM を効果的に使用する鍵は、アプリ コードを適切なクラスに組み込む方法と、クラスがどのようにやりとりするかを理解することにあります。 次のセクションでは、MVVM パターンの各クラスの役割について説明します。
表示
ビューには、ユーザーが画面に表示するものの構造、レイアウト、外観を定義する役割があります。 各ビューが XAML で定義され、ビジネス ロジックを含まない分離コードが限られていることが理想的です。 しかし、場合によっては、分離コードには、アニメーションなどの XAML で表現するのが困難な視覚的なビヘイビアーを実装する UI ロジックが含まれていることがあります。
Xamarin.Forms アプリケーションでは、ビューは通常、Page
の派生クラスまたは ContentView
の派生クラスです。 しかし、ビューは、表示時にオブジェクトを視覚的に表すために使用される UI 要素を指定するデータ テンプレートで表すこともできます。 ビューとしてのデータ テンプレートには分離コードがないため、特定のビュー モデル型にバインドするように設計されています。
ヒント
分離コードで UI 要素を有効または無効にすることは避けてください。 ビュー モデルには、ビューの表示の一部の側面 (コマンドが使用可能かどうかや操作が保留中であることを示すなど) に影響を与える、論理状態の変化を定義する役割があるということを理解してください。 したがって、分離コードで有効または無効にするのではなく、モデルのプロパティを表示するためにバインドすることで、UI 要素を有効または無効にします。
ボタンのクリックや項目の選択など、ビューでの操作に応じてビュー モデルでコードを実行するためのいくつかのオプションがあります。 コントロールがコマンドをサポートしているのであれば、そのコントロールの Command
プロパティは、ビュー モデルのICommand
プロパティにデータバインドできます。 コントロールのコマンドが呼び出されると、ビュー モデル内のコードが実行されます。 コマンドに加えて、ビヘイビアーはビュー内のオブジェクトに対しアタッチでき、また呼び出されるコマンドまたは発生するイベントをリッスンできます。 これにより、ビヘイビアーでは、ビュー モデル、またはそのメソッドで ICommand
を呼び出すことができます。
ViewModel
ビュー モデルでは、ビューでデータ バインドできるプロパティとコマンドを実装し、変更通知イベントを通じて状態の変更をビューに通知します。 ビュー モデルで提供されるプロパティとコマンドでは、UI によって提供される機能を定義しますが、その機能の表示方法はビューで決定されます。
ヒント
非同期操作で UI の応答性を維持します。 モバイル アプリでは、パフォーマンスに関するユーザーの認識を向上させるために、UI スレッドのブロックを解除したままにする必要があります。 したがって、ビュー モデルでは、I/O 操作に非同期メソッドを使用し、イベントを発生させ、プロパティの変更をビューに非同期的に通知します。
ビュー モデルには、必要なモデル クラスとビューのやりとりを調整する役割もあります。 通常、ビュー モデルとモデル クラスの間には一対多の関係があります。 ビュー モデルでは、ビュー内のコントロールが直接データ バインドできるように、モデル クラスをビューに直接公開することを選択する場合があります。 この場合、モデル クラスは、データ バインディングと変更通知イベントをサポートするように設計される必要があります。
各ビュー モデルでは、ビューで簡単に使用できる形式のモデルからのデータが提供されます。 これを実現するために、ビュー モデルではデータ変換を行うことがあります。 ビュー モデルにこのデータ変換を配置することをお勧めします。これにより、ビューがバインドできるプロパティが提供されるためです。 たとえば、ビュー モデルでは、2 つのプロパティの値を組み合わせて、ビューごとの表示を容易にすることができます。
ヒント
変換レイヤーでデータ変換を一元化します。 また、ビュー モデルとビューの間に位置する個別のデータ変換レイヤーとしてコンバーターを使用することもできます。 これは、たとえば、ビュー モデルで提供されない特殊な書式がデータに必要な場合などに必要になることがあります。
ビュー モデルがビューとの双方向データ バインディングに参加するには、そのプロパティで PropertyChanged
イベントを発生させる必要があります。 ビュー モデルは、INotifyPropertyChanged
インターフェイスを実装し、プロパティが変更されたときに PropertyChanged
イベントを発生させることで、この要件を満たします。
コレクションの場合は、ビュー フレンドリな ObservableCollection<T>
が提供されます。 このコレクションでは、コレクションの変更通知を実装するため、開発者はコレクションに INotifyCollectionChanged
インターフェイスを実装する必要がなくなります。
モデル
モデル クラスは、アプリのデータをカプセル化する非ビジュアル クラスです。 したがって、このモデルはアプリのドメイン モデルを表すものと考えることができます。通常、これにはビジネスおよび検証ロジックと共にデータ モデルが含まれます。 モデル オブジェクトの例として、データ転送オブジェクト (DTO)、単純な従来の CLR オブジェクト (POCO)、生成されたエンティティおよびプロキシ オブジェクトなどがあります。
通常、モデル クラスは、データ アクセスとキャッシュをカプセル化するサービスまたはリポジトリと組み合わせて使用されます。
ビューへのビュー モデルの接続
Xamarin.Forms のデータバインディング機能を使用すると、ビュー モデルをビューに接続できます。 ビューを構築し、モデルを表示し、実行時にそれらを関連付けるために使用できる多くのアプローチがあります。 これらのアプローチは、ビューの最初のコンポジション、およびビュー モデルの最初のコンポジションと呼ばれる 2 つのカテゴリに分類されます。 ビューの最初のコンポジションとビュー モデルの最初のコンポジションの選択では、優先順位と複雑さが問題となります。 しかし、すべてのアプローチで同じ目的が共有されます。これは、ビューで BindingContext プロパティにビュー モデルを割り当てることです。
ビューの最初のコンポジションでは、アプリは概念的に、それらが依存するビュー モデルに接続するビューで構成されます。 このアプローチの主な利点は、ビュー モデルがビュー自体に依存しないため、疎結合の単体テスト可能なアプリを簡単に構築できることです。 また、クラスがどのように作成されて関連付けられるかを理解するためにコードの実行を追跡する必要はなく、その視覚的な構造に従ってアプリの構造を理解することも簡単です。 さらに、ビューの最初のコンストラクションは、ナビゲーションが発生したときにページを構築する役割がある Xamarin.Forms のナビゲーション システムに合わせて調整されます。これにより、ビュー モデルの最初のコンポジションが複雑になり、プラットフォームとの不整合が生じます。
ビュー モデルの最初のコンポジションでは、アプリは概念的にビュー モデルで構成されます。これには、ビュー モデルのビューの場所を特定する役割を担うサービスが含まれています。 ビュー モデルの最初のコンポジションは、ビューの作成を抽象化できるため、一部の開発者にはより自然に感じられ、アプリの論理的な非 UI 構造に集中できます。 さらに、ビュー モデルを他のビュー モデルで作成することもできます。 しかし、多くの場合、このアプローチは複雑であり、アプリのさまざまな部分がどのように作成され関連付けられているか理解することが、難しくなる可能性があります。
ヒント
ビュー モデルとビューを独立した状態に保ちます。 データ ソース内のプロパティへのビューのバインドは、ビューの対応するビュー モデルに対する主な依存関係である必要があります。 具体的には、ビュー モデルからのビューの型 (Button
および ListView
など) を参照しないようにします。 ここで概説する原則に従うことで、ビュー モデルを分離してテストできるため、スコープを制限することでソフトウェアの欠陥の可能性を減らすことができます。
次のセクションでは、ビュー モデルをビューに接続する主なアプローチについて説明します。
ビュー モデルを宣言的に作成する
最も簡単なアプローチは、ビューで、XAML で対応するビュー モデルを宣言的にインスタンス化することです。 ビューが構築されると、対応するビュー モデル オブジェクトも構築されます。 このアプローチは次のコード例で示されています。
<ContentPage ... xmlns:local="clr-namespace:eShop">
<ContentPage.BindingContext>
<local:LoginViewModel />
</ContentPage.BindingContext>
...
</ContentPage>
ContentPage
が作成されると、LoginViewModel
のインスタンスが自動的に構築され、ビューの BindingContext
として設定されます。
ビューによるこの宣言型の構築とビュー モデルの割り当ては、簡単であるという利点がありますが、ビュー モデルに既定の (パラメーターレス) コンストラクターが必要であるという欠点があります。
ビュー モデルをプログラムにより作成する
ビューでは分離コード ファイルの中にコードを保持できます。その結果、ビュー モデルがその BindingContext
プロパティに割り当てられます。 これは、多くの場合、次のコード例に示すように、ビューのコンストラクターで実現されます。
public LoginView()
{
InitializeComponent();
BindingContext = new LoginViewModel(navigationService);
}
ビューの分離コード内でのビュー モデルのプログラムによる構築と割り当てには、簡単であるという利点があります。 しかし、このアプローチの主な欠点は、ビューで必要な依存関係をビュー モデルに提供する必要があることです。 依存関係挿入コンテナーを使用すると、ビューとビュー モデルの間の疎結合を維持するのに役立つ場合があります。 詳細情報については、「依存性の挿入」を参照してください。
データ テンプレートとして定義されたビューを作成する
ビューは、データ テンプレートとして定義でき、また、ビュー モデルの種類に関連付けることができます。 データ テンプレートはリソースとして定義することも、ビュー モデルを表示するコントロール内でインラインで定義することもできます。 コントロールのコンテンツはビュー モデルのインスタンスであり、これを視覚的に表すにはデータ テンプレートが使用されます。 この手法は、最初にビュー モデルがインスタンス化され、その後にビューが作成されるような状況の一例です。
ビュー モデル ロケーターを使用してビュー モデルを自動的に作成する
ビュー モデル ロケーターは、ビュー モデルのインスタンス化と、そのモデルとビューとの関連付けを管理するカスタム クラスです。 eShopOnContainers モバイル アプリ内の ViewModelLocator
クラスには、ビュー モデルをビューに関連付けるために使用される添付プロパティ AutoWireViewModel
があります。 次のコード例に示すように、ビューの XAML では、この添付プロパティは true に設定さており、ビュー モデルとビューが自動的に接続される必要があることを示しています。
viewModelBase:ViewModelLocator.AutoWireViewModel="true"
この AutoWireViewModel
プロパティは、バインド可能なプロパティであり false に初期化されます。この値が変更された場合は、OnAutoWireViewModelChanged
イベント ハンドラーが呼び出されます。 このメソッドは、ビューに対するビュー モデルの解決を処理します。 次のコード例で、この処理がどのように達成されるかを示します。
private static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as Element;
if (view == null)
{
return;
}
var viewType = view.GetType();
var viewName = viewType.FullName.Replace(".Views.", ".ViewModels.");
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var viewModelName = string.Format(
CultureInfo.InvariantCulture, "{0}Model, {1}", viewName, viewAssemblyName);
var viewModelType = Type.GetType(viewModelName);
if (viewModelType == null)
{
return;
}
var viewModel = _container.Resolve(viewModelType);
view.BindingContext = viewModel;
}
OnAutoWireViewModelChanged
メソッドは、規則ベースのアプローチを使用してビュー モデルの解決を試みます。 この規則では、次のことを前提とします。
- ビュー モデルが、ビューの型と同じアセンブリ内にある。
- ビューは、子の名前空間 .Views 内にある。
- ビュー モデルは、子の名前空間 .ViewModels 内にある。
- ビュー モデル名はビュー名に対応し、末尾が "ViewModel" である。
最後に、この OnAutoWireViewModelChanged
メソッドは、ビューの型の BindingContext
を解決済みのビュー モデル型に設定 します。 ビュー モデルの型の解決については、「解決」を参照してください。
この方法には、アプリが、ビュー モデルのインスタンス化とビューへの接続のために、単一のクラスを使用するという利点があります。
ヒント
置換を容易にするためには、ビュー モデル ロケーターを使用します。 ビュー モデル ロケーターは、単体テストやデザイン時データなど、依存関係の代替実装の置換ポイントとしても使用できます。
基になるビュー モデルまたはモデルの変更に応じてビューを更新する
ビューにアクセスできるすべてのビュー モデルおよびモデル クラスでは、INotifyPropertyChanged
インターフェイスを実装する必要があります。 ビュー モデルまたはモデル クラスにこのインターフェイスを実装すると、基になるプロパティ値が変更されたときに、ビュー内のデータバインド コントロールに変更通知を提供できます。
アプリは、次の要件を満たすことで、プロパティ変更通知を正しく使用できるように設計される必要があります。
- パブリック プロパティの値が変更された場合でも、常に
PropertyChanged
イベントを発生させる。 XAML バインディングがどのように発生するかがわかっているのでPropertyChanged
イベントを発生させることを無視できると思い込まないでください。 - ビュー モデルまたはモデルの他のプロパティで値が使用される計算プロパティの
PropertyChanged
イベントを常に発生させる。 - プロパティを変更するメソッドの最後に、またはオブジェクトが安全な状態であることが判明した場合は、常に
PropertyChanged
イベントを発生させる。 イベントを発生させると、そのイベントのハンドラーを同期的に呼び出すことによって操作が中断されます。 これが操作の途中で発生した場合、オブジェクトが安全ではなく、部分的に更新された状態のときにコールバック関数に公開される可能性があります。 さらに、カスケード変更がPropertyChanged
イベントによってトリガーされる可能性があります。 カスケード変更では、通常、カスケード変更を安全に実行する前に更新を完了する必要があります。 - プロパティが変更されない場合でも、
PropertyChanged
イベントを発生させない。 つまり、PropertyChanged
イベントを発生させる前に、古い値と新しい値を比較する必要があります。 - プロパティを初期化する場合は、ビュー モデルのコンストラクター中に
PropertyChanged
イベントを発生させない。 ビュー内のデータバインド コントロールは、この時点で変更通知を受信するためにサブスクライブされていません。 - クラスのパブリック メソッドの単一の同期呼び出し内で、同じプロパティ名引数を持つ複数の
PropertyChanged
イベントを発生させない。 たとえば、バッキング ストアが_numberOfItems
フィールドであるNumberOfItems
プロパティの場合、ループの実行中にメソッドで 50 回_numberOfItems
がインクリメントされた場合、すべての作業が完了した後、NumberOfItems
プロパティに対してプロパティ変更通知を 1 回だけ発生させる必要があります。 非同期メソッドの場合は、非同期継続チェーンの各同期セグメントで、特定のプロパティ名に対してPropertyChanged
イベントを発生させます。
eShopOnContainers モバイル アプリでは、次のコード例に示すように、ExtendedBindableObject
クラスを使用して変更通知を提供しています。
public abstract class ExtendedBindableObject : BindableObject
{
public void RaisePropertyChanged<T>(Expression<Func<T>> property)
{
var name = GetMemberInfo(property).Name;
OnPropertyChanged(name);
}
private MemberInfo GetMemberInfo(Expression expression)
{
...
}
}
Xamarin.Form の BindableObject
クラスは、INotifyPropertyChanged
インターフェイスの実装と、OnPropertyChanged
メソッドの提供を行ないます。 この ExtendedBindableObject
クラスでは、プロパティ変更通知を呼び出す RaisePropertyChanged
メソッドを提供します。その場合、BindableObject
クラスによって提供される機能が使用されます。
eShopOnContainers モバイル アプリの各ビュー モデル クラスは、それ自体が ExtendedBindableObject
クラスから派生した ViewModelBase
クラスから 派生しています。 したがって、各ビュー モデル クラスでは、ExtendedBindableObject
クラス内の RaisePropertyChanged
メソッドを使用してプロパティ変更通知を提供します。 次のコード例は、eShopOnContainers モバイル アプリがラムダ式を使用して、プロパティ変更通知をどのように呼び出しているかを示しています。
public bool IsLogin
{
get
{
return _isLogin;
}
set
{
_isLogin = value;
RaisePropertyChanged(() => IsLogin);
}
}
ラムダ式は呼び出しごとに評価される必要があるため、この方法でラムダ式を使用すると、パフォーマンス的に若干の コストがかかるという点には注意してください。 このパフォーマンス的コストは小さく、通常はアプリに影響しませんが、多くの変更通知がある場合には、コストを増加させる可能性があります。 しかし、このアプローチの利点は、プロパティの名前を変更するときにコンパイル時の型の安全性とリファクタリングのサポートが提供されることです。
コマンドとビヘイビアーを使用した UI 操作
モバイル アプリにおいて、アクションは通常、ボタン クリックなどのユーザー アクションに応じて呼び出されます。これは、分離コード ファイルにイベント ハンドラーを作成することによって実装できます。 しかし、MVVM パターンでは、アクションを実装する役割はビュー モデルにあり、分離コードにコードを配置することは避ける必要があります。
コマンドでは、UI のコントロールにバインドできるアクションを表す便利な方法が提供されます。 これは、アクションを実装するコードをカプセル化し、また、ビュー内の視覚的表現からアクションを切り離しておくのに役立ちます。 Xamarin.Forms には、コマンドに宣言的に接続できるコントロールが含まれており、これらのコントロールは、ユーザーがコントロールを操作した際にコマンドを呼び出します。
ビヘイビアーでは、コントロールをコマンドに宣言的に接続することもできます。 しかし、ビヘイビアーを使用して、コントロールによって発生するイベントの範囲に関連付けられているアクションを呼び出すことができます。 したがって、ビヘイビアーでは、より高い柔軟性とコントロールを提供しながら、コマンドが有効なコントロールと同じシナリオの多くに対処します。 さらに、ビヘイビアーを使用して、コマンド オブジェクトまたはメソッドを、コマンドとやりとりするように特別に設計されていないコントロールに関連付けることもできます。
コマンドを実装する
しばしばビュー モデルは、ビューからのバインド用のコマンド プロパティを公開します。このプロパティは、ICommand
インターフェイスを実装するオブジェクト インスタンスです。 多くの Xamarin.Forms コントロールでは、ビュー モデルによって提供される ICommand
オブジェクトへのデータ バインドが可能な、Command
プロパティが提供されています。 ICommand
インターフェイスでは、操作自体をカプセル化する Execute
メソッド、コマンドを呼び出すことができるかどうかを示す CanExecute
メソッド、およびコマンドを実行する必要があるかどうかに影響する変更が発生したときに発生する CanExecuteChanged
イベントを定義します。 Xamarin.Forms によって提供される Command
および Command<T>
クラスは ICommand
インターフェイスを実装します。この場合 T
は、Execute
および CanExecute
への引数の型です。
ビュー モデル内には、型 ICommand
のビュー モデル内の各パブリック プロパティのために、Command
または Command<T>
のオブジェクトが存在する必要があります。 Command
または Command<T>
コンストラクターには、ICommand.Execute
メソッドの呼び出し時に呼び出される、Action
コールバック オブジェクトが必要です。 CanExecute
メソッドはオプションのコンストラクター パラメータであり、bool
を返す Func
です。
次のコードは、register コマンドを表す Command
インスタンスを、Register
ビュー モデル メソッドへの委任を指定することによりコンストラクトする方法を示しています。
public ICommand RegisterCommand => new Command(Register);
このコマンドは、ICommand
への参照を返すプロパティを介してビューに公開されます。 Command
オブジェクトで Execute
メソッドが呼び出されると、Command
コンストラクターで指定されたデリゲートを通じて、ビュー モデル内のメソッドへの呼び出しが単に転送されます。
コマンドの Execute
の委任を指定するときに async
と await
キーワードを使用することで、非同期メソッドをそのコマンドで呼び出せるようになります。 これは、コールバックが Task
であり、待機する必要があることを示します。 たとえば、次のコードは、サインイン コマンドを表す Command
インスタンスを、SignInAsync
ビュー モデル メソッドへの委任を指定してコンストラクトする方法を示しています。
public ICommand SignInCommand => new Command(async () => await SignInAsync());
パラメーターは、Command<T>
クラスを使用してコマンドをインスタンス化することで、Execute
と CanExecute
アクションに渡すことができます。 たとえば、次のコードは、NavigateAsync
メソッドで string
型文字列の引数が必要であることを示すために、Command<T>
インスタンスがどのように使用されるのかを示しています。
public ICommand NavigateCommand => new Command<string>(NavigateAsync);
Command
と Command<T>
のいずれのクラスでも、各コンストラクターの CanExecute
メソッドへのデリゲートは省略可能です。 委任が指定されていない場合、Command
では CanExecute
に対して true
が返されます。 しかし、ビュー モデルでは、Command
オブジェクトで ChangeCanExecute
メソッドを呼び出すことによって、コマンドの CanExecute
状態の変更を示すことができます。 これにより、CanExecuteChanged
イベントが発生します。 その後、UI 内でコマンドにバインドされているすべてのコントロールが、データバインド コマンドの可用性を反映するために、有効となっている状態を更新します。
ビューからコマンドを呼び出す
次のコード例は、TapGestureRecognizer
インスタンスを使用して、LoginView
の Grid
が LoginViewModel
クラス内の RegisterCommand
にどのようにバインドされるかを示しています。
<Grid Grid.Column="1" HorizontalOptions="Center">
<Label Text="REGISTER" TextColor="Gray"/>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding RegisterCommand}" NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
</Grid>
CommandParameter
プロパティを使用して、必要に応じてコマンド パラメーターを定義することもできます。 予期される引数の型は、Execute
および CanExecute
ターゲット メソッドで指定されます。 TapGestureRecognizer
では、ユーザーがアタッチされたコントロールを操作すると、ターゲット コマンドが自動的に呼び出されます。 それが指定されている場合、コマンド パラメータは、引数としてコマンドの Execute
の委任に渡されます。
ビヘイビアーを実装する
ビヘイビアーを使用すると、サブクラス化することなく、機能を UI コントロールに追加できます。 代わりに、その機能はビヘイビアー クラスで実装され、それがコントロール自体の一部であるかのようにコントロールにアタッチされます。 ビヘイビアーを使うと、通常は分離コードとして記述する必要があるコードを実装できます。これは、コントロールの API と直接やりとりしているためで、このコードはコントロールに簡潔にアタッチすることができ、複数のビューまたはアプリ間で再利用できるようにパッケージ化されます。 MVVM のコンテキストでは、ビヘイビアーは、コントロールをコマンドに接続するための便利なアプローチです。
アタッチされたプロパティを介してコントロールにアタッチされるビヘイビアーは、''アタッチされたビヘイビアー'' と呼ばれます。 その後、ビヘイビアーでは、アタッチ先の要素の公開された API を使用して、ビューのビジュアル ツリーでそのコントロールまたはその他のコントロールに機能を追加できます。 eShopOnContainers モバイル アプリには、アタッチされたビヘイビアーとして、LineColorBehavior
クラスが含まれています。 この動作の詳細情報については、「検証エラーの表示」を参照してください。
Xamarin.Forms ビヘイビアーは、Behavior
または Behavior<T>
クラスから派生するクラスです。ここで T
は、ビヘイビアーを適用する必要があるコントロールの型です。 これらのクラスでは、OnAttachedTo
および OnDetachingFrom
メソッドが提供されます。これらは、ビヘイビアーがコンロールに対してアタッチおよびデタッチされるときに実行されるロジックを提供するためにオーバーライドする必要があります。
eShopOnContainers モバイル アプリでは、BindableBehavior<T>
クラスは Behavior<T>
クラスから派生しています。 BindableBehavior<T>
クラスの目的は、アタッチ済みのコントロールにビヘイビアーの BindingContext
を設定する必要がある Xamarin.Forms ビヘイビアーに対し、基本クラスを提供することです。
BindableBehavior<T>
クラスでは、ビヘイビアーの BindingContext
を設定するオーバーライド可能な OnAttachedTo
メソッドと、BindingContext
をクリーンアップするオーバーライド可能な OnDetachingFrom
メソッドが提供されます。 さらに、このクラスでは、アタッチされたコントロールへの参照が AssociatedObject
プロパティに格納されます。
eShopOnContainers モバイル アプリには、イベントの発生に応答してコマンドを実行する EventToCommandBehavior
クラスが含まれています。 このクラスは BindableBehavior<T>
クラスから派生し、ビヘイビアーが使用されるときに Command
プロパティで指定された ICommand
にそのビヘイビアーをバインドして実行できるようにします。 次に示すのは、EventToCommandBehavior
クラスのコード例です。
public class EventToCommandBehavior : BindableBehavior<View>
{
...
protected override void OnAttachedTo(View visualElement)
{
base.OnAttachedTo(visualElement);
var events = AssociatedObject.GetType().GetRuntimeEvents().ToArray();
if (events.Any())
{
_eventInfo = events.FirstOrDefault(e => e.Name == EventName);
if (_eventInfo == null)
throw new ArgumentException(string.Format(
"EventToCommand: Can't find any event named '{0}' on attached type",
EventName));
AddEventHandler(_eventInfo, AssociatedObject, OnFired);
}
}
protected override void OnDetachingFrom(View view)
{
if (_handler != null)
_eventInfo.RemoveEventHandler(AssociatedObject, _handler);
base.OnDetachingFrom(view);
}
private void AddEventHandler(
EventInfo eventInfo, object item, Action<object, EventArgs> action)
{
...
}
private void OnFired(object sender, EventArgs eventArgs)
{
...
}
}
OnAttachedTo
および OnDetachingFrom
メソッドは、EventName
プロパティで定義されているイベントのイベント ハンドラーを登録および登録解除するために使用されます。 その後、イベントが発生したときに、コマンドを実行する OnFired
メソッドが呼び出されます。
イベントが発生したときにコマンドを実行するために EventToCommandBehavior
を使用する利点は、コマンドとやりとりするように設計されていないコントロールにコマンドを関連付けられることです。 さらに、これにより、イベント処理コードがビュー モデルに移動され、そこで単体テストすることができます。
ビューからビヘイビアーを呼び出す
EventToCommandBehavior
は、コマンドをサポートしていないコントロールにコマンドをアタッチする場合に特に便利です。 たとえば、次のコードに示すように、ユーザーの注文を一覧表示する ListView
で ItemTapped
イベントが発生した場合、ProfileView
は EventToCommandBehavior
を使って OrderDetailCommand
を実行します。
<ListView>
<ListView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="ItemTapped"
Command="{Binding OrderDetailCommand}"
EventArgsConverter="{StaticResource ItemTappedEventArgsConverter}" />
</ListView.Behaviors>
...
</ListView>
実行時に、EventToCommandBehavior
によって、ListView
とのやりとりとの応答が行われます。 ListView
において項目を選択すると、ItemTapped
イベントが発生し、これによって、ProfileViewModel
の OrderDetailCommand
が実行されます。 既定では、このイベントのイベント引数がコマンドに渡されます。 このデータは、ソースとターゲットの間で渡される際に、EventArgsConverter
プロパティで指定されていて ItemTappedEventArgs
から ListView
の Item
を返すコンバーターによって変換されます。 そのため、OrderDetailCommand
が実行されると、選択された Order
がパラメータとして登録済みアクションに渡されます。
ビヘイビアーの詳細情報については、「ビヘイビアー」を参照してください。
まとめ
Model-View-ViewModel (MVVM) パターンを利用すると、アプリケーションのビジネスおよびプレゼンテーション ロジックを、そのユーザー インターフェイス (UI) から明確に分離できます。 アプリケーション ロジックと UI 間の明確な分離を維持することは、開発に関する多くの問題に対処しやすくするとともに、アプリケーションのテスト、保守、進化を容易にします。 これはまた、コード再利用の機会を大幅に増やし、開発者や UI デザイナーがアプリのそれぞれの担当部分を開発する際の共同作業を、より簡単に行なえるようにします。
MVVM パターンを使用することで、アプリの UI と、基になるプレゼンテーションおよびビジネスのロジックは、3 つの個別クラス (UI と UI ロジックをカプセル化するビュー、プレゼンテーション ロジックと状態をカプセル化するビュー モデル、アプリのビジネス ロジックとデータをカプセル化するモデル) に分離されます。