El patrón Model-View-ViewModel

Nota:

Este libro electrónico se publicó en la primavera de 2017 y no se ha actualizado desde entonces. Hay mucho en el libro que sigue siendo valioso, pero algunos de los materiales están obsoletos.

La Xamarin.Forms experiencia del desarrollador normalmente implica crear una interfaz de usuario en XAML y, a continuación, agregar código subyacente que funciona en la interfaz de usuario. A medida que se modifican las aplicaciones y aumenta el tamaño y el ámbito, pueden surgir problemas de mantenimiento complejos. Estos problemas incluyen el acoplamiento estricto entre los controles de interfaz de usuario y la lógica de negocios, lo que aumenta el costo de realizar modificaciones de la interfaz de usuario y la dificultad de probar este código unitaria.

El patrón Model-View-ViewModel (MVVM) ayuda a separar limpiamente la lógica de negocios y presentación de una aplicación de su interfaz de usuario (UI). Mantener una separación limpia entre la lógica de la aplicación y la interfaz de usuario ayuda a abordar numerosos problemas de desarrollo y puede facilitar la prueba, el mantenimiento y la evolución de una aplicación. También puede mejorar considerablemente las oportunidades de reutilización del código y permite a los desarrolladores y diseñadores de interfaz de usuario colaborar más fácilmente al desarrollar sus respectivas partes de una aplicación.

El patrón MVVM

Hay tres componentes principales en el patrón MVVM: el modelo, la vista y el modelo de vista. Cada uno sirve para un propósito distinto. En la figura 2-1 se muestran las relaciones entre los tres componentes.

The MVVM pattern

Figura 2-1: El patrón MVVM

Además de comprender las responsabilidades de cada componente, también es importante comprender cómo interactúan entre sí. 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. Por lo tanto, el modelo de vista aísla la vista del modelo y permite que el modelo evolucione independientemente de la vista.

Las ventajas de usar el patrón MVVM son las siguientes:

  • Si hay una implementación de modelo existente que encapsula la lógica de negocios existente, puede ser difícil o arriesgada cambiarla. En este escenario, el modelo de vista actúa como adaptador para las clases de modelo y le permite evitar realizar cambios importantes en el código del modelo.
  • Los desarrolladores pueden crear pruebas unitarias para el modelo de vista y el modelo, sin usar la vista. Las pruebas unitarias del modelo de vista pueden ejercer exactamente la misma funcionalidad que la vista.
  • 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. Por lo tanto, una nueva versión de la vista debe funcionar con el modelo de vista existente.
  • Los diseñadores y desarrolladores pueden trabajar de forma independiente y simultánea en sus componentes durante el proceso de desarrollo. Los diseñadores pueden centrarse en la vista, mientras que los desarrolladores pueden trabajar en el modelo de vista y los componentes del modelo.

La clave para usar MVVM consiste eficazmente en comprender cómo factorizar el código de la aplicación en las clases correctas y comprender cómo interactúan las clases. En las secciones siguientes se describen las responsabilidades de cada una de las clases del patrón MVVM.

Ver

La vista es responsable de definir la estructura, el diseño y la apariencia de lo que ve el usuario en la pantalla. Idealmente, cada vista se define en XAML, con un código subyacente limitado que no contiene lógica de negocios. Sin embargo, en algunos casos, el código subyacente podría contener lógica de interfaz de usuario que implementa el comportamiento visual que es difícil de expresar en XAML, como animaciones.

En una Xamarin.Forms aplicación, una vista suele ser una Pageclase derivada o ContentViewderivada. 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 usarán para representar visualmente un objeto cuando se muestra. Una plantilla de datos como vista no tiene código subyacente y está diseñada para enlazarse a un tipo de modelo de vista específico.

Sugerencia

