Hilo での Model-View-ViewModel (MVVM) パターンの使用 (C++ と XAML を使った Windows ストア アプリ)

上位のページ: C++ と XAML を使ったエンド ツー エンドの Windows ストア アプリの開発: Hilo

patterns & practices ロゴ

前のページ | 次の ページ

プロジェクトの初期に、Hilo のアーキテクチャには Model-View-ViewModel (MVVM) パターンを採用することを決定しました。決め手となったのは、 MVVM パターンを利用すると、特に規模が拡大する場合に、 C++ と XAML を使った Windows ストア アプリの保守とテストが 簡単になることでした。MVVM は、C++ アプリ用の比較的新しいパターンです。

ダウンロード

Hilo サンプルのダウンロード
ブック (PDF) のダウンロード

コードをダウンロードしたら、「Hilo の概要」で手順をご覧ください。

説明する内容

  • Windows ストア アプリにおける MVVM の利点。
  • MVVM パターンを Windows ストア アプリに適用するための お勧めの手法。
  • ビューを UI 要素にマップする方法。
  • 複数のビューでビュー モデルを共有する方法。
  • ビュー モデルでコマンドを実行する方法。

適用対象

  • Windows 8 の Windows ランタイム
  • Visual C++ コンポーネント拡張機能 (C++/CX)
  • XAML

MVVM とは

MVVM は、アーキテクチャのパターンです。Martin Fowler によって 導入されたプレゼンテーション モデル パターンを 特殊化したものです。広く知られているモデル - ビュー - コントローラー パターン (MVC) とモデル ビュー プレゼンター (MVP) パターン にも関連しています。

MVVM を使うアプリは、 ビジネス ロジック、UI、プレゼンテーション動作を分離します。

  • モデルは、アプリが 操作するビジネス オブジェクトの状態と動作を 表します。たとえば、Hilo は画像ファイルを読み込んで変更するので、 画像ファイルのデータ型と画像ファイルに対する動作を Hilo のモデルの一部にするのが適しています。
  • ビューは UI 要素を持ち、 アプリのユーザー エクスペリエンスを実装するコードが 含まれています。ビューによって、構造、レイアウト、ユーザーが 画面上で見るものの外観が定義されます。たとえば、グリッド、ページ、 ボタン、テキスト ボックスなどの要素を、ビュー オブジェクトが 管理します。
  • ビュー モデルは、 アプリの状態、アクション、動作をカプセル化します。モデルとビューの 間の分離レイヤーとして、ビュー モデルは機能 します。ビューが使える形式でデータを提供し、 ビューがモデルを操作しなくて済むように モデルを更新します。ビュー モデルはコマンドに応答し、イベントをトリガー します。また、ビューが表示するデータのデータ ソースとしての役割も 果たします。ビュー モデルはビューをサポートするために特別に 構築されます。アプリから UI を除いたものが、ビュー モデルだと考えることが できます。Windows ストア アプリでは、宣言を使って、 ビューを対応するビュー モデルにバインドできます。

以下の図は、ビュー、ビュー モデル、モデル の関係を示しています。

ビュー、ビュー モデル、モデルの関係

[トップに戻る]

Hilo での MVVM

Hilo では、UI のページごとに 1 つのビュー クラスがあります。ページは Windows::UI::Xaml::Controls::Page クラスのインスタンスです。 各ビューには、対応するビュー モデル クラス があります。Hilo のすべてのビュー モデルは、アプリのドメイン モデルを共有します。 このモデルは通常、単にモデルと呼ばれます。モデルは、 ビュー モデルがアプリの機能を実装するために使う クラスで構成されています。

  Hilo のビューとビュー モデルのコードについてのチュートリアルに 直接ジャンプする場合は、「Hilo でのページの作成とページ間の移動 (C++ と XAML を使った Windows ストア アプリ)」をご覧ください。

Visual Studio ソリューションの Hilo.sln には、各 MVVM レイヤーの 名前が付けられたソリューション フォルダーがあります。

Visual Studio のソリューション フォルダー
  • Models フォルダーには、Hilo モデルを構成している .cpp (C++) と .h (C++ ヘッダー) ファイルが含まれています。
  • Views フォルダーには、UI クラスと XAML ファイルが含まれています。
  • ViewModels フォルダーには、アプリのビュー モデル クラスのための .cpp と .h ファイルが含まれています。

