Patrón Model-View-ViewModelThe Model-View-ViewModel Pattern

La experiencia del desarrollador de Xamarin. Forms normalmente implica la creación de una interfaz de usuario en XAML y, a continuación, la adición de código subyacente que funciona en la interfaz de usuario.The Xamarin.Forms developer experience typically involves creating a user interface in XAML, and then adding code-behind that operates on the user interface. A medida que se modifican las aplicaciones y aumentan el tamaño y el ámbito, pueden surgir problemas de mantenimiento complejos.As apps are modified, and grow in size and scope, complex maintenance issues can arise. Estos problemas incluyen el acoplamiento estrecho entre los controles de interfaz de usuario y la lógica de negocios, lo que aumenta el costo de realizar modificaciones en la interfaz de usuario y la dificultad de las pruebas unitarias de este tipo de código.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.

El patrón Modelo-Vista-Modelo de vista (MVVM) ayuda a separar la lógica de negocios y presentación de una aplicación de su interfaz de usuario.The Model-View-ViewModel (MVVM) pattern helps to cleanly separate the business and presentation logic of an application from its user interface (UI). Mantener una separación clara entre la lógica de aplicación y la interfaz de usuario ayuda a abordar numerosos problemas de desarrollo y puede hacer que probar una aplicación, mantenerla y desarrollarla sea más fácil.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. También puede mejorar enormemente las oportunidades de reutilización de código y permite a los desarrolladores y diseñadores de interfaz de usuario colaborar con mayor facilidad al desarrollar sus respectivos elementos de una aplicación.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.

Patrón MVVMThe MVVM Pattern

Hay tres componentes principales en el patrón MVVM: el modelo, la vista y el modelo de vista.There are three core components in the MVVM pattern: the model, the view, and the view model. Cada una de ellas sirve para un propósito distinto.Each serves a distinct purpose. En la figura 2-1 se muestran las relaciones entre los tres componentes.Figure 2-1 shows the relationships between the three components.

Figura 2-1: Patrón MVVMFigure 2-1: The MVVM pattern

Además de comprender las responsabilidades de cada componente, también es importante entender cómo interactúan entre sí.In addition to understanding the responsibilities of each component, it's also important to understand how they interact with each other. En un nivel alto, la vista "conoce" el modelo de vista y el modelo de vista "conoce" el modelo, pero el modelo no es consciente del modelo de vista y el modelo de vista no es consciente de la vista.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. Por lo tanto, el modelo de vista aísla la vista del modelo y permite que el modelo evolucione independientemente de la vista.Therefore, the view model isolates the view from the model, and allows the model to evolve independently of the view.

Las ventajas de usar el patrón MVVM son las siguientes:The benefits of using the MVVM pattern are as follows:

  • Si hay una implementación de modelo existente que encapsula la lógica de negocios existente, puede ser difícil o arriesgada cambiarla.If there's an existing model implementation that encapsulates existing business logic, it can be difficult or risky to change it. En este escenario, el modelo de vista actúa como un adaptador para las clases de modelo y le permite evitar realizar cambios importantes en el código del modelo.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.
  • Los desarrolladores pueden crear pruebas unitarias para el modelo de vista y el modelo, sin usar la vista.Developers can create unit tests for the view model and the model, without using the view. Las pruebas unitarias para el modelo de vista pueden ejercitar exactamente la misma funcionalidad que la vista.The unit tests for the view model can exercise exactly the same functionality as used by the view.
  • La interfaz de usuario de la aplicación se puede rediseñar sin tocar el código, siempre que la vista se implemente completamente en XAML.The app UI can be redesigned without touching the code, provided that the view is implemented entirely in XAML. Por lo tanto, una nueva versión de la vista debe funcionar con el modelo de vista existente.Therefore, a new version of the view should work with the existing view model.
  • Los diseñadores y desarrolladores pueden trabajar de forma independiente y simultánea en sus componentes durante el proceso de desarrollo.Designers and developers can work independently and concurrently on their components during the development process. Los diseñadores pueden centrarse en la vista, mientras que los desarrolladores pueden trabajar en el modelo de vista y los componentes del modelo.Designers can focus on the view, while developers can work on the view model and model components.

La clave para usar MVVM de forma eficaz radica en entender cómo factorizar el código de la aplicación en las clases correctas y en comprender cómo interactúan las clases.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. En las secciones siguientes se describen las responsabilidades de cada una de las clases del patrón MVVM.The following sections discuss the responsibilities of each of the classes in the MVVM pattern.

VerView

La vista es responsable de definir la estructura, el diseño y la apariencia de lo que el usuario ve en la pantalla.The view is responsible for defining the structure, layout, and appearance of what the user sees on screen. Idealmente, cada vista se define en XAML, con un código subyacente limitado que no contiene la lógica de negocios.Ideally, each view is defined in XAML, with a limited code-behind that does not contain business logic. Sin embargo, en algunos casos, el código subyacente podría contener lógica de la interfaz de usuario que implementa el comportamiento visual que es difícil de expresar en XAML, como animaciones.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.