Evite habilitar y deshabilitar los elementos de la interfaz de usuario en el código subyacente. Asegúrese de que los modelos de vista son responsables de definir los cambios de estado lógico que afectan a algunos aspectos de la presentación de la vista, como si un comando está disponible o una indicación de que una operación está pendiente. Por lo tanto, habilite y deshabilite los elementos de la interfaz de usuario enlazando para ver las propiedades del modelo, en lugar de habilitarlos y deshabilitarlos en el código subyacente.

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. Si un control admite comandos, la propiedad del Command control puede estar enlazada a datos a una ICommand propiedad en el modelo de vista. Cuando se invoca el comando del control, se ejecutará el código del modelo de vista. 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 genere un evento. En respuesta, el comportamiento puede invocar un ICommand en el modelo de vista o un método en el modelo de vista.

ViewModel

El modelo de vista implementa propiedades y comandos a los que la vista puede enlazar datos y notifica a la vista los cambios de estado a través de eventos de notificación de cambios. Las propiedades y comandos que proporciona el modelo de vista definen la funcionalidad que ofrece la interfaz de usuario, pero la vista determina cómo se va a mostrar esa funcionalidad.

Sugerencia

Mantenga la interfaz de usuario con capacidad de respuesta con operaciones asincrónicas. Las aplicaciones móviles deben mantener desbloqueado el subproceso de interfaz de usuario para mejorar la percepción del rendimiento del usuario. Por lo tanto, en el modelo de vista, use métodos asincrónicos para las operaciones de E/S y genere eventos para notificar asincrónicamente las vistas de los cambios de propiedad.

El modelo de vista también es responsable de coordinar las interacciones de la vista con las clases de modelo necesarias. Normalmente hay una relación uno a varios entre el modelo de vista y las clases de modelo. El modelo de vista puede optar por exponer clases de modelo directamente a la vista para que los controles de la vista puedan enlazar datos directamente a ellas. 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.

Cada modelo de vista proporciona datos de un modelo en un formulario que la vista puede consumir fácilmente. Para ello, el modelo de vista a veces realiza la conversión de datos. Colocar esta conversión de datos en el modelo de vista es una buena idea porque proporciona propiedades a las que la vista se puede enlazar. Por ejemplo, el modelo de vista podría combinar los valores de dos propiedades para facilitar la visualización por parte de la vista.

Sugerencia

Centralice las conversiones de datos en una capa de conversión. 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. Esto puede ser necesario, por ejemplo, cuando los datos requieren un formato especial que el modelo de vista no proporciona.

Para que el modelo de vista participe en el enlace de datos bidireccional con la vista, sus propiedades deben generar el PropertyChanged evento . Los modelos de vista satisfacen este requisito implementando la INotifyPropertyChanged interfaz y generando el PropertyChanged evento cuando se cambia una propiedad.

En el caso de las colecciones, se proporciona el uso descriptivo ObservableCollection<T> de las vistas. Esta colección implementa una notificación modificada de recopilación, lo que impide que el desarrollador tenga que implementar la INotifyCollectionChanged interfaz en las colecciones.

Modelo

Las clases de modelo son clases no visuales que encapsulan los datos de la aplicación. Por lo tanto, el modelo se puede considerar como que 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. Algunos ejemplos de objetos de modelo son objetos de transferencia de datos (DTO), Objetos CLR antiguos sin formato (POCOs) y objetos de entidad y proxy generados.

Las clases de modelo se suelen usar junto con servicios o repositorios que encapsulan el acceso a datos y el almacenamiento en caché.

Conexión de modelos de vista a vistas

Los modelos de vista se pueden conectar a las vistas mediante las funcionalidades de enlace de datos de Xamarin.Forms. Hay muchos enfoques que se pueden usar para construir vistas y ver modelos y asociarlos en tiempo de ejecución. Estos enfoques se dividen en dos categorías, conocidas como la primera composición de la vista, y la primera composición del modelo de vista. Elegir entre la primera composición de la vista y la primera composición del modelo de vista es un problema de preferencia y complejidad. Sin embargo, todos los enfoques comparten el mismo objetivo, que es para que la vista tenga asignado un modelo de vista a su propiedad BindingContext.