MVVM パターンと共に、XAML データ バインディングは、ビュー モデルがページのデータ コンテキストとして機能できるようにします。データ コンテキストは、ページの UI 要素のデータをビューに提供するプロパティを用意します。

  ビューとビュー モデルを接続するためにデータ バインディングを使う必要は ありません。ページ クラスに関連付けられている C++ コードを含むコード ビハインド ファイルを使うこともできます。 コード ビハインド ファイルは、 .xaml.cpp というサフィックスが付いているので、 識別できます。たとえば、Hilo では MainHubView.xaml.cpp というファイルが、 MainHubView.xaml ファイルで定義されているページのコード ビハインド ファイル です。Microsoft Expression など多くのビジュアル デザイン ツールは、 データ バインディングでの使用を目的として 最適化されています。

ビュー モデルは、インスタンス メソッドを呼び出して、アプリの基になる モデルに接続します。これらの呼び出しを行うために、特別なバインドを作成する 必要はありません。モデルとアプリのビュー モデルを厳格に分離する場合は、 モデル クラスを別のライブラリにパッケージ化 できます。Hilo では、モデル用の別のライブラリは 使っていません。代わりに、モデル クラスを定義しているファイルを、 Hilo Visual Studio プロジェクト内の別のフォルダーに保持しています。

[トップに戻る]

Hilo で MVVM を使う理由

UI には、主な実装方法が 2 つあります。プレゼンテーション ロジックにコード ビハインド ファイルを使う方法と、 MVVM パターンのようなパターンで UI 構造と プレゼンテーション ロジックを分離する方法です。ニーズを分析した後で、次のような理由から、Hilo には MVVM 手法を使うことにしました。

  • プレゼンテーション ロジックをテストする必要がありました。 MVVM を使うと、ビュー ロジックを UI コントロールから明確に 分離しやすくなります。これはテストの自動化には重要です。
  • ビューとプレゼンテーション ロジックは 個別に発展させ、UX デザイナーと開発者間の依存関係を 減らす必要がありました。MVVM を XAML のデータ バインディングと共に 使うことで、それが可能になります。

[トップに戻る]

詳しい情報

MVVM については、オンラインで詳しい情報を見つけることができます。以下はいくつかの例です。 マネージ コードのものですが、概念は C++ にも適用できます。

[トップに戻る]

MVVM パターンのバリエーション

MVVM パターンは、さまざまな方法でカスタマイズできます。次のような 例があります。

ビューをページ以外の UI 要素にマップする

Hilo では、各ページ クラスは MVVM ビュー オブジェクトであり、すべての MVVM ビューがページです。しかし、同じようにする必要はありません。たとえば、 ビューは ItemsControl. 内のオブジェクトの DataTemplate にすることもできます。

複数のビューでビュー モデルを共有する

ビューは独自のビュー モデルを持つことも、他のビューのビュー モデルを共有することもできます。どちらにするかは、ビューが多くの共通の機能を共有するかどうかによって決まります。Hilo では、単純化するために、各ビューが 固有のビュー モデルに関連付けられていました。

ビュー モデルでコマンドを実行する

アプリに動作を実行させるボタンやその他の UI コントロールのために、 データ バインディングを使うことができます。 コントロールが コマンド ソースの場合、コントロールの Command プロパティは ビュー モデルの ICommand プロパティにデータ バインディングされます。コントロールのコマンドが呼び出されると、 ビュー モデル内のコードが実行されます。 MVVM を 使う場合は、コマンドにデータ バインディングを使うことを お勧めします。

次に、Hilo でのコマンドの実行の例を示します。画像の 回転のページには、[Save File] ボタンの UI 要素が含まれて います。この XAML コードは、RotateImageView.xaml ファイルのものです。


<Button x:Name="SaveButton"
        x:Uid="AcceptAppBarButton"
        Command="{Binding SaveCommand}" 
        Style="{StaticResource AcceptAppBarButtonStyle}"
        Tag="Save" />