En una aplicación de Xamarin. Forms, una vista normalmente Pagees una clase ContentViewderivada de o derivada de.In a Xamarin.Forms application, a view is typically a Page-derived or ContentView-derived class. Sin embargo, las vistas también se pueden representar mediante una plantilla de datos, que especifica los elementos de la interfaz de usuario que se van a usar para representar visualmente un objeto cuando se muestra.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. Una plantilla de datos como una vista no tiene código subyacente y está diseñada para enlazarse a un tipo de modelo de vista específico.A data template as a view does not have any code-behind, and is designed to bind to a specific view model type.

Sugerencia

Evite habilitar y deshabilitar elementos de la interfaz de usuario en el código subyacente.Avoid enabling and disabling UI elements in the code-behind. Asegúrese de que los modelos de vista sean responsables de definir cambios de estado lógicos que afecten a algunos aspectos de la presentación de la vista, por ejemplo, si un comando está disponible o una indicación de que hay una operación pendiente.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. Por consiguiente, habilite y deshabilite los elementos de la interfaz de usuario enlazando a las propiedades del modelo de vista, en lugar de habilitarlos y deshabilitarlos en el código subyacente.Therefore, enable and disable UI elements by binding to view model properties, rather than enabling and disabling them in code-behind.

Hay varias opciones para ejecutar código en el modelo de vista en respuesta a las interacciones de la vista, como un clic de botón o una selección de elementos.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. Si un control admite comandos, la propiedad del Command control puede estar enlazada a datos a ICommand una propiedad en el modelo de vista.If a control supports commands, the control's Command property can be data-bound to an ICommand property on the view model. Cuando se invoca el comando del control, se ejecutará el código del modelo de vista.When the control's command is invoked, the code in the view model will be executed. Además de los comandos, los comportamientos se pueden adjuntar a un objeto en la vista y pueden escuchar un comando que se va a invocar o que se va a generar el evento.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. En respuesta, el comportamiento puede invocar un ICommand en el modelo de vista o un método en el modelo de vista.In response, the behavior can then invoke an ICommand on the view model or a method on the view model.

ModeloViewModel

El modelo de vista implementa las propiedades y los comandos a los que la vista puede enlazarse y notifica a la vista de cualquier cambio de estado a través de los eventos de notificación de cambios.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. Las propiedades y los comandos que proporciona el modelo de vista definen la funcionalidad que ofrece la interfaz de usuario, pero la vista determina cómo se mostrará esa funcionalidad.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.

Sugerencia

Mantenga la interfaz de usuario con capacidad de respuesta con operaciones asincrónicas.Keep the UI responsive with asynchronous operations. Las aplicaciones móviles deben tener el subproceso de interfaz de usuario desbloqueado para mejorar la percepción del rendimiento del usuario.Mobile apps should keep the UI thread unblocked to improve the user's perception of performance. Por lo tanto, en el modelo de vista, use métodos asincrónicos para las operaciones de e/s y genere eventos para notificar de forma asincrónica a las vistas de los cambios de propiedad.Therefore, in the view model, use asynchronous methods for I/O operations and raise events to asynchronously notify views of property changes.

El modelo de vista también es responsable de coordinar las interacciones de la vista con cualquier clase de modelo que sea necesaria.The view model is also responsible for coordinating the view's interactions with any model classes that are required. Normalmente, hay una relación de uno a varios entre el modelo de vista y las clases de modelo.There's typically a one-to-many relationship between the view model and the model classes. El modelo de vista podría optar por exponer las clases de modelo directamente en la vista para que los controles de la vista puedan enlazarse directamente a ellas.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. En este caso, las clases de modelo deberán diseñarse para admitir el enlace de datos y los eventos de notificación de cambios.In this case, the model classes will need to be designed to support data binding and change notification events.

Cada modelo de vista proporciona datos de un modelo en un formulario que la vista puede consumir fácilmente.Each view model provides data from a model in a form that the view can easily consume. Para ello, el modelo de vista realiza a veces la conversión de datos.To accomplish this, the view model sometimes performs data conversion. Colocar esta conversión de datos en el modelo de vista es una buena idea porque proporciona las propiedades a las que se puede enlazar la vista.Placing this data conversion in the view model is a good idea because it provides properties that the view can bind to. Por ejemplo, el modelo de vista podría combinar los valores de dos propiedades para que sea más fácil de mostrar en la vista.For example, the view model might combine the values of two properties to make it easier for display by the view.

Sugerencia

Centralice las conversiones de datos en una capa de conversión.Centralize data conversions in a conversion layer. También es posible usar convertidores como una capa de conversión de datos independiente que se encuentra entre el modelo de vista y la vista.It's also possible to use converters as a separate data conversion layer that sits between the view model and the view. Esto puede ser necesario, por ejemplo, cuando los datos requieren un formato especial que no proporciona el modelo de vista.This can be necessary, for example, when data requires special formatting that the view model doesn't provide.

