Шаблон Model-View-ViewModelThe Model-View-ViewModel Pattern

Процесс разработки Xamarin. Forms обычно включает в себя создание пользовательского интерфейса в XAML, а затем Добавление кода программной части, которое работает в пользовательском интерфейсе.The 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. Эти проблемы включают тесную связь между элементами управления пользовательского интерфейса и бизнес-логикой, что увеличивает затраты на внесение изменений пользовательского интерфейса и сложность модульного тестирования такого кода.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.

Шаблон Model-View-ViewModel (MVVM) позволяет четко отделить бизнес-логику и представление представления приложения от пользовательского интерфейса.The Model-View-ViewModel (MVVM) pattern helps to cleanly separate the business and presentation logic of an application from its user interface (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. Она также может значительно улучшить возможности повторного использования кода и позволяет разработчикам и дизайнерам пользовательского интерфейса упростить совместную работу при разработке соответствующих частей приложения.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.

Шаблон MVVMThe MVVM Pattern

В шаблоне MVVM есть три основных компонента: модель, представление и модель представления.There are three core components in the MVVM pattern: the model, the view, and the view model. Каждый из них обслуживает отдельную цель.Each serves a distinct purpose. На рис. 2-1 показаны связи между тремя компонентами.Figure 2-1 shows the relationships between the three components.

Рис. 2-1. Шаблон MVVMFigure 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.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, например анимации.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.

В приложении Xamarin. Forms представление обычно является производным или Page ContentViewпроизводным от него классом.In a Xamarin.Forms application, a view is typically a Page-derived or ContentView-derived class. Однако представления также могут быть представлены шаблоном данных, который указывает элементы пользовательского интерфейса, используемые для визуального представления объекта при его отображении.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.

Совет

Избегайте включения и отключения элементов пользовательского интерфейса в коде программной части.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. Таким образом, можно включить и отключить элементы пользовательского интерфейса путем привязки для просмотра свойств модели, а не включать и отключать их в коде программной части.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.

ViewModelViewModel

Модель представления реализует свойства и команды, к которым может быть привязано представление данных, и уведомляет представление о любых изменениях состояния с помощью событий уведомления об изменениях.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. Свойства и команды, предоставляемые моделью представления, определяют функциональные возможности, предоставляемые пользовательским интерфейсом, но представление определяет, как эти функции должны отображаться.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.

Совет

Обеспечьте реагирование пользовательского интерфейса с помощью асинхронных операций.Keep the UI responsive with asynchronous operations. В мобильных приложениях поток пользовательского интерфейса должен оставаться незаблокированным, чтобы повысить производительность пользователя.Mobile apps should keep the UI thread unblocked to improve the user's perception of performance. Таким образом, в модели представления используйте асинхронные методы для операций ввода-вывода и инициируйте события для асинхронного уведомления представлений об изменениях свойств.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. Например, модель представления может сочетать значения двух свойств, чтобы упростить отображение представления.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), обычные старые объекты CLR (POCO), а затем созданные объекты сущностей и прокси-объектов.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

Модели представления можно подключать к представлениям с помощью возможностей привязки данных Xamarin. Forms.View models can be connected to views by using the data-binding capabilities of Xamarin.Forms. Существует множество подходов, которые можно использовать для создания представлений и просмотра моделей и их связывания во время выполнения.There are many approaches that can be used to construct views and view models and associate them at runtime. Эти подходы делятся на две категории, называемые «Просмотр первой композиции» и «Просмотр первой структуры модели».These approaches fall into two categories, known as view first composition, and view model first composition. Выбор между просмотром первой композиции и просмотром модели First композиция является проблемой предпочтения и сложности.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. Кроме того, представление первая конструкция соответствует системе навигации Xamarin. Forms, которая отвечает за создание страниц при переходе, что делает модель представления первой сложной и неправильно согласованной с платформой.In addition, view first construction aligns with the 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. Просмотр первой структуры модели более естественна для некоторых разработчиков, так как создание представления может быть абстрактным, что позволяет им сосредоточиться на логической структуре приложения без пользовательского интерфейса.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>

При создании экземпляр LoginViewModel класса автоматически создается и BindingContextзадается как представление. ContentPageWhen 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. В мобильном приложении ViewModelLocator eShopOnContainers класс имеет присоединенное 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"

Свойство является связываемым свойством, которое инициализируется значением false, а при OnAutoWireViewModelChanged изменении его значения вызывается обработчик событий. AutoWireViewModelThe 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.Views are in a .Views child namespace.
  • Модели представления находятся в. Дочернее пространство имен ViewModel.View models are in a .ViewModels child namespace.
  • Просмотрите имена моделей, соответствующие именам представлений и заканчивающиеся на "ViewModel".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. Не следует рассчитывать на PropertyChanged то, что создание события может быть проигнорировано из-за знаний о том, как происходит привязка XAML.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.
  • Никогда не вызывая PropertyChanged событие, если свойство не изменяется.Never 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 одного события с одним аргументом имени свойства в рамках одного синхронного вызова открытого метода класса.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 приналичиисвойства,NumberOfItems резервное хранилище которого является полем, если метод увеличивался 50развовремявыполненияцикла,ондолжентолькоодинразвызыватьуведомлениеобизменениисвойствавсвойстве,_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)  
    {  
        ...  
    }  
}

