Modelo-Vista-Modelo de vista (MVVM)

Sugerencia

Este contenido es un extracto del libro electrónico "Patrones de aplicaciones empresariales con .NET MAUI", disponible en Documentación de .NET o como un PDF descargable y gratuito que se puede leer sin conexión.

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

La experiencia del desarrollador de .NET MAUI normalmente supone crear una interfaz de usuario en XAML y, luego, agregar código subyacente que funcione en la interfaz de usuario. A medida que las aplicaciones se modifican y aumentan de tamaño y á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 realizar pruebas unitarias de este código.

El patrón MVVM ayuda a separar limpiamente la lógica de presentación y negocios 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 facilita 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 a los diseñadores de la 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 de ellos sirve para un propósito diferente. En el diagrama siguiente se muestran las relaciones entre los tres componentes.

The MVVM pattern

Además de comprender las responsabilidades de cada componente, también es importante comprender cómo interactúan. En general, la vista "conoce" el modelo de vista y el modelo de vista "conoce" el modelo, pero el modelo desconoce el modelo de vista y el modelo de vista desconoce 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 una implementación de modelo existente encapsula la lógica de negocios existente, puede ser difícil o arriesgado cambiarla. En este escenario, el modelo de vista actúa como adaptador para las clases de modelo y evita que realice 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 modelo de vista y el código del modelo, siempre que la vista se implemente completamente en XAML o C#. 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 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 de forma eficaz consiste en comprender cómo factorizar el código de la aplicación en las clases correctas y 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 es el caso de las animaciones.

En una aplicación .NET MAUI, una vista suele ser una clase ContentPage derivada o una clase ContentView derivada. 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 enlazar 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 sean responsables de definir los cambios de estado lógico que afectan a algunos aspectos de la presentación de la vista, por ejemplo, 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 a las propiedades del modelo de vista, 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 Command del control puede estar enlazada por datos a una propiedad ICommand del modelo de vista. Cuando se invoca el comando del control, se ejecuta el código del modelo de vista. Además de los comandos, los comportamientos pueden estar asociados a un objeto en la vista y pueden escuchar el comando que se va a invocar o el evento que se va a generar. En respuesta, el comportamiento puede invocar una propiedad 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 avisa a la vista los cambios de estado mediante 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 capacidad de respuesta de la interfaz de usuario con operaciones asincrónicas.

Las aplicaciones multiplataforma 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 formato que la vista puede consumir fácilmente. Para ello, el modelo de vista a veces realiza la conversión de datos. Es una buena idea situar esta conversión de datos en el modelo de vista porque proporciona propiedades que la vista 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 evento PropertyChanged. Los modelos de vista satisfacen este requisito al implementar la interfaz INotifyPropertyChanged y generar el evento PropertyChanged cuando se cambia una propiedad.

Para las colecciones, se proporciona la vista descriptiva ObservableCollection<T>. Esta colección implementa una notificación de cambios en la colección, que evita al desarrollador tener que implementar la interfaz INotifyCollectionChanged 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 de negocios. Algunos ejemplos de objetos de modelo son objetos de transferencia de datos (DTO), objetos CLR antiguos sin formato (POCO) 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 .NET MAUI. 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 composición inicial de vista y composición inicial de modelo de vista. Elegir entre la composición inicial de vista y la composición inicial del modelo de vista es un asunto de preferencia y complejidad. Sin embargo, todos los enfoques comparten el mismo objetivo, que es que la vista tenga asignado un modelo de vista a su propiedad BindingContext.

Con la composición inicial de 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 de acoplamiento flexible y comprobables por unidades, 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 construcción de primera vista se alinea con el sistema de navegación de Microsoft Maui que es responsable de construir páginas cuando se produce la navegación, lo que hace que una composición de primer modelo de vista sea complejo y esté mal alineado con la plataforma.

Con la composición inicial 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. Para algunos desarrolladores, la composición inicial del modelo de vista resulta más natural, ya que la creación de la vista puede abstraerse, lo que les permite centrarse en la estructura lógica sin 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 al limitar 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>
    <!-- Omitted for brevity... -->
</ContentPage>

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

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

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

Una vista puede tener código en el archivo de código subyacente, lo que da lugar a que el modelo de vista se asigne a su propiedad BindingContext. 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 simple. 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.

Actualización de las vistas en respuesta a los cambios en el modelo o el modelo de vista subyacente

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