Para que el modelo de vista participe en el enlace de datos bidireccional con la vista, sus propiedades deben generar el PropertyChanged evento.In order for the view model to participate in two-way data binding with the view, its properties must raise the PropertyChanged event. Los modelos de vista satisfacen este requisito INotifyPropertyChanged implementando la interfaz y PropertyChanged generando el evento cuando se cambia una propiedad.View models satisfy this requirement by implementing the INotifyPropertyChanged interface, and raising the PropertyChanged event when a property is changed.

En el caso de las colecciones, ObservableCollection<T> se proporciona la vista descriptiva.For collections, the view-friendly ObservableCollection<T> is provided. Esta colección implementa la notificación de cambios en la colección, lo que evita que el desarrollador INotifyCollectionChanged tenga que implementar la interfaz en las colecciones.This collection implements collection changed notification, relieving the developer from having to implement the INotifyCollectionChanged interface on collections.

ModeloModel

Las clases de modelo son clases no visuales que encapsulan los datos de la aplicación.Model classes are non-visual classes that encapsulate the app's data. Por lo tanto, se puede considerar que el modelo representa el modelo de dominio de la aplicación, que normalmente incluye un modelo de datos junto con la lógica de validación y negocios.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. Los ejemplos de objetos de modelo incluyen objetos de transferencia de datos (DTO), objetos CLR antiguos sin formato (POCO) y objetos de entidad y proxy generados.Examples of model objects include data transfer objects (DTOs), Plain Old CLR Objects (POCOs), and generated entity and proxy objects.

Las clases de modelo se usan normalmente junto con servicios o repositorios que encapsulan el acceso a datos y el almacenamiento en caché.Model classes are typically used in conjunction with services or repositories that encapsulate data access and caching.

Conectar modelos de vista a vistasConnecting View Models to Views

Los modelos de vista se pueden conectar a las vistas mediante las funciones de enlace de datos de Xamarin. Forms.View models can be connected to views by using the data-binding capabilities of Xamarin.Forms. Existen muchos enfoques que se pueden usar para construir vistas y ver modelos y asociarlos en tiempo de ejecución.There are many approaches that can be used to construct views and view models and associate them at runtime. Estos enfoques se dividen en dos categorías, lo que se conoce como ver primera composición y ver primera composición del modelo.These approaches fall into two categories, known as view first composition, and view model first composition. Elegir entre la primera composición de vista y la primera composición de modelo es un problema de preferencia y complejidad.Choosing between view first composition and view model first composition is an issue of preference and complexity. Sin embargo, todos los enfoques comparten el mismo objetivo, que es para que la vista tenga un modelo de vista asignado a su propiedad BindingContext.However, all approaches share the same aim, which is for the view to have a view model assigned to its BindingContext property.

Con la primera composición de vista, la aplicación se compone conceptualmente de vistas que se conectan a los modelos de vista de los que dependen.With view first composition the app is conceptually composed of views that connect to the view models they depend on. La principal ventaja de este enfoque es que facilita la creación de aplicaciones comprobables de acoplamiento flexible, ya que los modelos de vista no dependen de las propias vistas.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. También es fácil comprender la estructura de la aplicación siguiendo su estructura visual, en lugar de tener que realizar un seguimiento de la ejecución del código para comprender cómo se crean y asocian las clases.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. Además, la primera construcción se alinea con el sistema de navegación de Xamarin. Forms, que es responsable de la creación de páginas cuando se produce la navegación, lo que hace que una vista del modelo de la primera composición sea compleja y esté mal alineada con la plataforma.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.

Con la primera composición del modelo de vista, la aplicación se compone conceptualmente de modelos de vista, con un servicio responsable de localizar la vista de un modelo de vista.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. La primera composición del modelo de la vista es más natural para algunos desarrolladores, ya que la creación de la vista se puede abstraer, lo que les permite centrarse en la estructura lógica de la interfaz de usuario que no es de la aplicación.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. Además, permite que otros modelos de vista puedan crear modelos de vista.In addition, it allows view models to be created by other view models. Sin embargo, este enfoque suele ser complejo y puede resultar difícil entender cómo se crean y asocian las distintas partes de la aplicación.However, this approach is often complex and it can become difficult to understand how the various parts of the app are created and associated.

Sugerencia

Mantenga los modelos de vista y las vistas de manera independiente.Keep view models and views independent. El enlace de vistas a una propiedad en un origen de datos debe ser la dependencia principal de la vista en su modelo de vista correspondiente.The binding of views to a property in a data source should be the view's principal dependency on its corresponding view model. En concreto, no haga referencia a los tipos Button de ListViewvista, como y, de los modelos de vista.Specifically, don't reference view types, such as Button and ListView, from view models. Siguiendo los principios descritos aquí, los modelos de vista se pueden probar de forma aislada, lo que reduce la probabilidad de que se presenten defectos de software mediante la limitación del ámbito.By following the principles outlined here, view models can be tested in isolation, therefore reducing the likelihood of software defects by limiting scope.