"Command={Binding SaveCommand}" という表現は、ボタンの Command プロパティと RotateImageViewModel クラスの SaveCommand プロパティ間のバインドを作ります。 SaveCommand プロパティには ICommand オブジェクトへのハンドルが含まれます。次のコードは、 RotateImageViewModel.cpp ファイルのものです。


ICommand^ RotateImageViewModel::SaveCommand::get()
{
    return m_saveCommand;
}


m_saveCommand メンバー変数は、RotateImageViewModel クラスのコンストラクターで初期化されます。次のコードは、 RotateImageViewModel.cpp ファイルのものです。


m_saveCommand = ref new DelegateCommand(ref new ExecuteDelegate(this, &RotateImageViewModel::SaveImage), nullptr);


Hilo の DelegateCommand クラスは、ICommand インターフェイスの実装です。このクラスは、ExecuteDelegate デリゲート型を定義しています。デリゲートを利用すると、 C++ メンバー関数へのポインターを、呼び出し可能な Windows ランタイム オブジェクトとして使うことができます。Hilo は、UI がコマンドをトリガーしたときに、ExecuteDelegate を呼び出します。

  C++/CX でのデリゲート言語拡張機能について詳しくは、 「デリゲート (C++/CX)」をご覧ください。

データ バインディングを使っていたため、保存コマンドのアクションの変更は、 m_saveCommand メンバー変数に別の DelegateCommand オブジェクトを割り当てることを意味します。ビューの XAML ファイルを変更する必要はありません。

次の例では、基になるメンバー関数は RotateImageViewModel.cpp ファイルのものです。


void RotateImageViewModel::SaveImage(Object^ parameter)
{
   // Asynchronously save image file
}	  

ビューとビュー モデルをビュー モデル ロケーター オブジェクトを使ってバインドする

Hilo では、各ビュー (ページ) に対応するビュー モデルが あります。

MVVM を使う場合、アプリはビューをその ビュー モデルに接続する必要があります。つまり、各ビューの DataContext プロパティにはビュー モデルが割り当てられている必要があります。Hilo では、UI 要素がビュー モデルにバインドされる前にセットアップ コードを実行する 必要があったので、単一の ViewModelLocator クラスを使いました。ViewModelLocator クラスには、 アプリの各ページのビュー モデル オブジェクトを取得する、いくつかの プロパティがあります。Hilo で ViewModelLocator クラスがどのようにビューとビュー モデルを バインドしているかの説明については、「Hilo でのページの 作成とページ間の移動 (C++ と XAML を使った Windows ストア アプリ)」をご覧ください。

必ずしもビュー モデル ロケーター クラスを使う必要はありません。実際に、 ビューを対応するビュー モデル オブジェクトにバインドする方法は、 いくつかあります。ビュー モデル ロケーター クラスを使わない場合は、 ビュー モデル インスタンスの作成とデストラクションを、 対応するビュー オブジェクトの有効期間に結び付ける ことができます。たとえば、ページが読み込まれるたびに、新しいビュー モデル インスタンスを作成できます。

ビューをコード ビハインド ファイル内のビュー モデルに結び付けることも できます。コード ビハインド ファイル内のコードは、新しいビュー モデル インスタンスをインスタンス化し、 それをビューの DataContext プロパティに割り当てることができます。ページの Initialize メソッドまたは OnNavigatedTo メソッド内で、ビュー モデルをインスタンス化できます。

[トップに戻る]

MVVM を使う Windows ストア アプリの 設計のためのヒント

以下は、C++ で Windows ストア アプリに MVVM パターンを 適用するためのいくつかのヒントです。

ビューがビュー モデルに依存しないようにする

MVVM パターンを使って Windows ストア アプリを設計する場合、 モデルとビューとビュー モデルのそれぞれに何を配置するかを 判断する必要があります。この分割は、 好みの問題であることも少なくありませんが、いくつかの一般的な原則 が適用されます。 理想的には、ビューは XAML で定義し、限定的に、ビジネス ロジックを 含まないコード ビハインドを使います。また、 ビュー モデルが UI 要素やビューのデータ型に依存 しないようにすることもお勧めします。ビュー ヘッダー ファイルをビュー モデル ソース ファイルに含めないようにします。