Con la primera composición de la vista, la aplicación se compone conceptualmente de vistas que se conectan a los modelos de vista de los que dependen. La principal ventaja de este enfoque es que facilita la construcción de aplicaciones acoplables unitarias de forma flexible, ya que los modelos de vista no dependen de las propias vistas. 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 de código para comprender cómo se crean y asocian las clases. Además, la primera construcción de la vista se alinea con el Xamarin.Forms sistema de navegación responsable de construir páginas cuando se produce la navegación, lo que hace que un modelo de vista sea primero complejo y mal alineado con la plataforma.

Con la composición del modelo de vista, la aplicación se compone conceptualmente de modelos de vista, con un servicio responsable de localizar la vista para un modelo de vista. La primera composición del modelo de vista se siente 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 que no es de interfaz de usuario de la aplicación. Además, permite crear modelos de vista mediante otros modelos de vista. Sin embargo, este enfoque suele ser complejo y puede resultar difícil comprender cómo se crean y asocian las distintas partes de la aplicación.

Sugerencia

Mantenga los modelos de vista y las vistas independientes. El enlace de vistas a una propiedad de un origen de datos debe ser la dependencia principal de la vista en su modelo de vista correspondiente. En concreto, no haga referencia a tipos de vista, como Button y ListView, desde modelos de vista. Siguiendo los principios descritos aquí, los modelos de vista se pueden probar de forma aislada, lo que reduce la probabilidad de defectos de software limitando el ámbito.

En las secciones siguientes se describen los enfoques principales para conectar modelos de vista a vistas.

Creación de un modelo de vista mediante declaración

El enfoque más sencillo es que la vista cree una instancia declarativa de su modelo de vista correspondiente en XAML. Cuando se construye la vista, también se construirá el objeto de modelo de vista correspondiente. Este enfoque se muestra en el ejemplo de código siguiente:

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

ContentPage Cuando se crea , se crea automáticamente una instancia de LoginViewModel y se establece como BindingContext.

Esta construcción declarativa y 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.

Crear un modelo de vista mediante programación

Una vista puede tener código en el archivo de código subyacente que da lugar a que el modelo de vista se asigne a su BindingContext propiedad. Esto suele realizarse en el constructor de la vista, como se muestra en el ejemplo de código siguiente:

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

La construcción y asignación mediante programación del modelo de vista dentro del código subyacente de la vista tiene la ventaja de que es sencillo. Sin embargo, la principal desventaja de este enfoque es que la vista debe proporcionar al modelo de vista las dependencias necesarias. El uso de un contenedor de inserción de dependencias puede ayudar a mantener el acoplamiento flexible entre la vista y el modelo de vista. Para obtener más información, consulte Inserción de dependencias.

Crear una vista definida como plantilla de datos

Una vista se puede definir como una plantilla de datos y asociarla a un tipo de modelo de vista. Las plantillas de datos se pueden definir como recursos o se pueden definir insertadas dentro del control que mostrará el modelo de vista. El contenido del control es la instancia del modelo de vista y la plantilla de datos se usa para representarlo visualmente. Esta técnica es un ejemplo de una situación en la que primero se crea una instancia del modelo de vista, seguida de la creación de la vista.

Crear automáticamente un modelo de vista con un localizador de modelos de vista

Un localizador de modelos 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. En la aplicación móvil eShopOnContainers, la ViewModelLocator clase tiene una propiedad adjunta, AutoWireViewModel, que se usa para asociar modelos de vista con vistas. En el XAML de la vista, esta propiedad adjunta se establece en true para indicar que el modelo de vista debe conectarse automáticamente a la vista, como se muestra en el ejemplo de código siguiente:

viewModelBase:ViewModelLocator.AutoWireViewModel="true"

La AutoWireViewModel propiedad es una propiedad enlazable que se inicializa en false y, cuando su valor cambia, se llama al OnAutoWireViewModelChanged controlador de eventos. Este método resuelve el modelo de vista de la vista. En el ejemplo de código siguiente se muestra cómo se logra esto:

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. Esta convención supone que:

  • Los modelos de vista están en el mismo ensamblado que los tipos de vista.
  • Las vistas se encuentran en . Visualiza el espacio de nombres secundario.
  • Los modelos de vista se encuentran en . ViewModels espacio de nombres secundario.
  • Los nombres de modelo de vista se corresponden con los nombres de vista y terminan con "ViewModel".