En las secciones siguientes se describen los enfoques principales para conectar los modelos de vista a las vistas.The following sections discuss the main approaches to connecting view models to views.

Crear un modelo de vista mediante declaraciónCreating a View Model Declaratively

El enfoque más sencillo consiste en que la vista cree mediante declaración instancias de su modelo de vista correspondiente en XAML.The simplest approach is for the view to declaratively instantiate its corresponding view model in XAML. Cuando se construya la vista, también se construirá el objeto de modelo de vista correspondiente.When the view is constructed, the corresponding view model object will also be constructed. Este método se muestra en el ejemplo de código siguiente:This approach is demonstrated in the following code example:

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

Cuando se crea, una instancia LoginViewModel de se construye automáticamente y se establece como la de BindingContextla vista. ContentPageWhen the ContentPage is created, an instance of the LoginViewModel is automatically constructed and set as the view's BindingContext.

Esta construcción declarativa y la asignación del modelo de vista por la vista tiene la ventaja de que es simple, pero tiene la desventaja de que requiere un constructor predeterminado (sin parámetros) en el modelo de vista.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.

Crear un modelo de vista mediante programaciónCreating a View Model Programmatically

Una vista puede tener código en el archivo de código subyacente que hace que el modelo de vista se asigne a BindingContext su propiedad.A view can have code in the code-behind file that results in the view model being assigned to its BindingContext property. Esto se suele realizar en el constructor de la vista, como se muestra en el ejemplo de código siguiente:This is often accomplished in the view's constructor, as shown in the following code example:

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

La construcción y asignación mediante programación del modelo de vista en el código subyacente de la vista tiene la ventaja de que es simple.The programmatic construction and assignment of the view model within the view's code-behind has the advantage that it's simple. Sin embargo, el principal inconveniente de este enfoque es que la vista debe proporcionar el modelo de vista con las dependencias necesarias.However, the main disadvantage of this approach is that the view needs to provide the view model with any required dependencies. El uso de un contenedor de inserción de dependencias puede ayudar a mantener el acoplamiento flexible entre el modelo de vista y vista.Using a dependency injection container can help to maintain loose coupling between the view and view model. Para obtener más información, consulte inserción de dependencias.For more information, see Dependency Injection.

Crear una vista definida como una plantilla de datosCreating a View Defined as a Data Template

Una vista se puede definir como una plantilla de datos y asociarse a un tipo de modelo de vista.A view can be defined as a data template and associated with a view model type. Las plantillas de datos se pueden definir como recursos o se pueden definir en línea dentro del control que mostrará el modelo de vista.Data templates can be defined as resources, or they can be defined inline within the control that will display the view model. El contenido del control es la instancia del modelo de vista y la plantilla de datos se usa para representarla visualmente.The content of the control is the view model instance, and the data template is used to visually represent it. Esta técnica es un ejemplo de una situación en la que se crea una instancia del modelo de vista en primer lugar, seguida de la creación de la vista.This technique is an example of a situation in which the view model is instantiated first, followed by the creation of the view.

Crear automáticamente un modelo de vista con un localizador de modelo de vistaAutomatically Creating a View Model with a View Model Locator

Un localizador de modelo de vista es una clase personalizada que administra la creación de instancias de los modelos de vista y su asociación a las vistas.A view model locator is a custom class that manages the instantiation of view models and their association to views. En la aplicación móvil eShopOnContainers, la ViewModelLocator clase tiene una propiedad adjunta, AutoWireViewModel, que se usa para asociar los modelos de vista a las vistas.In the eShopOnContainers mobile app, the ViewModelLocator class has an attached property, AutoWireViewModel, that's used to associate view models with views. En el XAML de la vista, esta propiedad adjunta está establecida en true para indicar que el modelo de vista debe conectarse automáticamente a la vista, tal y como se muestra en el ejemplo de código siguiente: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"

La AutoWireViewModel propiedad es una propiedad enlazable que se inicializa en false y, cuando su valor cambia, se OnAutoWireViewModelChanged llama al controlador de eventos.The AutoWireViewModel property is a bindable property that's initialized to false, and when its value changes the OnAutoWireViewModelChanged event handler is called. Este método resuelve el modelo de vista de la vista.This method resolves the view model for the view. En el ejemplo de código siguiente se muestra cómo se consigue esto: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;  
}

El OnAutoWireViewModelChanged método intenta resolver el modelo de vista mediante un enfoque basado en convenciones.The OnAutoWireViewModelChanged method attempts to resolve the view model using a convention-based approach. Esta Convención presupone que:This convention assumes that:

  • Los modelos de vista están en el mismo ensamblado que los tipos de vista.View models are in the same assembly as view types.
  • Las vistas se encuentran en un. Muestra el espacio de nombres secundario.Views are in a .Views child namespace.
  • Los modelos de vista se encuentran en un. Espacio de nombres secundario ViewModels.View models are in a .ViewModels child namespace.
  • Los nombres de modelo de vista se corresponden con los nombres de vista y terminan con "ViewModel".View model names correspond with view names and end with "ViewModel".

