モデルビュービューモデルパターンThe Model-View-ViewModel Pattern

注意

この電子ブックは2017の spring で公開されており、その後、更新されていません。This eBook was published in the spring of 2017, and has not been updated since then. 本は貴重なものですが、一部のマテリアルは古くなっています。There is much in the book that remains valuable, but some of the material is outdated.

通常、開発者の経験では、 :::no-loc(Xamarin.Forms)::: XAML でユーザーインターフェイスを作成し、ユーザーインターフェイスで動作する分離コードを追加する必要があります。The :::no-loc(Xamarin.Forms)::: developer experience typically involves creating a user interface in XAML, and then adding code-behind that operates on the user interface. アプリが変更され、サイズと範囲が拡大するにつれて、複雑なメンテナンスの問題が発生する可能性があります。As apps are modified, and grow in size and scope, complex maintenance issues can arise. これらの問題には、ui コントロールとビジネスロジックとの密結合が含まれます。これにより、UI の変更にかかるコストが増加し、このようなコードを単体テストすることが困難になります。These issues include the tight coupling between the UI controls and the business logic, which increases the cost of making UI modifications, and the difficulty of unit testing such code.

モデルビュービューモデル (MVVM) パターンは、アプリケーションのビジネスロジックとプレゼンテーションロジックをユーザーインターフェイス (UI) から明確に分離するのに役立ちます。The Model-View-ViewModel (MVVM) pattern helps to cleanly separate the business and presentation logic of an application from its user interface (UI). アプリケーションロジックと UI を明確に分離することによって、さまざまな開発上の問題に対処し、アプリケーションを簡単にテスト、保守、および進化させることができます。Maintaining a clean separation between application logic and the UI helps to address numerous development issues and can make an application easier to test, maintain, and evolve. また、コードの再利用の機会を大幅に向上させることができ、開発者や UI デザイナーはアプリの各部分を開発する際により簡単に共同作業を行うことができます。It can also greatly improve code re-use opportunities and allows developers and UI designers to more easily collaborate when developing their respective parts of an app.

MVVM パターンThe MVVM Pattern

MVVM パターンには、モデル、ビュー、ビューモデルという3つの主要なコンポーネントがあります。There are three core components in the MVVM pattern: the model, the view, and the view model. それぞれが個別の目的を果たします。Each serves a distinct purpose. 図2-1 は、3つのコンポーネント間の関係を示しています。Figure 2-1 shows the relationships between the three components.

MVVM パターン

図 2-1 : MVVM パターンFigure 2-1 : The MVVM pattern

各コンポーネントの役割について理解することに加えて、相互にやり取りする方法を理解することも重要です。In addition to understanding the responsibilities of each component, it's also important to understand how they interact with each other. 大まかに言えば、ビューはビューモデルを "認識" し、ビューモデルではモデルを認識しますが、モデルはビューモデルを認識せず、ビューモデルはビューを認識しません。At a high level, the view "knows about" the view model, and the view model "knows about" the model, but the model is unaware of the view model, and the view model is unaware of the view. したがって、ビューモデルはビューをモデルから分離し、ビューとは無関係にモデルを進化させることができます。Therefore, the view model isolates the view from the model, and allows the model to evolve independently of the view.

MVVM パターンを使用する利点は次のとおりです。The benefits of using the MVVM pattern are as follows:

  • 既存のビジネスロジックをカプセル化する既存のモデルの実装がある場合は、それを変更することは困難であるか、または危険である可能性があります。If there's an existing model implementation that encapsulates existing business logic, it can be difficult or risky to change it. このシナリオでは、ビューモデルはモデルクラスのアダプターとして機能し、モデルコードに大きな変更を加えないようにします。In this scenario, the view model acts as an adapter for the model classes and enables you to avoid making any major changes to the model code.
  • 開発者はビューを使用せずに、ビューモデルとモデルの単体テストを作成できます。Developers can create unit tests for the view model and the model, without using the view. ビューモデルの単体テストでは、ビューで使用されるのとまったく同じ機能を使用できます。The unit tests for the view model can exercise exactly the same functionality as used by the view.
  • ビューが XAML で完全に実装されていれば、コードに触れることなくアプリの UI を再設計できます。The app UI can be redesigned without touching the code, provided that the view is implemented entirely in XAML. したがって、ビューの新しいバージョンは、既存のビューモデルで動作する必要があります。Therefore, a new version of the view should work with the existing view model.
  • デザイナーと開発者は、開発プロセス中に、コンポーネントに対して個別に、または同時に作業を行うことができます。Designers and developers can work independently and concurrently on their components during the development process. デザイナーはビューに専念できますが、開発者はビューモデルとモデルコンポーネントを操作できます。Designers can focus on the view, while developers can work on the view model and model components.

MVVM を効果的に使用するための鍵は、アプリケーションコードを適切なクラスに分類する方法と、クラスがどのように対話するかを理解することです。The key to using MVVM effectively lies in understanding how to factor app code into the correct classes, and in understanding how the classes interact. 以下のセクションでは、MVVM パターンの各クラスの役割について説明します。The following sections discuss the responsibilities of each of the classes in the MVVM pattern.

表示View

ビューでは、画面に表示される内容の構造、レイアウト、および外観を定義します。The view is responsible for defining the structure, layout, and appearance of what the user sees on screen. 各ビューは XAML で定義されており、ビジネスロジックを含まない分離された分離コードがあることが理想的です。Ideally, each view is defined in XAML, with a limited code-behind that does not contain business logic. ただし、場合によっては、アニメーションなど、XAML で表現しにくい視覚的な動作を実装する UI ロジックが分離コードに含まれていることがあります。However, in some cases, the code-behind might contain UI logic that implements visual behavior that is difficult to express in XAML, such as animations.