La aplicación debe diseñarse para el uso correcto de la notificación de cambio de propiedad, de modo que se cumplan los siguientes requisitos:

  • Generar siempre un evento PropertyChanged si cambia el valor de una propiedad pública. No suponer que se puede omitir la generación del evento PropertyChanged debido al conocimiento de cómo se produce el enlace XAML.
  • Generar siempre un evento PropertyChanged para las propiedades calculadas cuyos valores usan otras propiedades en el modelo de vista o modelo.
  • Generar siempre el evento PropertyChanged 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 este hecho se produce 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 actualizado parcialmente. Además, es posible que los eventos PropertyChanged desencadenen cambios en cascada. Por lo general, los cambios en cascada requieren que se realicen actualizaciones antes de que el cambio en cascada se pueda ejecutar de forma segura.
  • Nunca generar un evento PropertyChanged si la propiedad no cambia. Esto significa que debe comparar los valores antiguos y nuevos antes de generar el evento PropertyChanged.
  • Nunca generar el evento PropertyChanged durante el constructor de un modelo de vista si va a inicializar una propiedad. Los controles enlazados a datos de la vista no se habrán suscrito para recibir notificaciones de cambios en este momento.
  • Nunca generar más de un evento PropertyChanged 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 propiedad NumberOfItems cuya memoria auxiliar es el campo _numberOfItems, si un método incrementa _numberOfItems cincuenta veces durante la ejecución de un bucle, solo debe generar una notificación de cambio de propiedad en la propiedad NumberOfItems una vez, después de que se complete todo el trabajo. Para los métodos asincrónicos, genere el evento PropertyChanged para un nombre de propiedad determinado en cada segmento sincrónico de una cadena de continuación asincrónica.

Una manera sencilla de proporcionar esta funcionalidad sería crear una extensión de la clase BindableObject. En este ejemplo, la clase ExtendedBindableObject proporciona notificaciones de cambio, que se muestran 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)
    {
        // Omitted for brevity ...
    }
}

La clase BindableObject de .NET MAUI implementa la interfaz INotifyPropertyChanged y proporciona un método OnPropertyChanged. La clase ExtendedBindableObject proporciona el método RaisePropertyChanged para invocar la notificación de cambio de propiedad y, al hacerlo, usa la funcionalidad proporcionada por la clase BindableObject.

Las clases de modelo de vista se pueden derivar de la clase ExtendedBindableObject. Por lo tanto, cada clase de modelo de vista usa el método RaisePropertyChanged de la clase ExtendedBindableObject para proporcionar una notificación de cambio de propiedad. En el ejemplo de código siguiente se muestra cómo la aplicación multiplataforma eShopOnContainers invoca la notificación de cambio de propiedad mediante una expresión lambda:

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

El uso de una expresión lambda de esta manera supone un pequeño costo de rendimiento porque esta expresión debe evaluarse con 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 compatibilidad con la seguridad y la refactorización de tipos en tiempo de compilación al cambiar el nombre de las propiedades.

Marcos MVVM

El patrón MVVM está bien establecido en .NET y la comunidad ha creado muchos marcos que ayudan a facilitar este desarrollo. Aunque cada marco proporciona un conjunto diferente de características, la norma es proporcionar un modelo de vista común con una implementación de la interfaz INotifyPropertyChanged. Entre las características adicionales de los marcos MVVM se incluyen comandos personalizados, asistentes de navegación, componentes de inserción de dependencias o localizador de servicios e integración de la plataforma de interfaz de usuario. Aunque no es necesario usar estos marcos, pueden acelerar y estandarizar el desarrollo. La aplicación multiplataforma eShopOnContainers usa el kit de herramientas de MVVM de la comunidad .NET. Al elegir un marco, debe tener en cuenta las necesidades de la aplicación y los puntos fuertes de su equipo. En la lista siguiente se incluyen algunos de los marcos MVVM más comunes para .NET MAUI.

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

En las aplicaciones multiplataforma, las acciones se suelen invocar 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 radica 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 controles de la interfaz de usuario. Encapsulan el código que implementa la acción y ayudan a evitar que se desacople de su representación visual en la vista. De este modo, los modelos de vista se vuelven más portátiles para las nuevas plataformas, ya que no tienen una dependencia directa de los eventos proporcionados por el marco de interfaz de usuario de la plataforma. .NET MAUI incluye controles que se pueden conectar mediante declaración a un comando y estos controles invocan el comando cuando el usuario interactúa con el control.

Los comportamientos también permiten que los controles se conecten mediante declaración a un comando. Sin embargo, se pueden usar comportamientos para invocar una acción asociada a un intervalo de eventos generados por un control. Por lo tanto, los comportamientos solucionan muchos de los mismos escenarios que los controles habilitados para comandos, a la vez que proporcionan un mayor grado de flexibilidad y control. Además, también se pueden usar comportamientos para asociar objetos o métodos de comandos a controles que no se han diseñado para interactuar con los comandos.

Implementación de comandos