Por último, OnAutoWireViewModelChanged el método establece BindingContext el del tipo de vista en el tipo de modelo de vista resuelta.Finally, the OnAutoWireViewModelChanged method sets the BindingContext of the view type to the resolved view model type. Para obtener más información sobre cómo resolver el tipo de modelo de vista, vea resolución.For more information about resolving the view model type, see Resolution.

Este enfoque tiene la ventaja de que una aplicación tiene una sola clase que es responsable de la creación de instancias de los modelos de vista y su conexión con las vistas.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.

Sugerencia

Use un localizador de modelo de vista para facilitar la sustitución.Use a view model locator for ease of substitution. Un localizador de modelo de vista también se puede usar como punto de sustitución para implementaciones alternativas de dependencias, como las pruebas unitarias o los datos de tiempo de diseño.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.

Actualizar vistas en respuesta a cambios en el modelo o modelo de vista subyacenteUpdating Views in Response to Changes in the Underlying View Model or Model

Todas las clases modelo de vista y modelo a las que se puede tener acceso INotifyPropertyChanged en una vista deben implementar la interfaz.All view model and model classes that are accessible to a view should implement the INotifyPropertyChanged interface. La implementación de esta interfaz en un modelo de vista o una clase de modelo permite que la clase proporcione notificaciones de cambios a los controles enlazados a datos de la vista cuando cambia el valor de la propiedad subyacente.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.

Las aplicaciones deben diseñarse para el uso correcto de la notificación de cambios de propiedad, cumpliendo los siguientes requisitos:Apps should be architected for the correct use of property change notification, by meeting the following requirements:

  • Siempre genera un PropertyChanged evento si cambia el valor de una propiedad pública.Always raising a PropertyChanged event if a public property's value changes. No suponga que se puede omitir la generación del evento debido al conocimiento de cómo se produce el PropertyChanged enlace XAML.Do not assume that raising the PropertyChanged event can be ignored because of knowledge of how XAML binding occurs.
  • Siempre se genera PropertyChanged un evento para cualquier propiedad calculada cuyos valores se usan en otras propiedades del modelo o modelo de vista.Always raising a PropertyChanged event for any calculated properties whose values are used by other properties in the view model or model.
  • Provoque siempre el PropertyChanged evento al final del método que realiza un cambio en la propiedad o cuando se sabe que el objeto está en un estado seguro.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. La generación del evento interrumpe la operación al invocar los controladores del evento de forma sincrónica.Raising the event interrupts the operation by invoking the event's handlers synchronously. Si esto ocurre en medio de una operación, podría exponer el objeto a las funciones de devolución de llamada cuando está en un estado no seguro y parcialmente actualizado.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. Además, es posible que PropertyChanged los eventos activen los cambios en cascada.In addition, it's possible for cascading changes to be triggered by PropertyChanged events. Normalmente, los cambios en cascada requieren que se completen las actualizaciones antes de que se pueda ejecutar de forma segura el cambio en cascada.Cascading changes generally require updates to be complete before the cascading change is safe to execute.
  • Nunca se genera PropertyChanged un evento si la propiedad no cambia.Never raising a PropertyChanged event if the property does not change. Esto significa que debe comparar los valores antiguos y nuevos antes de generar el PropertyChanged evento.This means that you must compare the old and new values before raising the PropertyChanged event.
  • Nunca se genera PropertyChanged el evento durante el constructor de un modelo de vista si se está inicializando una propiedad.Never raising the PropertyChanged event during a view model's constructor if you are initializing a property. Los controles enlazados a datos de la vista no se habrán suscrito para recibir notificaciones de cambios en este momento.Data-bound controls in the view will not have subscribed to receive change notifications at this point.
  • Nunca se produce más de PropertyChanged un evento con el mismo argumento de nombre de propiedad dentro de una única invocación sincrónica de un método público de una clase.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. Por ejemplo, dada una NumberOfItems propiedad cuya memoria auxiliar es el _numberOfItems campo, _numberOfItems si un método incrementa 50 veces durante la ejecución de un bucle, solo debe generar una notificación de cambio de propiedad en NumberOfItems la propiedad una vez. una vez completado todo el trabajo.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. Para los métodos asincrónicos, genere PropertyChanged el evento para un nombre de propiedad determinado en cada segmento sincrónico de una cadena de continuación asincrónica.For asynchronous methods, raise the PropertyChanged event for a given property name in each synchronous segment of an asynchronous continuation chain.

La aplicación móvil eShopOnContainers usa la ExtendedBindableObject clase para proporcionar notificaciones de cambios, que se muestra en el ejemplo de código siguiente: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)  
    {  
        ...  
    }  
}