アプリケーションでは、 :::no-loc(Xamarin.Forms)::: ビューは通常、 Page 派生クラスまたは ContentView 派生クラスです。In a :::no-loc(Xamarin.Forms)::: application, a view is typically a Page-derived or ContentView-derived class. ただし、ビューはデータテンプレートで表すこともできます。これは、表示されたときにオブジェクトを視覚的に表現するために使用される UI 要素を指定します。However, views can also be represented by a data template, which specifies the UI elements to be used to visually represent an object when it's displayed. ビューとしてのデータテンプレートには分離コードがないため、特定のビューモデル型にバインドするように設計されています。A data template as a view does not have any code-behind, and is designed to bind to a specific view model type.

ヒント

分離コードで UI 要素を有効または無効にすることは避けてください。Avoid enabling and disabling UI elements in the code-behind. ビューモデルが、ビューの表示のいくつかの側面 (コマンドが使用可能かどうか、または操作が保留中であることを示すなど) に影響する論理的な状態の変更を定義する責任があることを確認します。Ensure that view models are responsible for defining logical state changes that affect some aspects of the view's display, such as whether a command is available, or an indication that an operation is pending. そのため、コードビハインドで有効または無効にするのではなく、ビューモデルのプロパティにバインドして、UI 要素を有効または無効にします。Therefore, enable and disable UI elements by binding to view model properties, rather than enabling and disabling them in code-behind.

ビューモデルでコードを実行するためのオプションはいくつかあります。これには、ボタンのクリックや項目の選択など、ビューの相互作用に応答します。There are several options for executing code on the view model in response to interactions on the view, such as a button click or item selection. コントロールでコマンドがサポートされている場合、コントロールのプロパティは、 Command ビューモデルのプロパティにデータバインドでき ICommand ます。If a control supports commands, the control's Command property can be data-bound to an ICommand property on the view model. コントロールのコマンドが呼び出されると、ビューモデルのコードが実行されます。When the control's command is invoked, the code in the view model will be executed. コマンドに加えて、動作はビュー内のオブジェクトにアタッチすることができ、コマンドを呼び出すか、イベントを発生させるかを待機できます。In addition to commands, behaviors can be attached to an object in the view and can listen for either a command to be invoked or event to be raised. これに対して、動作はビューモデル ICommand またはビューモデルのメソッドでを呼び出すことができます。In response, the behavior can then invoke an ICommand on the view model or a method on the view model.

モデルViewModel

ビューモデルでは、ビューのデータバインド先となるプロパティとコマンドが実装され、変更通知イベントによって状態の変化がビューに通知されます。The view model implements properties and commands to which the view can data bind to, and notifies the view of any state changes through change notification events. ビューモデルに用意されているプロパティとコマンドは、UI によって提供される機能を定義しますが、ビューはその機能をどのように表示するかを決定します。The properties and commands that the view model provides define the functionality to be offered by the UI, but the view determines how that functionality is to be displayed.

ヒント

非同期操作で UI の応答性を維持します。Keep the UI responsive with asynchronous operations. モバイルアプリでは、UI スレッドのブロックを解除して、ユーザーによるパフォーマンスの認識を向上させる必要があります。Mobile apps should keep the UI thread unblocked to improve the user's perception of performance. したがって、ビューモデルでは、i/o 操作に非同期メソッドを使用し、イベントを発生させて、プロパティの変更をビューに非同期的に通知します。Therefore, in the view model, use asynchronous methods for I/O operations and raise events to asynchronously notify views of property changes.

ビューモデルは、必要なモデルクラスとビューの相互作用を調整する役割も担います。The view model is also responsible for coordinating the view's interactions with any model classes that are required. ビューモデルとモデルクラスの間には、通常、一対多のリレーションシップがあります。There's typically a one-to-many relationship between the view model and the model classes. ビューモデルでは、モデルクラスをビューに直接公開して、ビュー内のコントロールが直接データをバインドできるようにすることができます。The view model might choose to expose model classes directly to the view so that controls in the view can data bind directly to them. この場合、モデルクラスは、データバインディングと変更通知イベントをサポートするように設計されている必要があります。In this case, the model classes will need to be designed to support data binding and change notification events.

各ビューモデルは、ビューが簡単に使用できる形式のモデルのデータを提供します。Each view model provides data from a model in a form that the view can easily consume. これを実現するために、ビューモデルはデータ変換を実行することがあります。To accomplish this, the view model sometimes performs data conversion. ビューをバインドできるプロパティを提供するため、このデータ変換をビューモデルに配置することをお勧めします。Placing this data conversion in the view model is a good idea because it provides properties that the view can bind to. たとえば、ビューモデルでは、2つのプロパティの値を組み合わせて、ビューでの表示を容易にすることができます。For example, the view model might combine the values of two properties to make it easier for display by the view.

ヒント

変換レイヤーでのデータ変換を一元化します。Centralize data conversions in a conversion layer. また、ビューモデルとビューの間にある独立したデータ変換層としてコンバーターを使用することもできます。It's also possible to use converters as a separate data conversion layer that sits between the view model and the view. これは、たとえば、データがビューモデルで提供されていない特殊な書式設定を必要とする場合に必要になることがあります。This can be necessary, for example, when data requires special formatting that the view model doesn't provide.

ビューモデルがビューを使用した双方向のデータバインディングに参加するためには、そのプロパティがイベントを発生させる必要があり PropertyChanged ます。In order for the view model to participate in two-way data binding with the view, its properties must raise the PropertyChanged event. モデルを表示するには、 INotifyPropertyChanged インターフェイスを実装し、 PropertyChanged プロパティが変更されたときにイベントを発生させることによって、この要件を満たします。View models satisfy this requirement by implementing the INotifyPropertyChanged interface, and raising the PropertyChanged event when a property is changed.

コレクションの場合、ビューフレンドリ ObservableCollection<T> が提供されます。For collections, the view-friendly ObservableCollection<T> is provided. このコレクションは、コレクションの変更通知を実装します。これにより、開発者は、インターフェイスをコレクションに実装する必要がなく INotifyCollectionChanged なります。This collection implements collection changed notification, relieving the developer from having to implement the INotifyCollectionChanged interface on collections.

