MVVM とは
MVVM を使わない .NET MAUI アプリでは、一般に、その "分離コード" ファイル内のコードが多くなります。 .NET MAUI の分離コード ファイルは、"{何か}.xaml.cs" というパターンに従います。 分離コード ファイル内のほとんどのコードは、ユーザー インターフェイス (UI) の動作の制御に使われます。 "UI の動作" には、色やテキストの変更など、UI に "対して" 発生するあらゆるものが含まれます。 また、これには、ボタン クリックのハンドラーなど、UI が "原因となって" 発生するあらゆるものが含まれます。
この方法の問題の 1 つは、分離コード ファイルに対する単体テストを作成するのが難しいことです。 多くの場合、分離コード ファイルでは、XAML を解析して作成された、または他のページによって作成されたアプリケーション状態が仮定されます。 モバイル デバイス上で実行すらされていないかもしれない単体テスト ランナーではこのような条件を処理するのは難しく、ユーザー インターフェイスについてはさらに困難です。 そのため、これらのファイルにおける UI の動作を単体テストでテストできることはほとんどありません。
しかし、MVVM パターンが役立つ場所は次のとおりです。 MVVM パターンを正しく使用すると、UI の動作ロジックのほとんどを "ビューモデル" という単体テスト可能なクラスに移動することで、これらの問題を解決できます。 MVVM パターンは、データ バインディングをサポートするフレームワークで最もよく使われます。 それは、.NET MAUI では、各 UI 要素を viewmodel
にデータ バインドして、ビューやコードビハインド内のコードを排除またはほとんど排除できるためです。
MVVM アプリケーションの各部分について
viewmodel
は MVVM パターン独特の部分ですが、このパターンではモデル部分とビュー部分も定義されます。 これらの部分の定義は、Model-View-Controller (MVC) など、その他のいくつかの一般的なパターンと一致しています。
モデルとは何か
MVVM アプリケーションでは、モデルという用語を使用して、ビジネス データと操作を表します。 モデルは、アプリのユーザー プレゼンテーションに関与しません。
モデルに属するコードを決定するための便利なルールは、異なるプラットフォームとの間で移植可能である必要があるということです。 モバイル アプリから Web インターフェイス、またはコマンドライン プログラムまで、すべてのインスタンスで同じ "モデル" を使用します。 これは、ユーザーに情報を表示する方法とは関係ありません。
このシナリオから人事管理システムのアプリケーションについて考えた場合、モデルには Employee
クラスと Department
クラスを含めることができます。それぞれのエンティティに関するデータとロジックがそれらで保持されます。 モデルには、永続化ロジックを保持する EmployeeRepository
クラスのようなものも含めることができます。 他のいくつかのソフトウェア設計パターンでは、"リポジトリ" のようなものがモデルとは別に考慮されます。 しかし、MVVM のコンテキストでは、多くの場合、ビジネス ロジックやビジネス データをモデルの一部と見なします。
C# での Model の例を次に 2 つ示します。
public class Employee
{
public int Id { get; }
public string Name { get; set; }
public Employee Supervisor { get; set; }
public DateTime HireDate { get; set; }
public void ClockIn() { ... }
}
public class EmployeeRepository
{
public IList<Employee> QueryEmployees() { ... }
...
}
ビューとは何か
"ビュー" コードは、ボタンや入力フィールドなどのコントロールや、テーマ、スタイル、フォントなどの純粋に視覚的な要素など、ユーザーと直接やり取りするものを制御します。
.NET MAUI では、ビューを自分で生成するために C# コードを記述する必要はありません。 代わりに、多くの場合、XAML ファイルでビューを定義します。 もちろん、コードを使用して独自のビューを作成するカスタム ユーザー コントロールを呼び出す状況もあります。
viewmodel
とは
これで viewmodel
に戻ってきました。 viewmodel
は、ビジネス ロジック (モデル) とビュー (UI) の間の仲介役です。
HR アプリケーションの場合、viewmodel
がどのように役立つかを考えてみましょう。 たとえば、従業員の利用可能な休暇時間を表示するビューがあり、休暇残高に "2 週間、3 日、4 時間" と示しているとします。しかし、モデルのビジネス ロジックでは、同じ値を 13.5 日として提供します。これは、8 時間の稼働日での合計日数を表す 10 進数です。 オブジェクト モデルは、次の一覧のようになります。
モデル – 次のメソッドを含む
Employee
クラス。public decimal GetVacationBalanceInDays() { //Some math that calculates current vacation balance ... }
ビューモデル – 次のようなプロパティを持つ
EmployeeViewModel
クラス。public class EmployeeViewModel { private Employee _model; public string FormattedVacationBalance { get { decimal vacationBalance = _model.GetVacationBalanceInDays(); ... // Some logic to format and return the string as "X weeks, Y days, Z hours" } } }
ビュー – 1 つのラベルと閉じるボタンを含む XAML ページ。 このラベルには、
viewmodel
のプロパティに対するバインドが含まれます。<Label Text="{Binding FormattedVacationBalance}" />
あとは、
EmployeeViewModel
のインスタンスに設定されたこのページ用のBindingContext
が必要になるだけです。
この例では、モデルに "ビジネス ロジック" が含まれます。 このロジックは、ビジュアル ディスプレイまたはデバイスにはバインドされていません。 ハンドヘルド デバイスまたはデスクトップ コンピューターにも同じロジックを使用できます。 ビューは、ビジネス ロジックについては何も知りません。 ビュー コントロール (ラベルなど) は、画面上のテキストを取得する方法を知っていますが、休暇残高やランダムな文字列かどうかは気にしません。 viewmodel
は、両方の世界を "少し" 理解しているので、仲介役として機能できます。
興味深い点は、viewmodel
が仲介役を果たす方法です。それでは、ビューをバインドできるプロパティが公開されます。 パブリック プロパティは、viewmodel
でデータを提供する唯一の方法です。 viewmodel
と "呼ばれる" ようになったのは、このためです。 MVVM の "モデル" は、ビジネス プロセスの構造、データ、ロジックを表し、viewmodel
は、ビューに必要な構造、データ、ロジックを表します。
viewmodel
でのビューの動作方法
viewmodel
とモデルのリレーションシップを見ると、それは標準のクラス間リレーションシップです。 viewmodel
にはモデルのインスタンスがあり、プロパティを使用してモデルの側面をビューに公開します。 しかし、ビューは viewmodel
のプロパティをどのように取得して設定するのでしょうか? viewmodel
が変更されたとき、ビューはどのようにしてビジュアル コントロールを新しい値で更新するのでしょうか? 答えはデータ バインディングです。
viewmodel
のプロパティは、オブジェクトがビューにバインドされるときに読み取られます。 しかし、バインドがビューに適用された後、viewmodel
のプロパティが変更されたかどうかをバインドで識別する方法はありません。 viewmodel
を変更しても、バインドを介してビューに新しい値が自動的に反映されることはありません。 viewmodel
からビューに更新するには、viewmodel
は System.ComponentModel.INotifyPropertyChanged
インターフェイスを実装する必要があります。
INotifyPropertyChanged
インターフェイスは、PropertyChanged
という名前の 1 つのイベントを宣言します。 それは、1 つのパラメーター (その値を変更したプロパティの名前) を受け取ります。 .NET MAUI で使用されるバインディング システムは、このインターフェイスを理解し、そのイベントをリッスンします。 viewmodel
でプロパティが変更され、イベントが発生すると、バインドによってその変更がターゲットに通知されます。
従業員の viewmodel
を使用した HR アプリケーションで、これがどのように動作するかについて考えてみましょう。 viewmodel
には、従業員を表すプロパティ (従業員の名前など) があります。 viewmodel
が INotifyPropertyChanged
インターフェイスを実装しており、Name
プロパティが変更されると、次のような PropertyChanged
イベントが発生します。
using System.ComponentModel;
public class EmployeeViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private Employee _model;
public string Name
{
get {...}
set
{
_model.Name = value;
OnPropertyChanged(nameof(Name))
}
}
protected void OnPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
従業員の詳細を説明するビューには、viewmodel
の Name
プロパティにバインドされたラベル コントロールが含まれています。
<Label Text="{Binding Name}" />
viewmodel
で Name
プロパティが変更されると、そのプロパティの名前で PropertyChanged
イベントが発生します。 バインディングはイベントをリッスンし、Name
プロパティが変更されたことをラベルに通知します。 その後、ラベルの Text
プロパティが最新の値で更新されます。