La clase de BindableObject Xamarin. Forms implementa INotifyPropertyChanged la interfaz y proporciona un OnPropertyChanged método.Xamarin.Form's BindableObject class implements the INotifyPropertyChanged interface, and provides an OnPropertyChanged method. La ExtendedBindableObject clase proporciona el RaisePropertyChanged método para invocar la notificación de cambio de propiedad y, al hacerlo, usa la BindableObject funcionalidad proporcionada por la clase.The ExtendedBindableObject class provides the RaisePropertyChanged method to invoke property change notification, and in doing so uses the functionality provided by the BindableObject class.

Cada clase de modelo de vista de la aplicación móvil eShopOnContainers se deriva ViewModelBase de la clase, que a su vez se ExtendedBindableObject deriva de la clase.Each view model class in the eShopOnContainers mobile app derives from the ViewModelBase class, which in turn derives from the ExtendedBindableObject class. Por lo tanto, cada clase de modelo RaisePropertyChanged de vista usa ExtendedBindableObject el método en la clase para proporcionar la notificación de cambio de propiedad.Therefore, each view model class uses the RaisePropertyChanged method in the ExtendedBindableObject class to provide property change notification. En el ejemplo de código siguiente se muestra cómo la aplicación móvil eShopOnContainers invoca la notificación de cambio de propiedad mediante una expresión lambda: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);  
    }  
}

Tenga en cuenta que el uso de una expresión lambda de esta manera implica un pequeño costo de rendimiento porque la expresión lambda debe evaluarse para cada llamada.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. Aunque el costo de rendimiento es pequeño y no afectaría normalmente a una aplicación, los costos pueden acumularse cuando hay muchas notificaciones de cambios.Although the performance cost is small and would not normally impact an app, the costs can accrue when there are many change notifications. Sin embargo, la ventaja de este enfoque es que proporciona compatibilidad con la refactorización y la seguridad de tipos en tiempo de compilación al cambiar el nombre de las propiedades.However, the benefit of this approach is that it provides compile-time type safety and refactoring support when renaming properties.

Interacción con la interfaz de usuario mediante comandos y comportamientosUI Interaction using Commands and Behaviors

En Mobile Apps, las acciones se invocan normalmente en respuesta a una acción del usuario, como un clic de botón, que se puede implementar creando un controlador de eventos en el archivo de código subyacente.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. Sin embargo, en el patrón MVVM, la responsabilidad de implementar la acción se basa en el modelo de vista y se debe evitar la colocación del código en el código subyacente.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.

Los comandos proporcionan una manera cómoda de representar acciones que se pueden enlazar a controles en la interfaz de usuario.Commands provide a convenient way to represent actions that can be bound to controls in the UI. Encapsulan el código que implementa la acción y ayudan a mantenerla desacoplada de su representación visual en la vista.They encapsulate the code that implements the action, and help to keep it decoupled from its visual representation in the view. Xamarin. Forms incluye controles que se pueden conectar mediante declaración a un comando, y estos controles invocarán el comando cuando el usuario interactúe con el control.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.

Los comportamientos también permiten que los controles se conecten mediante declaración a un comando.Behaviors also allow controls to be declaratively connected to a command. Sin embargo, los comportamientos se pueden usar para invocar una acción asociada a un intervalo de eventos generados por un control.However, behaviors can be used to invoke an action that's associated with a range of events raised by a control. Por lo tanto, los comportamientos abordan muchos de los mismos escenarios que los controles habilitados para comandos, a la vez que proporcionan un mayor grado de flexibilidad y control.Therefore, behaviors address many of the same scenarios as command-enabled controls, while providing a greater degree of flexibility and control. Además, los comportamientos también se pueden usar para asociar objetos de comando o métodos con controles que no se diseñaron específicamente para interactuar con los comandos.In addition, behaviors can also be used to associate command objects or methods with controls that were not specifically designed to interact with commands.

Implementar comandosImplementing Commands

Los modelos de vista normalmente exponen las propiedades del comando, para el enlace desde la vista, ICommand que son instancias de objeto que implementan la interfaz.View models typically expose command properties, for binding from the view, that are object instances that implement the ICommand interface. Varios controles de Xamarin. Forms proporcionan una Command propiedad, que puede ser datos enlazados a ICommand un objeto proporcionado por el modelo de vista.A number of Xamarin.Forms controls provide a Command property, which can be data bound to an ICommand object provided by the view model. La ICommand interfaz define un Execute método, que encapsula la propia operación, un CanExecute método, que indica si se puede invocar el comando y un CanExecuteChanged evento que se produce cuando se producen cambios que afectan a si el comando debe ejecutarse.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. Las Command clases Command<T> y, proporcionadas por Xamarin. Forms, ICommand implementan la T interfaz, Execute donde es el tipo de los CanExecuteargumentos de y.The 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.

Dentro de un modelo de vista, debe haber un objeto de Command tipo Command<T> o para cada propiedad pública en el modelo de vista ICommandde tipo.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. El Command constructor Command<T> o requiere un Action objeto de devolución de llamada al que ICommand.Execute se llama cuando se invoca el método.The Command or Command<T> constructor requires an Action callback object that's called when the ICommand.Execute method is invoked. El CanExecute método es un parámetro de constructor opcional y es un Func que devuelve bool.The CanExecute method is an optional constructor parameter, and is a Func that returns a bool.