Por último, el OnAutoWireViewModelChanged método establece el BindingContext del tipo de vista en el tipo de modelo de vista resuelto. Para obtener más información sobre cómo resolver el tipo de modelo de vista, vea Resolución.

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 a las vistas.

Sugerencia

Use un localizador de modelos de vista para facilitar la sustitución. Un localizador de modelos de vista también se puede usar como punto de sustitución para implementaciones alternativas de dependencias, como para pruebas unitarias o datos en tiempo de diseño.

Actualizar vistas en respuesta a los cambios en el modelo de vista subyacente o el modelo

Todas las clases de modelo y modelo que son accesibles para una vista deben implementar la INotifyPropertyChanged interfaz . La implementación de esta interfaz en un modelo de vista o clase de modelo permite a la clase proporcionar notificaciones de cambios a los controles enlazados a datos de la vista cuando cambia el valor de la propiedad subyacente.

Las aplicaciones deben diseñarse para el uso correcto de la notificación de cambio de propiedad, cumpliendo los siguientes requisitos:

  • Generar siempre un PropertyChanged evento si cambia el valor de una propiedad pública. No suponga que se puede omitir la generación del evento debido a la forma en que se produce el PropertyChanged enlace XAML.
  • Generar siempre un PropertyChanged evento para las propiedades calculadas cuyos valores se usan en otras propiedades del modelo de vista o modelo.
  • Generar siempre el PropertyChanged evento al final del método que realiza un cambio de propiedad o cuando se sabe que el objeto está en un estado seguro. La generación del evento interrumpe la operación invocando los controladores del evento de forma sincrónica. Si esto ocurre en medio de una operación, podría exponer el objeto a las funciones de devolución de llamada cuando se encuentra en un estado no seguro y parcialmente actualizado. Además, es posible que los eventos desencadene PropertyChanged los cambios en cascada. Por lo general, los cambios en cascada requieren que se completen las actualizaciones antes de que el cambio en cascada sea seguro para ejecutarse.
  • Nunca genere un PropertyChanged evento si la propiedad no cambia. Esto significa que debe comparar los valores antiguos y nuevos antes de generar el PropertyChanged evento.
  • No genere nunca el evento durante el PropertyChanged constructor de un modelo de vista si va a inicializar una propiedad. Los controles enlazados a datos en la vista no se habrán suscrito para recibir notificaciones de cambio en este momento.
  • Nunca genere más de un PropertyChanged 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. Por ejemplo, dada una NumberOfItems propiedad cuyo almacén de respaldo es el _numberOfItems campo, si un método aumenta _numberOfItems cincuenta veces durante la ejecución de un bucle, solo debe generar una notificación de cambio de propiedad en la NumberOfItems propiedad una vez, una vez completado todo el trabajo. En el caso de los métodos asincrónicos, genere el PropertyChanged evento para un nombre de propiedad determinado en cada segmento sincrónico de una cadena de continuación asincrónica.

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:

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.Form implementa la INotifyPropertyChanged interfaz y proporciona un OnPropertyChanged método . La ExtendedBindableObject clase proporciona el método para invocar la RaisePropertyChanged notificación de cambio de propiedad y, al hacerlo, usa la funcionalidad proporcionada por la BindableObject clase .

Cada clase de modelo de vista de la aplicación móvil eShopOnContainers se deriva de la ViewModelBase clase , que a su vez deriva de la ExtendedBindableObject clase . Por lo tanto, cada clase de modelo de vista usa el RaisePropertyChanged método de la ExtendedBindableObject clase para proporcionar una notificación de cambio de propiedad. 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:

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. Aunque el costo de rendimiento es pequeño y normalmente no afectaría a una aplicación, los costos pueden acumularse cuando hay muchas notificaciones de cambio. Sin embargo, la ventaja de este enfoque es que proporciona seguridad y refactorización de tipos en tiempo de compilación al cambiar el nombre de las propiedades.

Interacción de la interfaz de usuario mediante comandos y comportamientos