BindableObject Класс Xamarin. Form INotifyPropertyChanged реализует OnPropertyChanged интерфейс и предоставляет метод.Xamarin.Form's BindableObject class implements the INotifyPropertyChanged interface, and provides an OnPropertyChanged method. Класс предоставляет метод для вызова уведомления об изменении свойства, и в этом случае использует функциональные возможности, предоставляемые BindableObject классом. RaisePropertyChanged ExtendedBindableObjectThe 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 Interaction using Commands and Behaviors

В мобильных приложениях действия обычно вызываются в ответ на действие пользователя, например нажатие кнопки, которое может быть реализовано путем создания обработчика событий в файле кода программной части.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.

Команды предоставляют удобный способ представления действий, которые можно привязать к элементам управления в пользовательском интерфейсе.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. Xamarin. Forms включает элементы управления, которые можно декларативно подключить к команде, и эти элементы управления будут вызывать команду, когда пользователь взаимодействует с элементом управления.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

Представления модели обычно предоставляют свойства команд для привязки из представления, которые являются экземплярами объектов, реализующими ICommand интерфейс.View models typically expose command properties, for binding from the view, that are object instances that implement the ICommand interface. Ряд элементов управления Xamarin. Forms предоставляет Command свойство, которое может быть привязано ICommand к объекту, предоставленному моделью представления.A number of Xamarin.Forms controls provide a Command property, which can be data bound to an ICommand object provided by the view model. Интерфейс определяет метод, который инкапсулирует саму операцию, CanExecute метод, указывающий, можно ли вызвать CanExecuteChanged команду, и событие, возникающее при внесении изменений, которые влияют на необходимость Execute ICommand команда должна выполняться.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. ICommand T Execute CanExecuteКлассы и ,предоставляемыеXamarin.Forms,реализуютинтерфейс,где—этотипаргументовви.Command<T> CommandThe Command and Command<T> classes, provided by 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<T> или требуется Action объект обратного вызова, который вызывается ICommand.Executeпри Command вызове метода.The Command or Command<T> constructor requires an Action callback object that's called when the ICommand.Execute method is invoked. Метод является необязательным параметром конструктора, а Func —, который возвращает bool. CanExecuteThe CanExecute method is an optional constructor parameter, and is a Func that returns a bool.

В следующем коде показано, как Command экземпляр, представляющий команду Register, создается путем указания делегата 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. При вызове Commandметодадля объекта он просто перенаправляет вызов метода в модели представления через делегат Command , указанный в конструкторе. ExecuteWhen 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. Все элементы управления в пользовательском интерфейсе, привязанные к команде, будут обновлять их состояние Enabled, чтобы отразить доступность команды, привязанной к данным.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 целевых методах и. CanExecuteThe 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

Поведения позволяют добавлять функции в элементы управления пользовательского интерфейса без необходимости создавать их подкласс.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.

Поведение Xamarin. Forms — это класс, производный от Behavior класса или Behavior<T> , где T — это тип элемента управления, к которому должно применяться поведение.A 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.

В мобильном приложении BindableBehavior<T> eShopOnContainers класс является производным Behavior<T> от класса.In the eShopOnContainers mobile app, the BindableBehavior<T> class derives from the Behavior<T> class. Целью BindableBehavior<T> класса является предоставление базового класса для поведений Xamarin. Forms, требующего, BindingContext чтобы поведение было задано для присоединенного элемента управления.The purpose of the BindableBehavior<T> class is to provide a base class for Xamarin.Forms behaviors that require the BindingContext of the behavior to be set to the attached control.

OnDetachingFrom BindingContext BindingContext Класс предоставляет переопределяемый метод, который задает поведение и переопределяемый метод, очищающий. OnAttachedTo BindableBehavior<T>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 EventName и OnDetachingFrom используются для регистрации и отмены регистрации обработчика событий для события, определенного в свойстве.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 Например, OrderDetailCommand компонентиспользуетItemTapped ListView для выполнения, когда событие срабатывает на, в котором перечисляются заказы пользователя, как показано в следующем коде: EventToCommandBehaviorFor 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

Шаблон Model-View-ViewModel (MVVM) позволяет четко отделить бизнес-логику и представление представления приложения от пользовательского интерфейса.The Model-View-ViewModel (MVVM) pattern helps to cleanly separate the business and presentation logic of an application from its user interface (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. Она также может значительно улучшить возможности повторного использования кода и позволяет разработчикам и дизайнерам пользовательского интерфейса упростить совместную работу при разработке соответствующих частей приложения.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 пользовательский интерфейс приложения, базовая структура и бизнес-логика разделяются на три отдельных класса: представление, которое инкапсулирует логику пользовательского интерфейса и пользовательского интерфейса. модель представления, которая инкапсулирует логику представления и состояние; и модель, которая инкапсулирует бизнес-логику и данные приложения.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.