Compartir a través de


Atributo RelayCommand

El tipo RelayCommand es un atributo que permite generar propiedades de comando de retransmisión para métodos anotados. Su propósito es eliminar completamente la reutilizable que se necesita para definir comandos encapsulando métodos privados en un modelo de vista.

Nota:

Para poder funcionar, los métodos anotados deben estar en una clase parcial. Si el tipo está anidado, todos los tipos del árbol de sintaxis de declaración también se deben anotar como parciales. Si no lo hace, se producirán errores de compilación, ya que el generador no podrá generar una declaración parcial diferente de ese tipo con el comando solicitado.

API de la plataforma: RelayCommand, ICommand, IRelayCommand, IRelayCommand<T>, IAsyncRelayCommand, IAsyncRelayCommand<T>, Task, CancellationToken

Funcionamiento

El atributo RelayCommand se puede usar para anotar un método en un tipo parcial, de la siguiente manera:

[RelayCommand]
private void GreetUser()
{
    Console.WriteLine("Hello!");
}

Y generará un comando similar al siguiente:

private RelayCommand? greetUserCommand;

public IRelayCommand GreetUserCommand => greetUserCommand ??= new RelayCommand(GreetUser);

Nota:

El nombre del comando generado se creará en función del nombre del método. El generador usará el nombre del método y anexará "Command" al final, y quitará el prefijo "On", si está presente. Adicionalmente, para los métodos asíncronos, el sufijo "Async" también se elimina antes de añadir "Command".

Parámetros de comando

El atributo [RelayCommand] admite la creación de comandos para métodos con un parámetro. En ese caso, cambiará automáticamente el comando generado para que sea en su lugar IRelayCommand<T>, aceptando un parámetro del mismo tipo:

[RelayCommand]
private void GreetUser(User user)
{
    Console.WriteLine($"Hello {user.Name}!");
}

Esto dará como resultado el código generado siguiente:

private RelayCommand<User>? greetUserCommand;

public IRelayCommand<User> GreetUserCommand => greetUserCommand ??= new RelayCommand<User>(GreetUser);

El comando resultante usará automáticamente el tipo del argumento como argumento de tipo.

Comandos asincrónicos

El comando [RelayCommand] también admite el ajuste de métodos asincrónicos a través de las interfaces IAsyncRelayCommand y IAsyncRelayCommand<T>. Esto se controla automáticamente cada vez que un método devuelve un tipoTask. Por ejemplo:

[RelayCommand]
private async Task GreetUserAsync()
{
    User user = await userService.GetCurrentUserAsync();

    Console.WriteLine($"Hello {user.Name}!");
}

Esto dará como resultado el código siguiente:

private AsyncRelayCommand? greetUserCommand;

public IAsyncRelayCommand GreetUserCommand => greetUserCommand ??= new AsyncRelayCommand(GreetUserAsync);

Si el método toma un parámetro, el comando resultante también será genérico.

Hay un caso especial cuando el método tiene un CancellationToken, ya que se propagará al comando para habilitar la cancelación. Es decir, un método similar al siguiente:

[RelayCommand]
private async Task GreetUserAsync(CancellationToken token)
{
    try
    {
        User user = await userService.GetCurrentUserAsync(token);

        Console.WriteLine($"Hello {user.Name}!");
    }
    catch (OperationCanceledException)
    {
    }
}

Dará como resultado que el comando generado pase un token al método ajustado. Esto permite a los consumidores llamar a IAsyncRelayCommand.Cancelpara indicar ese token y permitir que las operaciones pendientes se detengan correctamente.

Habilitación y deshabilitación de comandos

A menudo resulta útil poder deshabilitar comandos y después, invalidar su estado y volver a comprobar si se pueden ejecutar o no. Para admitir esto, el atributo RelayCommand expone la propiedadCanExecute, que se puede usar para indicar una propiedad o método de destino que se va a usar para evaluar si se puede ejecutar un comando:

[RelayCommand(CanExecute = nameof(CanGreetUser))]
private void GreetUser(User? user)
{
    Console.WriteLine($"Hello {user!.Name}!");
}

private bool CanGreetUser(User? user)
{
    return user is not null;
}

De este modo, se invoca CanGreetUser cuando el botón se enlaza primero a la interfaz de usuario (por ejemplo, a un botón) y a continuación, se invoca IRelayCommand.NotifyCanExecuteChanged de nuevo cada vez que se invoca en el comando.

Por ejemplo, este es el modo en que un comando se puede enlazar a una propiedad para controlar su estado:

[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(GreetUserCommand))]
private User? selectedUser;
<!-- Note: this example uses traditional XAML binding syntax -->
<Button
    Content="Greet user"
    Command="{Binding GreetUserCommand}"
    CommandParameter="{Binding SelectedUser}"/>

En este ejemplo, la propiedad SelectedUser generada invocará el método GreetUserCommand.NotifyCanExecuteChanged() cada vez que cambie su valor. La interfaz de usuario tiene un enlace de control Button a GreetUserCommand, lo que significa que cada vez que se genera su evento CanExecuteChanged, llamará a su método CanExecute de nuevo. Esto hará que se evalúe el método CanGreetUser encapsulado, que devolverá el nuevo estado del botón en función de si la instancia User de entrada (que en la interfaz de usuario está vinculada a la propiedad SelectedUser) lo está null o no. Esto significa que, cada vez que SelectedUser se cambia, GreetUserCommand se habilitará o no en función de si esa propiedad tiene un valor, que es el comportamiento deseado en este escenario.