Los modelos de vista suelen exponer propiedades públicas, para enlazar desde la vista, que implementan la interfaz ICommand. Muchos controles y gestos de .NET MAUI proporcionan una propiedad Command, que pueden ser datos enlazados a un objeto ICommand proporcionado por el modelo de vista. El control de botón es uno de los controles más usados, y proporciona una propiedad de comando que se ejecuta cuando se hace clic en el botón.

Nota:

Aunque es posible exponer la implementación real de la interfaz ICommand que usa el modelo de vista (por ejemplo, Command<T> o RelayCommand), se recomienda exponer los comandos públicamente como ICommand. De este modo, si más adelante necesita cambiar la aplicación, podrá hacerlo fácilmente.

La interfaz ICommand define un método Execute, que encapsula la propia operación, un método CanExecute, que indica si se puede invocar el comando y un evento CanExecuteChanged que se produce cuando tienen lugar cambios que afectan a la ejecución del comando. En la mayoría de los casos, solo se proporcionará el método Execute para nuestros comandos. Para información general más detallada de ICommand, consulte Commanding en la documentación de .NET MAUI.

Con .NET MAUI se proporcionan las clases Command y Command<T> que implementan la interfaz ICommand, donde T es el tipo de los argumentos para Execute y CanExecute. Command y Command<T> son implementaciones básicas que proporcionan el conjunto mínimo de funcionalidades necesarias para la interfaz ICommand.

Nota

Muchos marcos MVVM ofrecen implementaciones con más características de la interfaz ICommand.

El constructor Command o Command<T> requiere un objeto de devolución de llamada Action al que se llama cuando se invoca el método ICommand.Execute. El método CanExecute es un parámetro de constructor opcional y es una función que devuelve un valor booleano.

La aplicación multiplataforma eShopOnContainers usa los objetos RelayCommand y AsyncRelayCommand. La principal ventaja para las aplicaciones modernas es que AsyncRelayCommand proporciona una mejor funcionalidad para las operaciones asincrónicas.

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

public ICommand RegisterCommand { get; }

El comando se expone a la vista mediante una propiedad que devuelve una referencia a un objeto ICommand. Cuando se llama al método Execute en el objeto Command, simplemente se reenvía la llamada al método en el modelo de vista mediante el delegado especificado en el constructor Command. Un comando puede invocar un método asincrónico mediante las palabras clave async y await al especificar el delegado del Execute comando. Esto indica que la devolución de llamada es un objeto Task y se debe esperar. Por ejemplo, el código siguiente muestra cómo se construye una instancia de ICommand, que representa un comando de inicio de sesión, mediante la especificación de un delegado para el método de modelo de vista SignInAsync:

public ICommand SignInCommand { get; }
...
SignInCommand = new AsyncRelayCommand(async () => await SignInAsync());

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

public ICommand NavigateCommand { get; }

...
NavigateCommand = new AsyncRelayCommand<string>(NavigateAsync);

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

Invocación de comandos desde una vista

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

<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 propiedad CommandParameter. El tipo del argumento esperado se especifica en los métodos de destino Execute y CanExecute. El objeto TapGestureRecognizer invocará automáticamente el comando de destino cuando el usuario interactúe con el control asociado. El objeto CommandParameter, si se proporciona, se pasará como argumento al delegado Execute del comando.

Implementación de comportamientos

Los comportamientos permiten agregar funcionalidad a los controles de interfaz de usuario sin tener que aplicarles subclases. 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 permiten implementar código que normalmente tendría que escribirse como código subyacente, ya que interactúa directamente con la API del control, de manera que puede asociarse al control de manera concisa y empaquetarse para reutilizarlo en más de una aplicación. En el contexto de MVVM, los comportamientos son un método útil para conectar controles a comandos.

Un comportamiento asociado a un control mediante propiedades asociadas se conoce como comportamiento asociado. El comportamiento puede usar entonces la API expuesta del elemento al que está asociado para agregar funcionalidad a ese control, o a otros controles, en el árbol visual de la vista.

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

En la aplicación multiplataforma eShopOnContainers, la clase BindableBehavior<T> se obtiene de la clase Behavior<T>. El propósito de la clase BindableBehavior<T> es proporcionar una clase base para cualquier comportamiento de .NET MAUI que requiera que el objeto BindingContext del comportamiento se establezca en el control asociado.

La clase BindableBehavior<T> proporciona un método OnAttachedTo reemplazable que establece el objeto BindingContext del comportamiento, y un método OnDetachingFrom reemplazable que limpia el objeto BindingContext.

La aplicación multiplataforma eShopOnContainers incluye una clase EventToCommandBehavior que proporciona el kit de herramientas de la comunidad MAUI. EventToCommandBehavior ejecuta un comando en respuesta a un evento que se produce. Esta clase se obtiene de la clase BaseBehavior<View>, de modo que el comportamiento puede enlazar con un objeto ICommand especificado por una propiedad Command, y ejecutarlo, cuando se usa el comportamiento. En el ejemplo de código siguiente se muestra la clase EventToCommandBehavior:

