Uso de un modelo de vista

Completado

Después de aprender sobre los componentes que componen el patrón MVVM, probablemente descubrió que el modelo y la vista eran fáciles de definir. Vamos a explorar cómo usar el modelo de vista para definir mejor su rol en el patrón.

Exposición de las propiedades a la interfaz de usuario

Como en el ejemplo anterior, los modelos de vista suelen basarse en los modelos de la mayoría de sus datos y en la lógica de negocios. Pero el modelo de vista es el que da formato a los datos, y los convierte y enriquece en la forma que requiere la vista actual.

Aplicación de formato mediante un modelo de vista

Previamente se ha visto un ejemplo de aplicación de formato a los días de vacaciones. El formato de fecha, la codificación de caracteres y la serialización son ejemplos de la manera en que el modelo de vista puede dar formato a los datos del modelo.

Conversión mediante un modelo de vista

A menudo, el modelo proporciona información de forma indirecta. Pero el modelo de vista puede solucionar ese problema. Por ejemplo, supongamos que se quiere mostrar en pantalla si un empleado es un supervisor. Pero el modelo Employee no proporciona esa información directamente. En su lugar, tenemos que inferir este hecho en función de si la persona tiene subordinados. Pongamos por caso que el modelo tiene esta propiedad:

public IList<Employee> DirectReports
{
    get
    {
        ...
    }
}

Si la lista está vacía, se deduce que este Employee no es un supervisor. En este caso, EmployeeViewModel incluye la propiedad IsSupervisor que proporciona esa lógica:

public bool IsSupervisor => _model.DirectReports.Any();

Enriquecimiento mediante un modelo de vista

A veces, es posible que un modelo pueda solo proporcionar un id. para datos relacionados. O también es posible que tenga que ir a varias clases de modelo para correlacionar los datos que se necesitan para una sola pantalla. El modelo de vista ofrece un lugar ideal para llevar a cabo también estas tareas. Supongamos que desea mostrar todos los proyectos que un empleado está administrando actualmente. Estos datos no forman parte de la clase de modelo Employee. Se pueden acceder a ellos al examinar la clase de modelo CompanyProjects. Como siempre, nuestro EmployeeViewModel expone su trabajo como una propiedad pública:

public IEnumerable<string> ActiveProjects => CompanyProjects.All
    .Where(p => p.Owner == _model.Id && p.IsActive)
    .Select(p => p.Name);

Uso de propiedades de paso a través con un modelo de vista

A menudo, un modelo de vista necesita exactamente la propiedad que el modelo proporciona. Para estas propiedades, el modelo de vista simplemente pasa los datos a través:

public string Name
{
    get => _model.Name;
    set => _model.Name = value;
}

Establecimiento del ámbito del modelo de vista

Se puede usar un modelo de vista en cualquier nivel en el que haya una vista. Por lo general, una página tiene un modelo de vista, pero las subvistas de la página también podrían tenerlo. Una razón común para los modelos de vista anidados es cuando la página muestra un ListView en la página. La lista tiene un modelo de vista que representa la colección, como EmployeeListViewModel. Cada elemento de la lista es un EmployeeViewModel.

Diagrama de un elemento EmployeeListViewModel con varios subobjetos EmployeeViewModel.

También es habitual disponer de un modelo de vista de nivel superior que contenga los datos y el estado de toda la aplicación, pero sin estar asociado a ninguna página en concreto. Un modelo de vista de este tipo suele usarse para mantener el elemento "activo". Considere el ejemplo de ListView que acaba de describir. Cuando el usuario selecciona la fila de un empleado, dicho empleado representa el elemento actual. Si el usuario va a una página de detalles o selecciona un botón de la barra de herramientas mientras esa fila está seleccionada, la acción o la pantalla deben estar relacionadas con ese empleado. Una forma elegante de controlar este escenario consiste en hacer que ListView.SelectItem esté enlazado a los datos de una propiedad a la que también puedan acceder la barra de herramientas o la página de detalles. La técnica de colocar esa propiedad en un modelo de vista central da buenos resultados.

Cómo identificar cuándo se puede reutilizar un modelo de vista con vistas

La manera en que se define la relación entre un modelo de vista y un modelo, así como entre un modelo de vista y una vista, viene determinada más bien por los requisitos de la aplicación, en vez de por las reglas. La finalidad del modelo de vista es proporcionar a la vista la estructura y los datos que necesita. Eso debe guiar decisiones acerca del "tamaño" para definir el ámbito de un modelo de vista.