Nota:

El comando no conocerá automáticamente cuándo ha cambiado el valor devuelto para el método CanExecute o la propiedad. Es necesario que el desarrollador llame IRelayCommand.NotifyCanExecuteChanged para invalidar el comando y solicitar que el método vinculado CanExecute se evalúe de nuevo para actualizar el estado visual del control enlazado al comando.

Control de ejecuciones simultáneas

Cada vez que un comando es asincrónico, se puede configurar para decidir si se permiten ejecuciones simultáneas o no. Al usar el atributo RelayCommand, se puede establecer a través de la propiedad AllowConcurrentExecutions. El valor predeterminado es false, lo que significa que hasta que una ejecución está pendiente, el comando indicará su estado como deshabilitado. Si en su lugar se establece en true, se puede poner en cola cualquier número de invocaciones simultáneas.

Tenga en cuenta que si un comando acepta un token de cancelación, también se cancelará un token si se solicita una ejecución simultánea. La principal diferencia es que si se permiten ejecuciones simultáneas, el comando permanecerá habilitado y iniciará una nueva ejecución solicitada sin esperar a que se complete realmente la anterior.

Control de excepciones asincrónicas

Hay dos maneras diferentes de controlar las excepciones de los comandos de retransmisión asincrónica:

  • Esperar y volver a lanzar (valor predeterminado): cuando el comando espera la finalización de una invocación, cualquier excepción se lanzará naturalmente en el mismo contexto de sincronización. Esto suele significar que las excepciones que se producen simplemente bloquearían la aplicación, que es un comportamiento coherente con el de los comandos sincrónicos (donde las excepciones que se producen también bloquearán la aplicación).
  • Excepciones de flujo al programador de tareas: si un comando está configurado para fluir excepciones al programador de tareas, las excepciones que se producen no bloquearán la aplicación, sino que ambas estarán disponibles a través de la expuesta IAsyncRelayCommand.ExecutionTask así como la propagación hasta TaskScheduler.UnobservedTaskException. Esto permite escenarios más avanzados (por ejemplo, tener componentes de interfaz de usuario enlazados a la tarea y mostrar resultados diferentes en función del resultado de la operación), pero es más complejo usar correctamente.

El comportamiento predeterminado es tener comandos await y volver a iniciar excepciones. Esto se puede configurar a través de la propiedad FlowExceptionsToTaskScheduler:

[RelayCommand(FlowExceptionsToTaskScheduler = true)]
private async Task GreetUserAsync(CancellationToken token)
{
    User user = await userService.GetCurrentUserAsync(token);

    Console.WriteLine($"Hello {user.Name}!");
}

En este caso, try/catch ya no es necesario, ya que las excepciones ya no bloquearán la aplicación. Tenga en cuenta que esto también hará que otras excepciones no relacionadas no se vuelvan a iniciar automáticamente, por lo que debe decidir cuidadosamente cómo abordar cada escenario individual y configurar el resto del código correctamente.

Cancelar comandos para operaciones asincrónicas

Una última opción para los comandos asincrónicos es la capacidad de solicitar que se genere un comando cancel. Se trata de un ajuste ICommand un comando de retransmisión asincrónica que se puede usar para solicitar la cancelación de una operación. Este comando indicará automáticamente su estado para reflejar si se puede usar o no en un momento dado. Por ejemplo, si el comando vinculado no se está ejecutando, notificará su estado como tampoco ejecutable. Esto se puede usar de la siguiente manera:

[RelayCommand(IncludeCancelCommand = true)]
private async Task DoWorkAsync(CancellationToken token)
{
    // Do some long running work...
}

Esto hará que también se genere una propiedad DoWorkCancelCommand. Después, esto se puede enlazar a algún otro componente de interfaz de usuario para permitir que los usuarios cancelen fácilmente las operaciones asincrónicas pendientes.

Adición de atributos personalizados

Al igual que con las propiedades observables, el RelayCommand generador también incluye compatibilidad con atributos personalizados para las propiedades generadas. Para aprovechar esto, simplemente puede usar el destino [property: ] en las listas de atributos sobre métodos anotados y el kit de herramientas de MVVM reenvía esos atributos a las propiedades de comando generadas.

Por ejemplo, considere un método similar al siguiente:

[RelayCommand]
[property: JsonIgnore]
private void GreetUser(User user)
{
    Console.WriteLine($"Hello {user.Name}!");
}

Esto generará una propiedad GreetUserCommand, con el atributo [JsonIgnore] sobre él. Puede usar tantas listas de atributos que tienen como destino la propiedad como desee y todas ellas se reenviarán a las propiedades generadas.

Ejemplos

  • Consulte la aplicación de ejemplo (para varios marcos de interfaz de usuario) para ver el kit de herramientas de MVVM en acción.
  • También puede encontrar más ejemplos en las pruebas unitarias.