モデルModel

モデルクラスは、アプリのデータをカプセル化する非ビジュアルクラスです。Model classes are non-visual classes that encapsulate the app's data. そのため、モデルはアプリのドメインモデルを表すものと考えることができます。通常、ビジネスおよび検証ロジックと共にデータモデルが含まれます。Therefore, the model can be thought of as representing the app's domain model, which usually includes a data model along with business and validation logic. モデルオブジェクトの例としては、データ転送オブジェクト (Dto)、Plain Old CLR Objects (POCOs)、生成されたエンティティとプロキシオブジェクトなどがあります。Examples of model objects include data transfer objects (DTOs), Plain Old CLR Objects (POCOs), and generated entity and proxy objects.

通常、モデルクラスは、データアクセスとキャッシュをカプセル化するサービスまたはリポジトリと共に使用されます。Model classes are typically used in conjunction with services or repositories that encapsulate data access and caching.

ビューモデルをビューに接続するConnecting View Models to Views

ビューモデルは、のデータバインディング機能を使用してビューに接続でき :::no-loc(Xamarin.Forms)::: ます。View models can be connected to views by using the data-binding capabilities of :::no-loc(Xamarin.Forms):::. ビューを構築してモデルを表示し、実行時に関連付けるために使用できる方法は多数あります。There are many approaches that can be used to construct views and view models and associate them at runtime. これらの方法は2つのカテゴリに分類されます。これは、最初のコンポジションの表示と、ビューモデルの最初の構成と呼ばれます。These approaches fall into two categories, known as view first composition, and view model first composition. 最初のコンポジションを表示するか、モデルを最初に表示するかを選択することは、優先度と複雑さの問題です。Choosing between view first composition and view model first composition is an issue of preference and complexity. ただし、すべての方法で同じ目的が共有されます。これは、ビューに BindingContext プロパティに割り当てられたビューモデルがあることを示します。However, all approaches share the same aim, which is for the view to have a view model assigned to its BindingContext property.

ビューの最初の構成では、アプリは概念的に、依存するビューモデルに接続するビューで構成されます。With view first composition the app is conceptually composed of views that connect to the view models they depend on. この方法の主な利点は、ビューモデルがビュー自体に依存していないため、疎結合された単体テスト可能なアプリを簡単に作成できることです。The primary benefit of this approach is that it makes it easy to construct loosely coupled, unit testable apps because the view models have no dependence on the views themselves. また、ビジュアル構造に従うことで、アプリの構造を簡単に理解することもできます。これは、コードの実行を追跡して、クラスがどのように作成され、関連付けられているかを理解する必要はありません。It's also easy to understand the structure of the app by following its visual structure, rather than having to track code execution to understand how classes are created and associated. また、ビューの最初の構築は、 :::no-loc(Xamarin.Forms)::: ナビゲーションが発生したときにページを構築するナビゲーションシステムと整合しています。これにより、ビューモデルが最初に複雑になり、プラットフォームに不整合が生じます。In addition, view first construction aligns with the :::no-loc(Xamarin.Forms)::: navigation system that's responsible for constructing pages when navigation occurs, which makes a view model first composition complex and misaligned with the platform.

ビューモデルの最初の構成では、アプリは概念的にビューモデルで構成され、ビューモデルのビューを検索するサービスがあります。With view model first composition the app is conceptually composed of view models, with a service being responsible for locating the view for a view model. ビューモデルの最初の構成は、一部の開発者にとってより自然なものになります。これは、ビューの作成を抽象化して、アプリの論理的な非 UI 構造に集中できるようにするためです。View model first composition feels more natural to some developers, since the view creation can be abstracted away, allowing them to focus on the logical non-UI structure of the app. また、ビューモデルを他のビューモデルで作成することもできます。In addition, it allows view models to be created by other view models. ただし、この方法は複雑になることが多く、アプリのさまざまな部分がどのように作成され、関連付けられているかを理解するのが困難になることがあります。However, this approach is often complex and it can become difficult to understand how the various parts of the app are created and associated.

ヒント

ビューモデルとビューを独立して保持します。Keep view models and views independent. データソース内のプロパティへのビューのバインドは、対応するビューモデルに対するビューのプリンシパルの依存関係である必要があります。The binding of views to a property in a data source should be the view's principal dependency on its corresponding view model. 具体的には、 Button ビューモデルからやなどのビューの種類を参照しないで ListView ください。Specifically, don't reference view types, such as Button and ListView, from view models. ここで説明する原則に従うことで、ビューモデルを分離してテストできるため、スコープを制限することでソフトウェアの欠陥の可能性を低減できます。By following the principles outlined here, view models can be tested in isolation, therefore reducing the likelihood of software defects by limiting scope.

以下のセクションでは、ビューモデルをビューに接続する主な方法について説明します。The following sections discuss the main approaches to connecting view models to views.

ビューモデルの宣言による作成Creating a View Model Declaratively

最も簡単な方法は、ビューが XAML で対応するビューモデルを宣言によってインスタンス化することです。The simplest approach is for the view to declaratively instantiate its corresponding view model in XAML. ビューが構築されると、対応するビューモデルオブジェクトも構築されます。When the view is constructed, the corresponding view model object will also be constructed. この方法を次のコード例に示します。This approach is demonstrated in the following code example:

<ContentPage ... xmlns:local="clr-namespace:eShop">  
    <ContentPage.BindingContext>  
        <local:LoginViewModel />  
    </ContentPage.BindingContext>  
    ...  
</ContentPage>

が作成されると、 ContentPage のインスタンス LoginViewModel が自動的に作成され、ビューのとして設定され BindingContext ます。When the ContentPage is created, an instance of the LoginViewModel is automatically constructed and set as the view's BindingContext.