/// <summary>
/// The <see cref="EventToCommandBehavior"/> is a behavior that allows the user to invoke a <see cref="ICommand"/> through an event. It is designed to associate Commands to events exposed by controls that were not designed to support Commands. It allows you to map any arbitrary event on a control to a Command.
/// </summary>
public class EventToCommandBehavior : BaseBehavior<VisualElement>
{
    // Omitted for brevity...

    /// <inheritdoc/>
    protected override void OnAttachedTo(VisualElement bindable)
    {
        base.OnAttachedTo(bindable);
        RegisterEvent();
    }

    /// <inheritdoc/>
    protected override void OnDetachingFrom(VisualElement bindable)
    {
        UnregisterEvent();
        base.OnDetachingFrom(bindable);
    }

    static void OnEventNamePropertyChanged(BindableObject bindable, object oldValue, object newValue)
        => ((EventToCommandBehavior)bindable).RegisterEvent();

    void RegisterEvent()
    {
        UnregisterEvent();

        var eventName = EventName;
        if (View is null || string.IsNullOrWhiteSpace(eventName))
        {
            return;
        }

        eventInfo = View.GetType()?.GetRuntimeEvent(eventName) ??
            throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't resolve the event.", nameof(EventName));

        ArgumentNullException.ThrowIfNull(eventInfo.EventHandlerType);
        ArgumentNullException.ThrowIfNull(eventHandlerMethodInfo);

        eventHandler = eventHandlerMethodInfo.CreateDelegate(eventInfo.EventHandlerType, this) ??
            throw new ArgumentException($"{nameof(EventToCommandBehavior)}: Couldn't create event handler.", nameof(EventName));

        eventInfo.AddEventHandler(View, eventHandler);
    }

    void UnregisterEvent()
    {
        if (eventInfo is not null && eventHandler is not null)
        {
            eventInfo.RemoveEventHandler(View, eventHandler);
        }

        eventInfo = null;
        eventHandler = null;
    }

    /// <summary>
    /// Virtual method that executes when a Command is invoked
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="eventArgs"></param>
    [Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)]
    protected virtual void OnTriggerHandled(object? sender = null, object? eventArgs = null)
    {
        var parameter = CommandParameter
            ?? EventArgsConverter?.Convert(eventArgs, typeof(object), null, null);

        var command = Command;
        if (command?.CanExecute(parameter) ?? false)
        {
            command.Execute(parameter);
        }
    }
}

Los métodos OnAttachedTo y OnDetachingFrom se usan para registrar y anular el registro de un controlador de eventos para el evento definido en la propiedad EventName. A continuación, cuando se desencadena el evento, se invoca el método OnTriggerHandled, que ejecuta el comando.

La ventaja de usar EventToCommandBehavior para ejecutar un comando cuando se desencadena un evento es que se pueden asociar comandos con controles que no se han diseñado para interactuar con comandos. Además, el código de control de eventos se traslada a los modelos de vista, donde se pueden realizar pruebas unitarias.

Invocación de comportamientos desde una vista

EventToCommandBehavior es especialmente útil para asociar un comando a un control que no admite comandos. Por ejemplo, LoginView usa EventToCommandBehavior para ejecutar ValidateCommand cuando el usuario cambia el valor de su contraseña, como se muestra en el código siguiente:

<Entry
    IsPassword="True"
    Text="{Binding Password.Value, Mode=TwoWay}">
    <!-- Omitted for brevity... -->
    <Entry.Behaviors>
        <mct:EventToCommandBehavior
            EventName="TextChanged"
            Command="{Binding ValidateCommand}" />
    </Entry.Behaviors>
    <!-- Omitted for brevity... -->
</Entry>

En tiempo de ejecución, EventToCommandBehavior responderá a la interacción con Entry. Cuando un usuario escribe en el campo Entry, se desencadena el evento TextChanged, que ejecutará ValidateCommand en LoginViewModel. De forma predeterminada, los argumentos del evento se pasan al comando. Si es necesario, la propiedad EventArgsConverter se puede usar para convertir el objeto EventArgs proporcionado por el evento en un valor que el comando espera como entrada.

Para más información sobre los comportamientos, consulte Comportamientos en el Centro para desarrolladores de .NET MAUI.

Resumen

El patrón Modelo-Vista-Modelo de vista (MVVM) ayuda a separar limpiamente la lógica de presentación y de negocios 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 facilita 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 a los diseñadores de la 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 lógica de presentación y negocios subyacente se separan en tres clases distintas: 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.