A menudo, los modelos de vista reflejan fielmente la estructura de una clase de modelo y tienen una relación uno a uno con esa clase. Ya se ha visto un ejemplo antes con EmployeeViewModel, que encapsulaba y ampliaba una sola instancia Employee. Pero no siempre es una relación uno a uno. Si el modelo de vista está diseñado para proporcionar lo que necesita la vista, es posible que termine con algo parecido HRDashboardViewModel a proporcionar información general de un departamento de RR. HH., que no tiene ninguna relación explícita con ningún modelo, pero puede usar datos de cualquier clase de modelo.

De forma similar, puede que se descubra que los modelos de vista y las vistas a menudo tienen una relación uno a uno. aunque no es necesariamente el caso. Consideremos de nuevo un elemento ListView que muestra una fila para cada empleado. Al seleccionar una de las filas, se le dirige a una página de detalles del empleado.

La página de lista tiene su propio modelo de vista con una colección. Como se ha sugerido anteriormente, esa colección podría ser una colección de objetos EmployeeViewModel. Cuando el usuario selecciona una fila, la instancia EmployeeViewModel podría pasarse a EmployeeDetailPage. y la página de detalles podría usar dicha instancia EmployeeViewModel como BindingContext.

Este escenario puede ser una excelente oportunidad para reutilizar un modelo de vista. Pero hay que tener en cuenta que los modelos de vista están pensados para proporcionar lo que la vista necesita. En algunos casos, puede que le interese tener modelos de vista independientes, incluso si están basados en la misma clase de modelo. En este ejemplo, las filas de ListView probablemente necesiten mucha menos información que la página de detalles completa. Si la recuperación de los datos que necesita la página de detalles agrega mucho si se sobrecarga, es posible que desee tener modelos EmployeeListRowViewModel y EmployeeDetailViewModel que abastezcan estas vistas respectivas.

Modelo de objetos Viewmodel

El uso de una clase base que implementa INotifyPropertyChanged significa que no es necesario volver a implementar la interfaz en cada modelo de vista. Considere la aplicación de RR. HH. como se describe en la parte anterior de este módulo de entrenamiento. La clase EmployeeViewModel implementó la interfaz INotifyPropertyChanged y proporcionó un método auxiliar denominado OnPropertyChanged para generar el evento PropertyChanged. Otros modelos de vista del proyecto, como los que describen los recursos asignados a un empleado, también requerirían INotifyPropertyChanged para integrar completamente con una vista.

La biblioteca MVVM Toolkit, que forma parte del kit de herramientas de la comunidad de .NET, es una colección de tipos estándar, independientes y ligeros que proporcionan una implementación inicial para compilar aplicaciones modernas mediante el patrón MVVM.

En lugar de escribir su propia clase base de modelo de vista, hereda de la clase ObservableObject del kit de herramientas, que proporciona todo lo que necesita para una clase base de modelo de vista. El EmployeeViewModel se puede simplificar desde:

using System.ComponentModel;

public class EmployeeViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    private Employee _model;

    public string Name
    {
        get {...}
        set
        {
            _model.Name = value;
            OnPropertyChanged(nameof(Name))
        }
    }

    protected void OnPropertyChanged(string propertyName) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Para el código siguiente:

using Microsoft.Toolkit.Mvvm.ComponentModel;

public class EmployeeViewModel : ObservableObject
{
    private string _name;

    public string Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }
}

El código se puede simplificar aún más mediante generadores de código fuente proporcionados por el kit de herramientas de MVVM. Al crear la clase partial y agregar [ObservableProperty] a la variable private, la propiedad pública Name se genera con las notificaciones de cambio de propiedad adecuadas.

using Microsoft.Toolkit.Mvvm.ComponentModel;

public partial class EmployeeViewModel : ObservableObject
{
    [ObservableProperty]
    private string _name;
}

El kit de herramientas de MVVM se distribuye a través del paquete NuGet CommunityToolkit.Mvvm.

Comprobación de conocimientos

1.

Cuando se usa el patrón MVVM con .NET MAUI, el modelo, la vista y el modelo de vista no se desacoplan completamente entre sí. ¿Qué opción describe una dependencia común entre los elementos de MVVM?

2.

¿Qué elemento es más probable que esté estrechamente acoplado a la plataforma y sea más difícil crear pruebas unitarias para él: el modelo, la vista o el modelo de vista?