ビューによるビューモデルの宣言型の構築と割り当てには、単純であるという利点がありますが、ビューモデルに既定の (パラメーターのない) コンストラクターが必要であるという欠点があります。This declarative construction and assignment of the view model by the view has the advantage that it's simple, but has the disadvantage that it requires a default (parameter-less) constructor in the view model.

プログラムによるビューモデルの作成Creating a View Model Programmatically

ビューでは、ビューモデルがプロパティに割り当てられるコードビハインドファイルにコードを含めることができ BindingContext ます。A view can have code in the code-behind file that results in the view model being assigned to its BindingContext property. これは、次のコード例に示すように、多くの場合、ビューのコンストラクターで実現されます。This is often accomplished in the view's constructor, as shown in the following code example:

public LoginView()  
{  
    InitializeComponent();  
    BindingContext = new LoginViewModel(navigationService);  
}

ビューの分離コード内でのビューモデルのプログラムによる構築と割り当てには、単純な利点があります。The programmatic construction and assignment of the view model within the view's code-behind has the advantage that it's simple. ただし、この方法の主な欠点は、ビューが必要な依存関係を持つビューモデルを提供する必要があることです。However, the main disadvantage of this approach is that the view needs to provide the view model with any required dependencies. 依存関係挿入コンテナーを使用すると、ビューモデルとビューモデルの間の疎結合を維持するのに役立ちます。Using a dependency injection container can help to maintain loose coupling between the view and view model. 詳細については、「 依存関係の挿入」を参照してください。For more information, see Dependency Injection.

データテンプレートとして定義されたビューを作成するCreating a View Defined as a Data Template

ビューは、データテンプレートとして定義し、ビューモデルの種類に関連付けることができます。A view can be defined as a data template and associated with a view model type. データテンプレートは、リソースとして定義することも、ビューモデルを表示するコントロール内でインラインで定義することもできます。Data templates can be defined as resources, or they can be defined inline within the control that will display the view model. コントロールの内容はビューモデルインスタンスであり、データテンプレートを使用して視覚的に表現します。The content of the control is the view model instance, and the data template is used to visually represent it. この手法は、最初にビューモデルがインスタンス化され、次にビューの作成が行われる状況の例です。This technique is an example of a situation in which the view model is instantiated first, followed by the creation of the view.

ビューモデルロケーターを使用したビューモデルの自動作成Automatically Creating a View Model with a View Model Locator

ビューモデルロケーターは、ビューモデルのインスタンス化とビューへの関連付けを管理するカスタムクラスです。A view model locator is a custom class that manages the instantiation of view models and their association to views. EShopOnContainers モバイルアプリでは、 ViewModelLocator クラスに添付プロパティがあり AutoWireViewModel ます。これは、ビューモデルをビューに関連付けるために使用されます。In the eShopOnContainers mobile app, the ViewModelLocator class has an attached property, AutoWireViewModel, that's used to associate view models with views. ビューの XAML では、次のコード例に示すように、この添付プロパティが true に設定されて、ビューモデルがビューに自動的に接続されることを示しています。In the view's XAML, this attached property is set to true to indicate that the view model should be automatically connected to the view, as shown in the following code example:

viewModelBase:ViewModelLocator.AutoWireViewModel="true"

AutoWireViewModelプロパティは、false に初期化されるバインド可能なプロパティであり、その値が変更されると、 OnAutoWireViewModelChanged イベントハンドラーが呼び出されます。The AutoWireViewModel property is a bindable property that's initialized to false, and when its value changes the OnAutoWireViewModelChanged event handler is called. このメソッドは、ビューのビューモデルを解決します。This method resolves the view model for the view. これを実現する方法を次のコード例に示します。The following code example shows how this is achieved:

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 規則ベースの方法を使用して、ビューモデルの解決を試みます。The OnAutoWireViewModelChanged method attempts to resolve the view model using a convention-based approach. この規則は、次のことを前提としています。This convention assumes that:

  • ビューモデルは、ビューの種類と同じアセンブリにあります。View models are in the same assembly as view types.
  • ビューはにあります。子の名前空間を表示します。Views are in a .Views child namespace.
  • ビューモデルはに含まれています。Viewmodel child 名前空間。View models are in a .ViewModels child namespace.
  • ビューモデル名は、ビュー名に対応し、"モデルビュー" で終了します。View model names correspond with view names and end with "ViewModel".

最後に、 OnAutoWireViewModelChanged メソッドは、 BindingContext ビュー型のを、解決されたビューモデル型に設定します。Finally, the OnAutoWireViewModelChanged method sets the BindingContext of the view type to the resolved view model type. ビューモデルの種類の解決の詳細については、「 解決策」を参照してください。For more information about resolving the view model type, see Resolution.

このアプローチには、ビューモデルのインスタンス化とビューへの接続を行う単一のクラスがアプリにあるという利点があります。This approach has the advantage that an app has a single class that is responsible for the instantiation of view models and their connection to views.

ヒント

代替を簡単にするためにビューモデルロケーターを使用します。Use a view model locator for ease of substitution. ビューモデルロケーターは、単体テストやデザイン時データなどの依存関係の代替実装のために、代替のポイントとして使用することもできます。A view model locator can also be used as a point of substitution for alternate implementations of dependencies, such as for unit testing or design time data.

基になるビューモデルまたはモデルの変更に応じてビューを更新するUpdating Views in Response to Changes in the Underlying View Model or Model

ビューにアクセスできるすべてのビューモデルおよびモデルクラスは、インターフェイスを実装する必要があり INotifyPropertyChanged ます。All view model and model classes that are accessible to a view should implement the INotifyPropertyChanged interface. ビューモデルまたはモデルクラスにこのインターフェイスを実装すると、基になるプロパティ値が変更されたときに、クラスはビュー内のデータバインドコントロールに変更通知を提供できます。Implementing this interface in a view model or model class allows the class to provide change notifications to any data-bound controls in the view when the underlying property value changes.