En el código siguiente se muestra Command cómo una instancia de, que representa un comando de registro, se construye especificando un Register delegado para el método de modelo de vista: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);

El comando se expone a la vista a través de una propiedad que devuelve una referencia ICommanda un.The command is exposed to the view through a property that returns a reference to an ICommand. Cuando se llama al Command Command método en el objeto, simplemente reenvía la llamada al método en el modelo de vista a través del delegado que se especificó en el constructor. 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.

Un comando puede invocar un método asincrónico mediante el uso de las async palabras clave y await al especificar el delegado del Execute comando.An asynchronous method can be invoked by a command by using the async and await keywords when specifying the command's Execute delegate. Esto indica que la devolución de llamada Task es y se debe esperar.This indicates that the callback is a Task and should be awaited. Por ejemplo, el código siguiente muestra cómo una Command instancia de, que representa un comando de inicio de sesión, se construye especificando un delegado para SignInAsync el método de modelo de vista: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());

Los parámetros se pueden pasar a Execute las CanExecute acciones y mediante la Command<T> clase para crear una instancia del comando.Parameters can be passed to the Execute and CanExecute actions by using the Command<T> class to instantiate the command. Por ejemplo, el código siguiente muestra cómo se Command<T> utiliza una instancia para indicar que el NavigateAsync método necesitará un argumento de tipo 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);

En las Command clases y Command<T>,el delegado del métodoencadaconstructoresopcional.CanExecuteIn both the Command and Command<T> classes, the delegate to the CanExecute method in each constructor is optional. Si no se especifica un delegado, Command devolverá CanExecute true para.If a delegate isn't specified, the Command will return true for CanExecute. Sin embargo, el modelo de vista puede indicar un cambio en el CanExecute estado del comando llamando ChangeCanExecute al método en Command el objeto.However, the view model can indicate a change in the command's CanExecute status by calling the ChangeCanExecute method on the Command object. Esto hace que CanExecuteChanged se genere el evento.This causes the CanExecuteChanged event to be raised. Los controles de la interfaz de usuario que estén enlazados al comando actualizarán su estado habilitado para reflejar la disponibilidad del comando enlazado a datos.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.

Invocar comandos desde una vistaInvoking Commands from a View

En el ejemplo de código siguiente se Grid muestra cómo LoginView se enlaza a RegisterCommand LoginViewModel en la clase mediante una TapGestureRecognizer instancia de: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>

También se puede definir opcionalmente un parámetro de comando mediante CommandParameter la propiedad.A command parameter can also be optionally defined using the CommandParameter property. El tipo del argumento esperado se especifica en los Execute métodos de destino y. CanExecuteThe type of the expected argument is specified in the Execute and CanExecute target methods. TapGestureRecognizer Invocará automáticamente el comando de destino cuando el usuario interactúe con el control adjunto.The TapGestureRecognizer will automatically invoke the target command when the user interacts with the attached control. El parámetro de comando, si se proporciona, se pasará como argumento al delegado del Execute comando.The command parameter, if provided, will be passed as the argument to the command's Execute delegate.

Implementar comportamientosImplementing Behaviors

Los comportamientos permiten agregar funcionalidad a los controles de interfaz de usuario sin tener que crear una subclase.Behaviors allow functionality to be added to UI controls without having to subclass them. En su lugar, la función se implementa en una clase de comportamiento y se asocia al control como si fuera parte de este.Instead, the functionality is implemented in a behavior class and attached to the control as if it was part of the control itself. Los comportamientos permiten implementar código que normalmente tendría que escribir como código subyacente, ya que interactúa directamente con la API del control, de tal forma que se puede adjuntar de manera concisa al control y empaquetarse para reutilizarlo en más de una vista o aplicación.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. En el contexto de MVVM, los comportamientos son un enfoque útil para la conexión de controles a comandos.In the context of MVVM, behaviors are a useful approach for connecting controls to commands.

Un comportamiento que se adjunta a un control a través de las propiedades adjuntas se conoce como un comportamiento adjunto.A behavior that's attached to a control through attached properties is known as an attached behavior. Después, el comportamiento puede usar la API expuesta del elemento al que está adjunto para agregar funcionalidad a ese control, u otros controles, en el árbol visual de la vista.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. La aplicación móvil eShopOnContainers contiene la LineColorBehavior clase, que es un comportamiento adjunto.The eShopOnContainers mobile app contains the LineColorBehavior class, which is an attached behavior. Para obtener más información sobre este comportamiento, vea Mostrar errores de validación.For more information about this behavior, see Displaying Validation Errors.

