ビューモデルを使用する
- 8 分
Model-View-ViewModel (MVVM) パターンを構成するコンポーネントについて学習した後は、モデルとビューを定義するのは簡単なことがわかったと思います。 ビューモデルを使用して、パターンでそのロールをより適切に定義する方法を探りましょう。
ユーザー インターフェイスにプロパティを公開する
前の例のように、通常、ビューモデルでは、そのほとんどのデータとあらゆるビジネス ロジックをモデルに依存します。 ただし、現在のビューで必要な方法でデータの書式設定、変換、強化が行われるのはビューモデルです。
ビューモデルを使用して書式設定する
書式設定の例は休暇時間で既に見ました。 日付の書式設定、文字エンコード、シリアル化は、すべて、ビューモデルでモデルからデータの書式設定が行われる場合の例です。
ビューモデルを使用して変換する
多くの場合、モデルでは間接的な方法で情報が提供されます。 しかし、ビューモデルでそれを修正できます。 たとえば、従業員が監督者であるかどうかを画面に表示するとします。 しかし、Employee モデルではそれは直接示されません。 代わりに、その人に、自分への報告を行う他の人が存在するかどうかに基づいて、この事実を推測する必要があります。 モデルに次のプロパティがあるとします。
public IList<Employee> DirectReports
{
get
{
...
}
}
このリストが空の場合は、この Employee は監督者ではないと推測できます。 この場合、EmployeeViewModel には、そのロジックを提供するプロパティ IsSupervisor が含まれます。
public bool IsSupervisor => _model.DirectReports.Any();
ビューモデルを使用して強化する
モデルで関連するデータの ID のみが提供される場合があります。 または、1 つの画面に必要なデータを関連付けるために、複数のモデル クラスを参照することが必要な場合があります。 ビューモデルは、これらのタスクを実行する理想的な場所でもあります。 ある従業員が現在管理しているすべてのプロジェクトを表示したいとします。 このデータは、Employee モデル クラスの一部ではありません。
CompanyProjects モデル クラスを参照することによってアクセスできます。
EmployeeViewModel では、いつものように、その作業がパブリック プロパティとして公開されます。
public IEnumerable<string> ActiveProjects => CompanyProjects.All
.Where(p => p.Owner == _model.Id && p.IsActive)
.Select(p => p.Name);
ビューモデルでパススルー プロパティを使用する
多くの場合、ビューモデルには、モデルが提供するプロパティ が正確 に必要です。 それらのプロパティに対して、ViewModel では単にデータをパススルーします。
public string Name
{
get => _model.Name;
set => _model.Name = value;
}
ビューモデルのスコープを設定する
ビューモデルは、ビューが存在する任意のレベルで使用できます。 通常、ページ には ビューモデルがありますが、ページのサブビューも含まれる可能性があります。 ビューモデルを入れ子にする一般的な理由の 1 つは、ページ上に CollectionView が表示される場合です。 このリストには、EmployeeListViewModel など、コレクションを表す ViewModel があります。 リスト内の各要素は EmployeeViewModel です。
アプリケーション全体のデータと状態を保持しても、特定のページに関連付けられていない、最上位レベルのビューモデルを使うことも一般的です。 そのようなビューモデルは、"アクティブ" な項目を維持するためによく使われます。 先ほど説明した CollectionView の例を考えてみましょう。 ユーザーが従業員行を選択すると、その従業員は 現在のアイテムを表します。 その行を選択したままで、ユーザーが詳細ページに移動したり、ツール バーのボタンを選択したりした場合、そのアクションや表示はその従業員を対象としたものになる必要があります。 このシナリオを処理するための洗練された方法は、ツール バーや詳細ページでもアクセスできるプロパティに CollectionView.SelectedItem をデータ バインドすることです。 中央のビューモデルにそのプロパティを置くとうまくいきます。
ViewModel と View を再利用するタイミングを特定する
ビューモデルとモデル間のリレーションシップ、およびビューモデルとビュー間のリレーションシップを定義する方法は、規則よりアプリの要件によって示されます。 ビューモデルの目的は、必要な構造とデータをビューに提供することです。 その目的によって、ビューモデルのスコープを "どれくらい大きく" するかついての判断が導かれる必要があります。
ビューモデルには、モデル クラスの構造が密接に反映されることがよくあり、そのクラスとの間に一対一のリレーションシップがあります。 前に EmployeeViewModel を使う例について説明しましたが、これでは 1 つの Employee インスタンスをラップして拡張していました。 ただし、常に一対一のリレーションシップになるわけではありません。 ビューモデルがビューに必要な内容を提供するように設計されている場合、どのモデルとも明示的なリレーションシップを持たないものの、"任意の" モデル クラスのデータを使える、HR 部署の概要を示す HRDashboardViewModel のようなものを、代わりに作成できます。
同様に、多くの場合、ビューモデルと ビュー には 1 対 1 のリレーションシップがある場合があります。 しかし、常にそうだとは限りません。 もう一度、各従業員の行を表示する CollectionView について考えてみましょう。 いずれかの行を選択すると、従業員の詳細ページに移動します。
リスト ページには、そのビューモデルとコレクションがあります。 前述のように、そのコレクションは オブジェクトのコレクションであるEmployeeViewModel。 ユーザーが行を選択すると、EmployeeViewModel インスタンスをEmployeeDetailPageに渡すことができます。 詳細ページでは、そのをEmployeeViewModelとして使用BindingContext。
このシナリオは、ビューモデルを再利用する絶好の機会 となる可能性があります 。 ただし、ビューモデルの目的は、ビューに必要なものを提供することであることを忘れないでください。 場合によっては、すべて同じモデル クラスに基づいていても、別のビューモデルを使いたいことがあります。 この例では、CollectionView の行に必要な情報は、詳細ページ全体よりもずっと少なくなりそうです。 詳細ページからデータを取得するとオーバーヘッドが大きすぎる場合は、これらの各ビューを処理する EmployeeListRowViewModel と EmployeeDetailViewModel の両方のモデルを使用するとよい場合があります。
ビューモデル オブジェクト モデル
INotifyPropertyChanged を実装する基底クラスを使用すると、すべてのビューモデルにインターフェイスを再実装する必要はありません。 このトレーニング モジュールの前のパートで説明したように、HR アプリケーションについて考えてみます。
EmployeeViewModel クラスでは INotifyPropertyChanged インターフェイスを実装し、OnPropertyChanged イベントを発生する PropertyChanged という名前のヘルパー メソッドを提供しました。 プロジェクト内の他のビューモデル (たとえば、従業員に割り当てられたリソースを記述するもの) でも、ビューと完全に統合するために INotifyPropertyChanged が必要です。
.NET Community Toolkit の一部である MVVM Toolkit ライブラリは、MVVM パターンを使用して最新のアプリを構築するための最初の実装を提供する、標準の自己完結型の軽量型のコレクションです。
独自のビューモデルの基底クラスを記述する代わりに、ツールキットの ObservableObject クラス (ビューモデルの基底クラスに必要なものがすべて用意されています) から継承します。
EmployeeViewModel は、次から簡素化できます
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));
}
次のコードにします。
using Microsoft.Toolkit.Mvvm.ComponentModel;
public class EmployeeViewModel : ObservableObject
{
private string _name;
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
}
MVVM Toolkit で提供されるソース ジェネレーターを使用して、コードをさらに簡略化できます。 クラス partial を作成し、[ObservableProperty] を private 変数に追加することで、適切なプロパティの変更が通知されるパブリック プロパティ Name が生成されます。
using Microsoft.Toolkit.Mvvm.ComponentModel;
public partial class EmployeeViewModel : ObservableObject
{
[ObservableProperty]
private string _name;
}
MVVM Toolkit は、CommunityToolkit.Mvvm NuGet パッケージを通じて配布されます。