第 18 章の概要。 MVVM

Note

この本は 2016 年春に発行されて以降、改訂されていません。 多くの情報はまだ価値がありますが、一部の資料は古くなっており、トピックの中にはまったく正しくないものまたは不完全なものもあります。

アプリケーションを設計する最良の方法の 1 つは、基になるコード ("ビジネス ロジック" とも呼ばれます) からユーザー インターフェイスを分離することです。 いくつかの手法がありますが、XAML ベースの環境に合った手法は Model-View-ViewModel または MVVM と呼ばれます。

MVVM の相関関係

MVVM アプリケーションには、3 つのレイヤーがあります。

  • Model によって、基になるデータが提供されます。ファイルや Web アクセスが使用される場合があります
  • View はユーザー インターフェイス レイヤーまたはプレゼンテーション層です。通常は XAML で実装されます
  • ViewModel によって、Model と View が接続されます

Model は ViewModel について何も知らず、ViewModel は View について何も知りません。 これら 3 つのレイヤーは、通常、次のメカニズムを使用して相互に接続されます。

View、ViewModel、View

多くの小規模なプログラムでは (より大規模なプログラムでも)、Model が存在しないか、その機能が ViewModel に統合されていることがよくあります。

ViewModel とデータ バインディング

データ バインディングを使用するために、ViewModel では、ViewModel のプロパティが変更されたときに View に通知できる必要があります。 ViewModel では、System.ComponentModel 名前空間の INotifyPropertyChanged インターフェイスを実装することによってこれを行います。 これは、Xamarin.Forms ではなく .NET の一部です。 (通常、ViewModel はプラットフォームに依存しないようにします。)

INotifyPropertyChanged インターフェイスでは、変更されたプロパティを示す PropertyChanged という名前の単一のイベントが宣言されます。

ViewModel の時計

Xamarin.FormsBook.Toolkit ライブラリの DateTimeViewModel によって、タイマーに基づいて変化する DateTime 型のプロパティが定義されます。 このクラスでは INotifyPropertyChanged が実装され、DateTime プロパティが変更されるたびに PropertyChanged イベントが発生します。

MvvmClock サンプルでは、この ViewModel をインスタンス化し、その ViewModel に対してデータ バインディングを使用して、更新された日付と時刻の情報を表示します。

ViewModel の対話型プロパティ

SimpleMultiplier サンプルに含まれている SimpleMultiplierViewModel クラスが示しているように、ViewModel のプロパティはより対話的にすることができます。 データ バインディングによって、2 つの Slider 要素から被乗数と乗数の値が提供され、Label でその積が表示されます。 ただし、XAML でこのユーザー インターフェイスに広範な変更を加えつつ、ViewModel や分離コード ファイルにそれに伴う変更を生じさせないようにすることができます。

カラー ViewModel

Xamarin.FormsBook.Toolkit ライブラリの ColorViewModel では、RGB と HSL のカラー モデルが統合されています。 これは HslSliders サンプルで示されています。

TK のトリプル スクリーンショット

ViewModel の合理化

ViewModel のコードは、呼び出し元のプロパティ名を自動的に取得する CallerMemberName 属性を使用して OnPropertyChanged メソッドを定義することで、合理化できます。 Xamarin.FormsBook.Toolkit ライブラリの ViewModelBase クラスでは、これが行われ、ViewModel の基底クラスが提供されます。

コマンド インターフェイス

MVVM ではデータ バインディングが使用され、データ バインディングではプロパティが使用されます。そのため、MVVM は、ButtonClicked イベントや TapGestureRecognizerTapped イベントを処理する場合には不完全であるように見えます。 ViewModel でこのようなイベントを処理できるようにするために、Xamarin.Forms では "コマンド インターフェイス" がサポートされています。

コマンド インターフェイスは、Button 内で次の 2 つのパブリック プロパティを使用して明示されます。

コマンド インターフェイスをサポートするために、ViewModel では、後で ButtonCommand プロパティにデータ バインディングされる ICommand 型のプロパティを定義する必要があります。 ICommand インターフェイスでは、2 つのメソッドと 1 つのイベントが宣言されています。

ViewModel では、内部的に、ICommand 型の各プロパティが、ICommand インターフェイスを実装するクラスのインスタンスに設定されます。 データ バインディングを通じて、Button では最初に CanExecute メソッドが呼び出され、そのメソッドが false を返す場合はそれ自体が無効になります。 また、CanExecuteChanged イベントのハンドラーも設定され、そのイベントが発生するたびに CanExecute が呼び出されます。 Button が有効になっている場合は、Button がクリックされるたびに Execute メソッドが呼び出されます。

Xamarin.Forms よりも前から存在する ViewModel を利用している場合があり、それらで既に、コマンド インターフェイスがサポートされていることがあります。 Xamarin.Forms とだけ使用することを想定された新しい ViewModel のために、Xamarin.Forms には、ICommand インターフェイスを実装する Command クラスと Command<T> クラスが用意されています。 ジェネリック型は、Execute および CanExecute メソッドに対する引数の型です。

シンプルなメソッドの実行

PowersOfThree サンプルでは、ViewModel でコマンド インターフェイスを使用する方法が示されています。 PowersViewModel クラスでは、ICommand 型の 2 つのプロパティと、最もシンプルな Command コンストラクターに渡される 2 つのプライベート プロパティが定義されています。 このプログラムには、この ViewModel から 2 つの Button 要素の Command プロパティへのデータ バインディングが含まれています。

Button 要素は、コードを変更せずに、XAML で TapGestureRecognizer オブジェクトに簡単に置き換えることができます。

(ほぼ) 電卓

AddingMachine サンプルでは、ICommandExecute メソッドと CanExecute メソッドの両方が使用されています。 Xamarin.FormsBook.Toolkit ライブラリの AdderViewModel クラスが使用されています。 ViewModel には、ICommand 型のプロパティが 6 つ含まれています。 これらは、CommandCommand コンストラクターおよび Command コンストラクターと、Command<T>Command<T> コンストラクターから初期化されます。 足し算プログラムの数値キーは、すべて Command<T> で初期化されるプロパティにバインドされます。また、ExecuteCanExecute に対する string 引数によって、特定のキーが識別されます。

ViewModel とアプリケーションのライフサイクル

AddingMachine サンプルで使用されている AdderViewModel では、SaveStateRestoreState という名前の 2 つのメソッドも定義されています。 これらのメソッドは、アプリケーションがスリープ状態になるときと再び起動するときに呼び出されます。