次の要件を満たすことで、プロパティ変更通知を適切に使用するために、アプリを設計する必要があります。Apps should be architected for the correct use of property change notification, by meeting the following requirements:

  • PropertyChangedパブリックプロパティの値が変更された場合は、常にイベントを発生させます。Always raising a PropertyChanged event if a public property's value changes. PropertyChangedXAML バインディングがどのように行われるかについての知識があるため、イベントの発生を無視できるとは限りません。Do not assume that raising the PropertyChanged event can be ignored because of knowledge of how XAML binding occurs.
  • PropertyChangedビューモデルまたはモデル内の他のプロパティで値が使用されているすべての計算プロパティについて、常にイベントを発生させます。Always raising a PropertyChanged event for any calculated properties whose values are used by other properties in the view model or model.
  • PropertyChangedプロパティを変更するメソッドの最後、またはオブジェクトが安全な状態であることがわかっている場合は、常にイベントを発生させます。Always raising the PropertyChanged event at the end of the method that makes a property change, or when the object is known to be in a safe state. イベントを発生させると、イベントのハンドラーが同期的に呼び出され、操作が中断されます。Raising the event interrupts the operation by invoking the event's handlers synchronously. これが操作の途中で発生する場合、アンセーフで部分的に更新された状態にあるときに、オブジェクトがコールバック関数に公開されることがあります。If this happens in the middle of an operation, it might expose the object to callback functions when it is in an unsafe, partially updated state. また、イベントによって連鎖変更がトリガーされる可能性があり PropertyChanged ます。In addition, it's possible for cascading changes to be triggered by PropertyChanged events. カスケード変更を行うには、通常、連鎖変更が安全に実行されるためには更新が完了している必要があります。Cascading changes generally require updates to be complete before the cascading change is safe to execute.
  • プロパティが変更されない場合、イベントは発生しません PropertyChangedNever raising a PropertyChanged event if the property does not change. これは、イベントを発生させる前に、古い値と新しい値を比較する必要があることを意味し PropertyChanged ます。This means that you must compare the old and new values before raising the PropertyChanged event.
  • PropertyChangedプロパティを初期化する場合は、ビューモデルのコンストラクター中にイベントを発生させないでください。Never raising the PropertyChanged event during a view model's constructor if you are initializing a property. ビュー内のデータバインドコントロールは、この時点で変更通知の受信をサブスクライブしていません。Data-bound controls in the view will not have subscribed to receive change notifications at this point.
  • PropertyChangedクラスのパブリックメソッドの1回の同期呼び出しで、同じプロパティ名引数を持つ複数のイベントを発生させないでください。Never raising more than one PropertyChanged event with the same property name argument within a single synchronous invocation of a public method of a class. たとえば、バッキングストアがフィールドであるプロパティを指定した NumberOfItems _numberOfItems 場合、ループの実行中にメソッドが50回インクリメントされた場合、 _numberOfItems NumberOfItems すべての作業が完了した後で、プロパティのプロパティ変更通知だけが発生します。For example, given a NumberOfItems property whose backing store is the _numberOfItems field, if a method increments _numberOfItems fifty times during the execution of a loop, it should only raise property change notification on the NumberOfItems property once, after all the work is complete. 非同期メソッドの場合は、 PropertyChanged 非同期の継続チェーンの各同期セグメントで、指定されたプロパティ名のイベントを発生させます。For asynchronous methods, raise the PropertyChanged event for a given property name in each synchronous segment of an asynchronous continuation chain.

EShopOnContainers モバイルアプリでは、クラスを使用して ExtendedBindableObject 変更通知を提供します。次のコード例を参照してください。The eShopOnContainers mobile app uses the ExtendedBindableObject class to provide change notifications, which is shown in the following code example:

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 インターフェイスを実装し、メソッドを提供します OnPropertyChangedXamarin.Form's BindableObject class implements the INotifyPropertyChanged interface, and provides an OnPropertyChanged method. クラスは、 ExtendedBindableObject RaisePropertyChanged プロパティ変更通知を呼び出すためのメソッドを提供します。また、では、クラスによって提供される機能を使用し BindableObject ます。The ExtendedBindableObject class provides the RaisePropertyChanged method to invoke property change notification, and in doing so uses the functionality provided by the BindableObject class.

EShopOnContainers モバイルアプリの各ビューモデルクラスは、クラスから派生します。このクラスは、クラス ViewModelBase から派生 ExtendedBindableObject します。Each view model class in the eShopOnContainers mobile app derives from the ViewModelBase class, which in turn derives from the ExtendedBindableObject class. したがって、各ビューモデルクラスは、クラスのメソッドを使用して RaisePropertyChanged ExtendedBindableObject プロパティ変更通知を提供します。Therefore, each view model class uses the RaisePropertyChanged method in the ExtendedBindableObject class to provide property change notification. 次のコード例は、ラムダ式を使用して、eShopOnContainers モバイルアプリがプロパティ変更通知を呼び出す方法を示しています。The following code example shows how the eShopOnContainers mobile app invokes property change notification by using a lambda expression:

public bool IsLogin  
{  
    get  
    {  
        return _isLogin;  
    }  
    set  
    {  
        _isLogin = value;  
        RaisePropertyChanged(() => IsLogin);  
    }  
}

この方法でラムダ式を使用すると、各呼び出しに対してラムダ式を評価する必要があるため、パフォーマンスがわずかに低下します。Note that using a lambda expression in this way involves a small performance cost because the lambda expression has to be evaluated for each call. パフォーマンスコストは小さく、通常はアプリには影響しませんが、多くの変更通知がある場合はコストが発生する可能性があります。Although the performance cost is small and would not normally impact an app, the costs can accrue when there are many change notifications. ただし、この方法の利点は、プロパティの名前を変更するときに、コンパイル時のタイプセーフとリファクタリングサポートが提供されることです。However, the benefit of this approach is that it provides compile-time type safety and refactoring support when renaming properties.