データ変換をビュー モデルまたは変換レイヤーに 集中化する

ビュー モデルはモデルのデータを、ビューが簡単に使うことができる 形式で提供します。そのために、ビュー モデルはデータ変換を 行わなければならない場合があります。 ビュー モデルには UI をバインド できる形式のプロパティがあるので、この変換は ビュー モデルに配置するのが適しています。

ビュー モデルとビューの間に、別途、データ変換レイヤーを 設けることもできます。このように するのは、たとえば、ビュー モデルでは提供されない 特殊な形式をデータ型が必要とする場合です。

動作モードをビュー モデル内で公開する

ビュー モデルは、ビューの表示のいくつかの面に影響する、 ロジックの状態の変化も定義する場合があります。 たとえば、なんらかの操作が保留になっていることや、特定の コマンドが利用できるかどうかを示す表示などです。UI 要素を 有効にしたり無効にしたりするために、コード ビハインドは必要ありません。 そうした操作は、ビュー モデル プロパティにバインドすることで 行うことができます。

次に例を示します。


<Grid Background="{Binding HasPhotos, Converter={StaticResource BrushConverter}}"
      Height="150"
      IsTapEnabled="{Binding HasPhotos}"
      PointerEntered="OnZoomedOutGridPointerEntered"
      Margin="0"
      Width="150">


この例では、IsTapEnabled 属性がビュー モデルの HasPhoto プロパティにバインドされています。

ビュー モデルが Bindable 属性を持つようにする

ビュー モデルがビューとのデータ バインディングに参加するには、 型が XAML の生成されるファイルに含まれるように、ビュー モデル クラスに Windows::UI::Xaml::Data::Bindable 属性がなければ なりません。

また、 ビュー モデルのヘッダーを直接または間接的に、 App.xaml.h ヘッダー ファイルにインクルードする必要が あります。Hilo では、すべてのビュー モデル ヘッダー ファイルが ViewModelLocator.h ファイルにインクルードされていて、このファイルは App.xaml.h ファイルにインクルードされています。そうすることで、XAML と共に動作するために必要な型が コンパイル時に正しく生成されるようになります。

  C++/CX での属性言語拡張機能について詳しくは、 「ユーザー定義 属性 (C++/CX)」をご覧ください。

次の例は、Bindable 属性を示しています。


[Windows::UI::Xaml::Data::Bindable] 
[Windows::Foundation::Metadata::WebHostHiddenAttribute]
public ref class MainHubViewModel sealed : public ViewModelBase 
{ 
  // ...  
} 	  

データ バインディングが機能するように、ビュー モデルが INotifyProperyChanged インターフェイスを実装するようにする

プロパティの値が変化したことをクライアントに通知する必要があるビュー モデルは、 PropertyChanged イベントを生成しなければなりません。そうするには、ビュー モデル クラスは Windows::UI::Xaml::Data::INotifyPropertyChanged インターフェイスを実装する必要があります。Windows ランタイムは、アプリの実行時に、このイベントのハンドラーを登録します。 Visual Studio では、 INotifyPropertyChanged インターフェイスの実装が、BindableBase テンプレート クラス内に用意されています。このクラスは、任意の XAML データ ソースの 基底クラスとして使うことができます。以下は生成されたヘッダー ファイルで、 BindableBase.h からのものです。



[Windows::Foundation::Metadata::WebHostHidden]
public ref class BindableBase : Windows::UI::Xaml::DependencyObject, Windows::UI::Xaml::Data::INotifyPropertyChanged, Windows::UI::Xaml::Data::ICustomPropertyProvider
{
  public:
    virtual event Windows::UI::Xaml::Data::PropertyChangedEventHandler^ PropertyChanged;

   // ...

  protected:
    virtual void OnPropertyChanged(Platform::String^ propertyName);
};	  

生成された実装は、イベントが生成されるとハンドラーを 呼び出します。