En las aplicaciones móviles, las acciones se invocan normalmente en respuesta a una acción del usuario, como un clic de botón, que se puede implementar mediante la creación de un controlador de eventos en el archivo de código subyacente. Sin embargo, en el patrón MVVM, la responsabilidad de implementar la acción reside en el modelo de vista y se debe evitar colocar código en el código subyacente.

Los comandos proporcionan una manera cómoda de representar acciones que se pueden enlazar a los controles de la interfaz de usuario. Encapsulan el código que implementa la acción y ayudan a evitar que se desacopla de su representación visual en la vista. 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.

Los comportamientos también permiten que los controles se conecten mediante declaración a un comando. Sin embargo, los comportamientos se pueden usar para invocar una acción asociada a un intervalo de eventos generados por un control . Por lo tanto, los comportamientos abordan muchos de los mismos escenarios que los controles habilitados para comandos, al tiempo que proporcionan un mayor grado de flexibilidad y 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 comandos.

Implementación de comandos

Los modelos de vista suelen exponer propiedades de comando, para el enlace desde la vista, que son instancias de objeto que implementan la ICommand interfaz . Xamarin.Forms Varios controles proporcionan una Command propiedad , que puede ser datos enlazados a un ICommand objeto proporcionado por el modelo de vista. 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. Las Command clases y Command<T> , proporcionadas por Xamarin.Forms, implementan la ICommand interfaz , donde T es el tipo de los argumentos en Execute y CanExecute.

Dentro de un modelo de vista, debe haber un objeto de tipo Command o Command<T> para cada propiedad pública en el modelo de vista de tipo ICommand. El Command constructor o Command<T> requiere un Action objeto de devolución de llamada al que se llama cuando se invoca el ICommand.Execute método . El CanExecute método es un parámetro de constructor opcional y es un Func que devuelve .bool

El código siguiente muestra cómo se construye una Command instancia de , que representa un comando register, especificando un delegado para el método de Register modelo de vista:

public ICommand RegisterCommand => new Command(Register);

El comando se expone a la vista a través de una propiedad que devuelve una referencia a .ICommand Cuando se llama al Execute método en el Command objeto , simplemente reenvía la llamada al método en el modelo de vista a través del delegado especificado en el Command constructor.

Un comando puede invocar un método asincrónico mediante las async palabras clave y await al especificar el delegado del Execute comando. Esto indica que la devolución de llamada es y Task debe esperarse. Por ejemplo, el código siguiente muestra cómo se construye una Command instancia de , que representa un comando de inicio de sesión, especificando un delegado para el método de SignInAsync modelo de vista:

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

Los parámetros se pueden pasar a las Execute acciones y CanExecute mediante la Command<T> clase para crear instancias del comando. Por ejemplo, el código siguiente muestra cómo se usa una Command<T> instancia para indicar que el NavigateAsync método requerirá un argumento de tipo string:

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

En las Command clases y Command<T> , el delegado al CanExecute método en cada constructor es opcional. Si no se especifica un delegado, Command devolverá true para CanExecute. Sin embargo, el modelo de vista puede indicar un cambio en el estado del CanExecute comando llamando al ChangeCanExecute método en el Command objeto . Esto hace que se genere el CanExecuteChanged evento . Los controles de la interfaz de usuario enlazados al comando actualizarán su estado habilitado para reflejar la disponibilidad del comando enlazado a datos.

Invocar comandos desde una vista

En el ejemplo de código siguiente se muestra cómo un Grid objeto de la LoginView clase enlaza a RegisterCommand en la LoginViewModel clase mediante una TapGestureRecognizer instancia de :

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

Un parámetro de comando también se puede definir opcionalmente mediante la CommandParameter propiedad . El tipo del argumento esperado se especifica en los Execute métodos y CanExecute de destino. Invocará TapGestureRecognizer automáticamente el comando de destino cuando el usuario interactúe con el control adjunto. El parámetro de comando, si se proporciona, se pasará como argumento al delegado del Execute comando.

Implementación de comportamientos