Un comportamiento de Xamarin. Forms es una clase que se deriva Behavior de Behavior<T> la clase o T , donde es el tipo del control al que se debe aplicar el comportamiento.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. Estas clases proporcionan OnAttachedTo métodos OnDetachingFrom y, que se deben invalidar para proporcionar lógica que se ejecutará cuando el comportamiento se adjunte a los controles y se desasocie de ellos.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.

En la aplicación móvil eShopOnContainers, la BindableBehavior<T> clase se deriva de la Behavior<T> clase.In the eShopOnContainers mobile app, the BindableBehavior<T> class derives from the Behavior<T> class. El propósito de la BindableBehavior<T> clase es proporcionar una clase base para los comportamientos de Xamarin. Forms que BindingContext requieren que el comportamiento se establezca en el control adjunto.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.

La BindableBehavior<T> clase proporciona un OnAttachedTo método reemplazable que establece el BindingContext OnDetachingFrom del comportamiento y un método reemplazable que limpia el 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. Además, la clase almacena una referencia al control adjunto en la propiedad AssociatedObject.In addition, the class stores a reference to the attached control in the AssociatedObject property.

La aplicación móvil eShopOnContainers incluye una EventToCommandBehavior clase, que ejecuta un comando en respuesta a un evento que se está produciendo.The eShopOnContainers mobile app includes an EventToCommandBehavior class, which executes a command in response to an event occurring. Esta clase se deriva de la BindableBehavior<T> clase para que el comportamiento pueda enlazar y ejecutar un ICommand especificado por una Command propiedad cuando se consume el comportamiento.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. En el ejemplo de código siguiente se muestra la clase 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)  
    {  
        ...  
    }  
}

Los OnAttachedTo métodos OnDetachingFrom y se usan para registrar y anular el registro de un controlador de eventos para el EventName evento definido en la propiedad.The OnAttachedTo and OnDetachingFrom methods are used to register and deregister an event handler for the event defined in the EventName property. A continuación, cuando se desencadena el evento OnFired , se invoca el método, que ejecuta el comando.Then, when the event fires, the OnFired method is invoked, which executes the command.

La ventaja de utilizar EventToCommandBehavior para ejecutar un comando cuando se activa un evento es que los comandos se pueden asociar a controles que no se diseñaron para interactuar con los comandos.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. Además, mueve el código de control de eventos para ver los modelos, donde se puede probar la unidad.In addition, this moves event-handling code to view models, where it can be unit tested.

Invocar comportamientos desde una vistaInvoking Behaviors from a View

EventToCommandBehavior Es especialmente útil para adjuntar un comando a un control que no admite comandos.The EventToCommandBehavior is particularly useful for attaching a command to a control that doesn't support commands. Por ProfileView ejemplo, OrderDetailCommand utiliza para ejecutar cuando el ItemTapped evento se activa en el ListView que muestra los pedidos del usuario, tal y como se muestra en el código siguiente: 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>

En tiempo de ejecución EventToCommandBehavior , responderá a la ListViewinteracción con.At runtime, the EventToCommandBehavior will respond to interaction with the ListView. Cuando se selecciona un elemento en la ListView, se ItemTapped activa el evento OrderDetailCommand , que ejecutará en ProfileViewModel.When an item is selected in the ListView, the ItemTapped event will fire, which will execute the OrderDetailCommand in the ProfileViewModel. De forma predeterminada, los argumentos de evento para el evento se pasan al comando.By default, the event arguments for the event are passed to the command. Estos datos se convierten a medida que se pasan entre el origen y el destino mediante el EventArgsConverter convertidor especificado en la propiedad Item , que ListView devuelve el ItemTappedEventArgsde de.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. Por lo tanto, OrderDetailCommand cuando se ejecuta, el Order seleccionado se pasa como un parámetro a la acción registrada.Therefore, when the OrderDetailCommand is executed, the selected Order is passed as a parameter to the registered Action.

Para obtener más información sobre los comportamientos, vea Behaviors.For more information about behaviors, see Behaviors.

ResumenSummary

El patrón Modelo-Vista-Modelo de vista (MVVM) ayuda a separar la lógica de negocios y presentación de una aplicación de su interfaz de usuario.The Model-View-ViewModel (MVVM) pattern helps to cleanly separate the business and presentation logic of an application from its user interface (UI). Mantener una separación clara entre la lógica de aplicación y la interfaz de usuario ayuda a abordar numerosos problemas de desarrollo y puede hacer que probar una aplicación, mantenerla y desarrollarla sea más fácil.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. También puede mejorar enormemente las oportunidades de reutilización de código y permite a los desarrolladores y diseñadores de interfaz de usuario colaborar con mayor facilidad al desarrollar sus respectivos elementos de una aplicación.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.

Con el patrón de MVVM, la interfaz de usuario de la aplicación y la presentación subyacente y la lógica de negocios se dividen en tres clases independientes: la vista, que encapsula la interfaz de usuario y la lógica de la interfaz de usuario. el modelo de vista, que encapsula la lógica de presentación y el estado; y el modelo, que encapsula la lógica de negocios y los datos de la aplicación.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.