コマンドと動作を使用した UI の操作UI Interaction using Commands and Behaviors

Mobile apps では、通常、アクションは、ボタンクリックなどのユーザー操作に応答して呼び出されます。これは、分離コードファイルにイベントハンドラーを作成することによって実装できます。In mobile apps, actions are typically invoked in response to a user action, such as a button click, that can be implemented by creating an event handler in the code-behind file. ただし、MVVM パターンでは、アクションを実装する責任はビューモデルにあり、分離コードにコードを配置することは避けてください。However, in the MVVM pattern, the responsibility for implementing the action lies with the view model, and placing code in the code-behind should be avoided.

コマンドは、UI 内のコントロールにバインドできるアクションを表す便利な方法を提供します。Commands provide a convenient way to represent actions that can be bound to controls in the UI. アクションを実装するコードをカプセル化し、ビューのビジュアル表現から分離された状態を維持するために役立ちます。They encapsulate the code that implements the action, and help to keep it decoupled from its visual representation in the view. :::no-loc(Xamarin.Forms)::: には、コマンドに宣言によって接続できるコントロールが含まれています。これらのコントロールは、ユーザーがコントロールと対話するときにコマンドを呼び出します。:::no-loc(Xamarin.Forms)::: includes controls that can be declaratively connected to a command, and these controls will invoke the command when the user interacts with the control.

ビヘイビアーを使用すると、コントロールを宣言によってコマンドに接続することもできます。Behaviors also allow controls to be declaratively connected to a command. ただし、ビヘイビアーを使用して、コントロールによって生成されるイベントの範囲に関連付けられたアクションを呼び出すことができます。However, behaviors can be used to invoke an action that's associated with a range of events raised by a control. そのため、動作は、コマンド対応コントロールと同じシナリオの多くに対応しながら、柔軟性と制御性を高めることができます。Therefore, behaviors address many of the same scenarios as command-enabled controls, while providing a greater degree of flexibility and control. また、ビヘイビアーを使用して、コマンドを操作するために特に設計されていないコントロールにコマンドオブジェクトやメソッドを関連付けることもできます。In addition, behaviors can also be used to associate command objects or methods with controls that were not specifically designed to interact with commands.

コマンドの実装Implementing Commands

ビューモデルは、通常、ビューからバインドするためのコマンドプロパティを公開します。これは、インターフェイスを実装するオブジェクトインスタンスです ICommandView models typically expose command properties, for binding from the view, that are object instances that implement the ICommand interface. 多くのコントロールには、 :::no-loc(Xamarin.Forms)::: プロパティが用意され Command ています。これは、 ICommand ビューモデルによって提供されるオブジェクトにデータをバインドすることができます。A number of :::no-loc(Xamarin.Forms)::: controls provide a Command property, which can be data bound to an ICommand object provided by the view model. インターフェイスは、 ICommand Execute 操作自体をカプセル化するメソッド、コマンドを CanExecute 呼び出すことができるかどうかを示すメソッド、および CanExecuteChanged コマンドを実行する必要があるかどうかに影響を与える変更が発生したときに発生するイベントを定義します。The ICommand interface defines an Execute method, which encapsulates the operation itself, a CanExecute method, which indicates whether the command can be invoked, and a CanExecuteChanged event that occurs when changes occur that affect whether the command should execute. Command Command<T> によって提供されるクラスとクラスは、 :::no-loc(Xamarin.Forms)::: インターフェイスを実装し ICommand ます。ここで、 T は、およびの引数の型です Execute CanExecuteThe Command and Command<T> classes, provided by :::no-loc(Xamarin.Forms):::, implement the ICommand interface, where T is the type of the arguments to Execute and CanExecute.

ビューモデル内では、型の Command Command<T> ビューモデルの各パブリックプロパティに対して、型または型のオブジェクトが存在する必要があり ICommand ます。Within a view model, there should be an object of type Command or Command<T> for each public property in the view model of type ICommand. Commandまたはコンストラクターには、 Command<T> Action メソッドが呼び出されたときに呼び出されるコールバックオブジェクトが必要です ICommand.ExecuteThe Command or Command<T> constructor requires an Action callback object that's called when the ICommand.Execute method is invoked. CanExecuteメソッドは省略可能なコンストラクターパラメーターであり、はを Func 返すです boolThe CanExecute method is an optional constructor parameter, and is a Func that returns a bool.

次のコードは、 Command レジスタコマンドを表すインスタンスを、ビューモデルメソッドへのデリゲートを指定することによって構築する方法を示してい Register ます。The following code shows how a Command instance, which represents a register command, is constructed by specifying a delegate to the Register view model method:

public ICommand RegisterCommand => new Command(Register);

コマンドは、への参照を返すプロパティを使用してビューに公開され ICommand ます。The command is exposed to the view through a property that returns a reference to an ICommand. Executeオブジェクトでメソッドが呼び出されると Command 、そのメソッドは、コンストラクターで指定されたデリゲートを介して、ビューモデルのメソッドへの呼び出しを単純に転送し Command ます。When the Execute method is called on the Command object, it simply forwards the call to the method in the view model via the delegate that was specified in the Command constructor.

コマンド async のデリゲートを指定するときに、キーワードとキーワードを使用して、コマンドによって非同期メソッドを呼び出すことができ await Execute ます。An asynchronous method can be invoked by a command by using the async and await keywords when specifying the command's Execute delegate. これは、コールバックがであり、待機する必要があることを示し Task ます。This indicates that the callback is a Task and should be awaited. たとえば、次のコードは、 Command ビューモデルメソッドへのデリゲートを指定することによって、サインインコマンドを表すインスタンスを作成する方法を示してい SignInAsync ます。For example, the following code shows how a Command instance, which represents a sign-in command, is constructed by specifying a delegate to the SignInAsync view model method:

public ICommand SignInCommand => new Command(async () => await SignInAsync());

パラメーターは、クラスを使用して Execute CanExecute コマンドをインスタンス化することにより、アクションとアクションに渡すことができ Command<T> ます。Parameters can be passed to the Execute and CanExecute actions by using the Command<T> class to instantiate the command. たとえば、次のコードは、メソッドが Command<T> 型の引数を必要とすることを示すために、インスタンスを使用する方法を示してい NavigateAsync string ます。For example, the following code shows how a Command<T> instance is used to indicate that the NavigateAsync method will require an argument of type string:

public ICommand NavigateCommand => new Command<string>(NavigateAsync);

Commandクラスとクラスの両方で Command<T>CanExecute 各コンストラクターのメソッドへのデリゲートは省略可能です。In both the Command and Command<T> classes, the delegate to the CanExecute method in each constructor is optional. デリゲートが指定されていない場合、は Command true を返し CanExecute ます。If a delegate isn't specified, the Command will return true for CanExecute. ただし、ビューモデルでは、 CanExecute オブジェクトに対してメソッドを呼び出すことによって、コマンドの状態の変更を示すことができ ChangeCanExecute Command ます。However, the view model can indicate a change in the command's CanExecute status by calling the ChangeCanExecute method on the Command object. これにより、 CanExecuteChanged イベントが発生します。This causes the CanExecuteChanged event to be raised. コマンドにバインドされている UI 内のコントロールは、データバインドコマンドの可用性を反映するように、有効な状態を更新します。Any controls in the UI that are bound to the command will then update their enabled status to reflect the availability of the data-bound command.

ビューからのコマンドの呼び出しInvoking Commands from a View

次のコード例では、インスタンスを使用して、内のをクラスのにバインドする方法を示し Grid LoginView RegisterCommand LoginViewModel TapGestureRecognizer ます。The following code example shows how a Grid in the LoginView binds to the RegisterCommand in the LoginViewModel class by using a TapGestureRecognizer instance:

<Grid Grid.Column="1" HorizontalOptions="Center">  
    <Label Text="REGISTER" TextColor="Gray"/>  
    <Grid.GestureRecognizers>  
        <TapGestureRecognizer Command="{Binding RegisterCommand}" NumberOfTapsRequired="1" />  
    </Grid.GestureRecognizers>  
</Grid>

コマンドパラメーターは、必要に応じてプロパティを使用して定義することもでき CommandParameter ます。A command parameter can also be optionally defined using the CommandParameter property. 予期される引数の型は、およびのターゲットメソッドで指定され Execute CanExecute ます。The type of the expected argument is specified in the Execute and CanExecute target methods. TapGestureRecognizerユーザーが添付コントロールを操作すると、はターゲットコマンドを自動的に呼び出します。The TapGestureRecognizer will automatically invoke the target command when the user interacts with the attached control. コマンドパラメーターが指定されている場合は、コマンドのデリゲートに引数として渡され Execute ます。The command parameter, if provided, will be passed as the argument to the command's Execute delegate.

実装 (動作を)Implementing Behaviors

ビヘイビアーを使用すると、機能を UI コントロールに追加できるようになります。Behaviors allow functionality to be added to UI controls without having to subclass them. 代わりに、その機能はビヘイビアー クラスで実装され、それがコントロール自体の一部であるかのようにコントロールにアタッチされます。Instead, the functionality is implemented in a behavior class and attached to the control as if it was part of the control itself. 動作を使用すると、通常は分離コードとして記述する必要のあるコードを実装できます。これは、コントロールに簡潔にアタッチし、複数のビューやアプリで再利用するためにパッケージ化できるように、コントロールの API と直接対話するためです。Behaviors enable you to implement code that you would normally have to write as code-behind, because it directly interacts with the API of the control, in such a way that it can be concisely attached to the control, and packaged for reuse across more than one view or app. MVVM のコンテキストでは、動作は、コントロールをコマンドに接続するための便利な方法です。In the context of MVVM, behaviors are a useful approach for connecting controls to commands.

添付プロパティを介してコントロールにアタッチされる動作は、アタッチされる 動作 と呼ばれます。A behavior that's attached to a control through attached properties is known as an attached behavior. その後、この動作によって、関連付けられている要素の公開された API を使用して、ビューのビジュアルツリー内のコントロールまたはその他のコントロールに機能を追加できます。The behavior can then use the exposed API of the element to which it is attached to add functionality to that control, or other controls, in the visual tree of the view. EShopOnContainers モバイルアプリには、アタッチされる動作であるクラスが含まれてい LineColorBehavior ます。The eShopOnContainers mobile app contains the LineColorBehavior class, which is an attached behavior. この動作の詳細については、「 検証エラーの表示」を参照してください。For more information about this behavior, see Displaying Validation Errors.

:::no-loc(Xamarin.Forms):::動作は、クラスまたはクラスから派生するクラスです Behavior 。ここで、 Behavior<T> T は動作を適用するコントロールの型です。A :::no-loc(Xamarin.Forms)::: behavior is a class that derives from the Behavior or Behavior<T> class, where T is the type of the control to which the behavior should apply. これらのクラスにはメソッドとメソッドが用意されており、これをオーバーライドして、 OnAttachedTo OnDetachingFrom 動作がコントロールにアタッチされ、コントロールからデタッチされるときに実行されるロジックを提供する必要があります。These classes provide OnAttachedTo and OnDetachingFrom methods, which should be overridden to provide logic that will be executed when the behavior is attached to and detached from controls.

EShopOnContainers モバイルアプリでは、クラスは BindableBehavior<T> クラスから派生し Behavior<T> ます。In the eShopOnContainers mobile app, the BindableBehavior<T> class derives from the Behavior<T> class. クラスの目的 BindableBehavior<T> は、動作の基底クラスを提供することです。これにより、ビヘイビアー :::no-loc(Xamarin.Forms)::: BindingContext のを添付コントロールに設定する必要があります。The purpose of the BindableBehavior<T> class is to provide a base class for :::no-loc(Xamarin.Forms)::: behaviors that require the BindingContext of the behavior to be set to the attached control.

