Uso de comandos en un modelo de vista

Completado

Ha visto cómo obtener datos de los modelos de vista a la interfaz de usuario y cómo puede usar el enlace bidireccional para devolver los datos a los modelos de vista.

El uso de enlaces bidireccionales de este tipo es la mejor manera de reaccionar a los cambios en la interfaz de usuario cada vez que los datos cambian. Muchos de los elementos que se administrarían como eventos pueden controlarse con enlaces bidireccionales y MVVM. Otros ejemplos son cosas como Switch.IsToggled y Slider.Value, que se pueden reflejar en nuestro modelo de vista como un valor booleano o entero, sin tener que usar eventos.

Aun así, algunas cosas como la activación de un elemento Button o MenuItem no están vinculadas directamente a la modificación de datos. Estas interacciones todavía necesitan un control similar a un evento. Dado que estos componentes de interfaz de usuario suelen invocar algún tipo de lógica con los datos, queremos esa lógica en el modelo de vista. Pero no se pretende controlarlas como eventos Clicked y Selected en el código subyacente, si es posible. Queremos que tanto como sea posible estar en el modelo de vista, de esa manera es probable.

Uso del patrón de comandos

Muchos de los controles MAUI de .NET que tienen este tipo de interacción admiten el enlace a una propiedad que expone una interfaz ICommand. Es más probable que esta propiedad tenga el nombre Command. El botón es un ejemplo:

<Button Text="Give Bonus" Command="{Binding GiveBonusCommand}" />

El control sabe cuándo invocar el comando. Por ejemplo, un botón invoca el comando cuando se presiona. El comando de este ejemplo está enlazado a la propiedad GiveBonusCommand del modelo de vista. El tipo de propiedad tiene que implementar la interfaz ICommand. El código debería tener un aspecto parecido al siguiente:

public class EmployeeViewModel : INotifyPropertyChanged
{
    public ICommand GiveBonusCommand {get; private set;}
    ...
}

La interfaz ICommand tiene un método Execute al que se llama cuando se hace clic en el botón. De este modo, ICommand.Execute reemplazará directamente el código de control de eventos Button.Click.

La interfaz completa de ICommand tiene dos métodos más: CanExecute y CanExecuteChanged pueden usarse para determinar si un control debe aparecer habilitado o deshabilitado.

Un botón, por ejemplo, podría aparecer atenuado si CanExecute devuelve false.

Este es el aspecto de la interfaz ICommand en C#:

public interface ICommand
{
    bool CanExecute(object parameter);
    void Execute(object parameter);
    event EventHandler CanExecuteChanged;
}

Usar la clase Command

Este patrón de comando permite mantener una separación clara entre el comportamiento de la interfaz de usuario y su implementación. Pero puede complicar el código si se necesita crear una clase independiente para implementar cada controlador de eventos.

En lugar de crear varias clases personalizadas que implementen la interfaz, lo habitual es usar Command o Command<T>. Estas clases implementan ICommand pero exponen su comportamiento como propiedades en el modelo de vista que puede establecer. Esto permite implementar la propiedad GiveBonusCommand que se ha descrito antes completamente en la clase modelo de vista:

public class EmployeeViewModel : INotifyPropertyChanged
{
    public ICommand GiveBonusCommand {get; private set;}
    public EmployeeViewModel(Employee model)
    {
        GiveBonusCommand = new Command(GiveBonusExecute, GiveBonusCanExecute)
    }

    void GiveBonusExecute()
    {
        //logic for giving bonus
    }

    bool GiveBonusCanExecute()
    {
        //logic for deciding if "give bonus" button should be enabled.
    }
}

En este código, el comportamiento de Execute se proporciona mediante el método GiveBonusExecute. Y CanExecute se proporciona mediante GiveBonusCanExecute. Los delegados a esos métodos se pasan al constructor Command. En este ejemplo, no hay ninguna implementación para CanExecuteChanged.

Simplificación con el kit de herramientas de MVVM

La biblioteca del kit de herramientas de MVVM contiene implementaciones de ICommand conocidas como RelayCommand y AsyncRelayCommand. También proporciona generadores de origen para simplificar aún más este código. En el siguiente ejemplo, se generará el GiveBonusCommand estableciendo tanto el método a llamar para ejecutar como a llamar para ver si se puede ejecutar. El atributo [RelayCommand] se usa en el método GiveBonus y generará el GiveBonusCommand. Además, al establecer la propiedad CanExecute en el atributo en el nombre del método que queremos enlazar al método CanExecute del ICommand, generará el código para configurarlo para nosotros.

public partial class EmployeeViewModel : ObservableObject
{
    public EmployeeViewModel(Employee model)
    {
    }

    [RelayCommand(CanExecute = nameof(GiveBonusCanExecute))]
    void GiveBonus()
    {
        //logic for giving bonus
    }

    bool GiveBonusCanExecute()
    {
        //logic for deciding if "give bonus" button should be enabled.
        return true;
    }
}

El kit de herramientas de MVVM también controla los métodos async, que son comunes en la programación de .NET.

Comandos con parámetros

La interfaz ICommand acepta un parámetro object para los métodos CanExecute y Execute. .NET MAUI implementa esta interfaz sin ninguna comprobación de tipos a través de la clase Command. Los delegados que adjunte al comando deben realizar su propia comprobación de tipos para asegurarse de que se pasa el parámetro correcto. .NET MAUI también proporciona la implementación de Command<T> donde se establece el tipo de parámetro esperado. Cuando cree un comando que acepte un único tipo de parámetro, use Command<T>.

Los controles MAUI de .NET que implementan el patrón de comandos proporcionan la propiedad CommandParameter. Al establecer esta propiedad, puede pasar un parámetro al comando cuando se invoca con Execute, o cuando comprueba el estado del método CanExecute.

En este ejemplo, el valor de cadena 25 se envía al comando:

<Button Text="Give Bonus" Command="{Binding GiveBonusCommand}" CommandParameter="25" />

El comando tendría que interpretar y convertir ese parámetro de cadena. Hay muchas maneras de proporcionar un parámetro fuertemente tipado.

  • En lugar de usar la sintaxis de atributo para definir CommandParameter, use elementos XAML.

    <Button Text="Give Bonus" Command="{Binding GiveBonusCommand}">
        <Button.CommandParameter>
            <x:Int32>25</x:Int32>
        </Button.CommandParameter>
    </Button>
    
  • Enlace el CommandParameter a una instancia del tipo correcto.

  • Si el CommandParameter está enlazado al tipo incorrecto, aplique un convertidor para convertir el valor en el tipo correcto.

Comprobación de conocimientos

1.

¿Cuál es el propósito de la interfaz ICommand?

2.

¿Cómo pueden las clases Command o Command<T> simplificar el uso de la interfaz ICommand?

3.

¿Cuál es el rol de la propiedad CommandParameter en los controles MAUI de .NET?