Los comportamientos permiten agregar la funcionalidad a los controles de interfaz de usuario sin tener que subclasarlos. En su lugar, la función se implementa en una clase de comportamiento y se asocia al control como si fuera parte de este. Los comportamientos le 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 asociar concisamente al control y empaquetar para su reutilización en más de una vista o aplicación. En el contexto de MVVM, los comportamientos son un enfoque útil para conectar controles a comandos.

Un comportamiento asociado a un control a través de propiedades adjuntas se conoce como comportamiento adjunto. Después, el comportamiento puede usar la API expuesta del elemento al que está asociado para agregar funcionalidad a ese control, u otros controles, en el árbol visual de la vista. La aplicación móvil eShopOnContainers contiene la LineColorBehavior clase , que es un comportamiento adjunto. Para obtener más información sobre este comportamiento, vea Mostrar errores de validación.

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

En la aplicación móvil eShopOnContainers, la BindableBehavior<T> clase deriva de la Behavior<T> clase . El propósito de la BindableBehavior<T> clase es proporcionar una clase base para Xamarin.Forms los comportamientos que requieren que el BindingContext comportamiento se establezca en el control adjunto.

La BindableBehavior<T> clase proporciona un método reemplazable OnAttachedTo que establece el BindingContext de comportamiento y un método reemplazable OnDetachingFrom que limpia .BindingContext Además, la clase almacena una referencia al control adjunto en la propiedad AssociatedObject.

La aplicación móvil eShopOnContainers incluye una EventToCommandBehavior clase , que ejecuta un comando en respuesta a un evento que se produce. Esta clase 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. En el ejemplo de código siguiente se muestra la clase EventToCommandBehavior:

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 y OnDetachingFrom se usan para registrar y anular el registro de un controlador de eventos para el evento definido en la EventName propiedad . A continuación, cuando se desencadena el evento, se invoca el OnFired método , que ejecuta el comando .

La ventaja de usar EventToCommandBehavior para ejecutar un comando cuando se desencadena un evento es que los comandos se pueden asociar a controles que no se diseñaron para interactuar con comandos. Además, esto mueve el código de control de eventos para ver los modelos, donde se puede probar unitariamente.

Invocar comportamientos desde una vista

EventToCommandBehavior es especialmente útil para adjuntar un comando a un control que no admite comandos. Por ejemplo, usa ProfileViewEventToCommandBehavior para ejecutar OrderDetailCommand cuando se desencadena el ItemTapped evento en el ListView que se enumeran los pedidos del usuario, como se muestra en el código siguiente:

<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 interacción con ListView. Cuando se selecciona un elemento en ListView, se activará el ItemTapped evento , que ejecutará OrderDetailCommand en .ProfileViewModel De forma predeterminada, los argumentos de evento para el evento se pasan al comando . Estos datos se convierten a medida que se pasan entre el origen y el destino por el convertidor especificado en la EventArgsConverter propiedad , que devuelve el Item de ListView desde ItemTappedEventArgs. Por lo tanto, cuando OrderDetailCommand se ejecuta , el objeto seleccionado Order se pasa como parámetro a la acción registrada.

Para obtener más información sobre los comportamientos, consulte Comportamientos.

Resumen

El patrón Model-View-ViewModel (MVVM) ayuda a separar limpiamente la lógica empresarial y de presentación de una aplicación de su interfaz de usuario (UI). Mantener una separación limpia entre la lógica de la aplicación y la interfaz de usuario ayuda a abordar numerosos problemas de desarrollo y puede facilitar la prueba, el mantenimiento y la evolución de una aplicación. También puede mejorar considerablemente las oportunidades de reutilización del código y permite a los desarrolladores y diseñadores de interfaz de usuario colaborar más fácilmente al desarrollar sus respectivas partes de una aplicación.

Con el patrón MVVM, la interfaz de usuario de la aplicación y la presentación subyacente y la lógica de negocios se separan en tres clases independientes: la vista, que encapsula la lógica de la interfaz de usuario y la interfaz de usuario; el modelo de vista, que encapsula la lógica y el estado de la presentación; y el modelo, que encapsula la lógica de negocios y los datos de la aplicación.