クラスには、 BindableBehavior<T> OnAttachedTo 動作のを設定するオーバーライド可能なメソッドと、をクリーンアップするオーバーライド可能なメソッドが用意されて BindingContext OnDetachingFrom BindingContext います。The BindableBehavior<T> class provides an overridable OnAttachedTo method that sets the BindingContext of the behavior, and an overridable OnDetachingFrom method that cleans up the BindingContext. さらに、このクラスでは、アタッチされたコントロールへの参照が AssociatedObject プロパティに格納されます。In addition, the class stores a reference to the attached control in the AssociatedObject property.

EShopOnContainers モバイルアプリには、 EventToCommandBehavior イベントの発生に応じてコマンドを実行するクラスが含まれています。The eShopOnContainers mobile app includes an EventToCommandBehavior class, which executes a command in response to an event occurring. このクラスは、動作 BindableBehavior<T> が使用されるときに、 ICommand プロパティによって指定されたをバインドして実行できるように、クラスから派生し Command ます。This class derives from the BindableBehavior<T> class so that the behavior can bind to and execute an ICommand specified by a Command property when the behavior is consumed. 次に示すのは、EventToCommandBehavior クラスのコード例です。The following code example shows the EventToCommandBehavior class:

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 ます。The OnAttachedTo and OnDetachingFrom methods are used to register and deregister an event handler for the event defined in the EventName property. 次に、イベントが発生すると、 OnFired メソッドが呼び出され、コマンドが実行されます。Then, when the event fires, the OnFired method is invoked, which executes the command.

イベントが発生したときにを使用してコマンドを実行すると、コマンド EventToCommandBehavior と対話するように設計されていないコントロールにコマンドを関連付けることができるという利点があります。The advantage of using the EventToCommandBehavior to execute a command when an event fires, is that commands can be associated with controls that weren't designed to interact with commands. さらに、イベント処理コードを移動して、モデルを単体テストできるようにします。In addition, this moves event-handling code to view models, where it can be unit tested.

ビューからの動作の呼び出しInvoking Behaviors from a View

は、コマンド EventToCommandBehavior をサポートしないコントロールにコマンドをアタッチする場合に特に便利です。The EventToCommandBehavior is particularly useful for attaching a command to a control that doesn't support commands. たとえば、では、 ProfileView EventToCommandBehavior 次の OrderDetailCommand ItemTapped ListView コードに示すように、ユーザーの注文を一覧表示するでイベントが発生したときに、を使用してを実行します。For example, the ProfileView uses the EventToCommandBehavior to execute the OrderDetailCommand when the ItemTapped event fires on the ListView that lists the user's orders, as shown in the following code:

<ListView>  
    <ListView.Behaviors>  
        <behaviors:EventToCommandBehavior             
            EventName="ItemTapped"  
            Command="{Binding OrderDetailCommand}"  
            EventArgsConverter="{StaticResource ItemTappedEventArgsConverter}" />  
    </ListView.Behaviors>  
    ...  
</ListView>

実行時には、 EventToCommandBehavior との対話に応答し ListView ます。At runtime, the EventToCommandBehavior will respond to interaction with the ListView. で項目が選択されると、 ListView ItemTapped イベントが発生します。これにより、でが実行され OrderDetailCommand ProfileViewModel ます。When an item is selected in the ListView, the ItemTapped event will fire, which will execute the OrderDetailCommand in the ProfileViewModel. 既定では、イベントのイベント引数がコマンドに渡されます。By default, the event arguments for the event are passed to the command. このデータは、プロパティで指定されたコンバーターによってソースとターゲットの間で渡されるときに変換されます。これにより、 EventArgsConverter からのが返され Item ListView ItemTappedEventArgs ます。This data is converted as it's passed between source and target by the converter specified in the EventArgsConverter property, which returns the Item of the ListView from the ItemTappedEventArgs. そのため、を実行すると、選択したが、登録された OrderDetailCommand Order アクションにパラメーターとして渡されます。Therefore, when the OrderDetailCommand is executed, the selected Order is passed as a parameter to the registered Action.

動作の詳細については、「 動作」を参照してください。For more information about behaviors, see Behaviors.

まとめSummary

モデルビュービューモデル (MVVM) パターンは、アプリケーションのビジネスロジックとプレゼンテーションロジックをユーザーインターフェイス (UI) から明確に分離するのに役立ちます。The Model-View-ViewModel (MVVM) pattern helps to cleanly separate the business and presentation logic of an application from its user interface (UI). アプリケーションロジックと UI を明確に分離することによって、さまざまな開発上の問題に対処し、アプリケーションを簡単にテスト、保守、および進化させることができます。Maintaining a clean separation between application logic and the UI helps to address numerous development issues and can make an application easier to test, maintain, and evolve. また、コードの再利用の機会を大幅に向上させることができ、開発者や UI デザイナーはアプリの各部分を開発する際により簡単に共同作業を行うことができます。It can also greatly improve code re-use opportunities and allows developers and UI designers to more easily collaborate when developing their respective parts of an app.

MVVM パターンを使用すると、アプリの UI と基になるプレゼンテーションとビジネスロジックが、UI と UI ロジックをカプセル化したビューという3つの異なるクラスに分割されます。ビューモデル。プレゼンテーションロジックと状態をカプセル化します。およびモデル。アプリのビジネスロジックとデータをカプセル化します。Using the MVVM pattern, the UI of the app and the underlying presentation and business logic is separated into three separate classes: the view, which encapsulates the UI and UI logic; the view model, which encapsulates presentation logic and state; and the model, which encapsulates the app's business logic and data.