void BindableBase::OnPropertyChanged(String^ propertyName)
{
    PropertyChanged(this, ref new PropertyChangedEventArgs(propertyName));
}	  

  C++/CX には、プログラミング言語の一部として、 イベントとプロパティが用意されています。イベントとプロパティの キーワードが含まれています。Windows ランタイムの型はパブリック インターフェイスで イベントを宣言しており、アプリはそのイベントに登録できます。サブスクライバーは、 パブリッシャーがイベントを発生させると、カスタムのアクションを 実行します。Windows ランタイムをサポートしている C++/CX 機能について詳しくは、 「C++ での Windows ランタイム コンポーネントの作成」をご覧ください。このコード例で 使われている C++/CX のイベント言語拡張機能 について詳しくは、「イベント (C++/CX)」をご覧ください。

ビュー モデル クラスは、BindableBase クラスから派生させることにより、INotifyPropertyChanged の 実装を継承 できます。たとえば、Hilo では次のように ViewModelBase クラスを宣言しています。次のコードは、 ViewModelBase.h ファイルのものです。


public ref class ViewModelBase : public Common::BindableBase
{
  // ...
}	  

バインド プロパティが変化したことをビュー モデルが UI に 伝える必要があるときは常に、BindableBase クラスから継承された OnPropertyChanged メソッドが 呼び出されます。たとえば、RotateImageViewModel クラスでは、次のように Property Set メソッドが 定義されています。


void RotateImageViewModel::RotationAngle::set(float64 value)
{
    m_rotationAngle = value;

    // Derive margin so that rotated image is always fully shown on screen.
    Thickness margin(0.0);
    switch (safe_cast<unsigned int>(m_rotationAngle))
    {
    case 90:
    case 270:
        margin.Top = 110.0;
        margin.Bottom = 110.0;
        break;
    }
    m_imageMargin = margin;
    OnPropertyChanged("ImageMargin");
    OnPropertyChanged("RotationAngle");
}


  XAML へのプロパティ通知は、UI スレッドで行われる必要が あります。つまり、OnPropertyChanged メソッドと、そのすべての呼び出しも、 アプリの UI スレッドで行われる必要があります。一般に、 有益な規則として、ビュー モデルのすべてのメソッドと プロパティは、アプリの UI スレッド内で呼び出すようにします。

ビューとビュー モデルの独立性を維持する

以下に概要を示した原則に従うと、ビューをまったく 変更せずに、ビュー モデルを再実装することが できます。ビューをそのデータ ソースの特定の プロパティにバインドすることが、対応するビュー モデル に対するビューの主な依存関係になります。ビュー モデルで バインド プロパティの名前を変更すると、XAML データ バインディング式でも 同様にその名前を変更しなければなりません。

非同期プログラミング手法を使って、UI の応答性を保つ

Windows ストア アプリで重要なのは、すばやくて滑らかな ユーザー エクスペリエンスです。そのため、Hilo では UI スレッドがブロックされることが ないようにしました。I/O 操作には非同期ライブラリ メソッドを使い、 大量の計算を行う操作では 並列タスクを使いました。 ビューにプロパティの変化を 非同期的に通知するには、イベントを発生させます。

詳しくは、「 Hilo での非同期プログラミング パターンとヒント (C++ と XAML を使った Windows ストア アプリ)」 をご覧ください。

常に Windows ランタイム オブジェクトのスレッドの 規則を守る

Windows ランタイムへの呼び出しによって作成されたオブジェクトは、シングル スレッドである場合があります。つまり、 オブジェクトの作成に使われたスレッド コンテキストで、 メソッド、プロパティ、イベント ハンドラーを呼び出す必要が あります。ほとんどの場合、コンテキストはアプリの UI スレッドです。

エラーを避けるために、Hilo はそのビュー モデルへの呼び出しが アプリの UI スレッドで行われるように設計されました。モデル クラスは、画像処理のような時間のかかる 操作をワーカー スレッドで 行います。

詳しくは、「Hilo での非同期 プログラミング パターンとヒント (C++ と XAML を使った Windows ストア アプリ)」をご覧ください。Windows ストア アプリが使うスレッド モデルについては、 「実行スレッドの 制御」をご覧ください。

Hilo での非同期プログラミングの使用のチュートリアルについては、「Hilo での非同期プログラミング パターンとヒント (C++ と XAML を使った Windows ストア アプリ)」をご覧ください。

[トップに戻る]

 

 

表示:
© 2015 Microsoft