Compartilhar via


Atributo do RelayCommand

O tipo RelayCommand é um atributo que permite gerar propriedades de comando de retransmissão para métodos anotados. Sua finalidade é eliminar completamente o padrão necessário para definir comandos que encapsulam métodos privados em um viewmodel.

Observação

Para funcionar, os métodos anotados precisam estar em uma classe parcial. Se o tipo estiver aninhado, todos os tipos da árvore de sintaxe de declaração também deverão ser anotados como parciais. Não fazer isso resultará em erros de compilação, pois o gerador não poderá gerar uma declaração parcial diferente desse tipo com o comando solicitado.

APIs da plataforma:RelayCommand, ICommand, IRelayCommand, IRelayCommand<T>, IAsyncRelayCommand, IAsyncRelayCommand<T>, Task, CancellationToken

Como ele funciona

O atributo RelayCommand pode ser usado para anotar um método em um tipo parcial, assim como:

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

E gerará um comando como este:

private RelayCommand? greetUserCommand;

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

Observação

O nome do comando gerado será criado com base no nome do método. O gerador usará o nome do método e acrescentará "Command" no final, e removerá o prefixo “Ativado”, se estiver presente. Além disso, para métodos assíncronos, o sufixo "Async" também será removido antes do "Command" ser anexado.

Parâmetros de comando

O atributo [RelayCommand] dá suporte a criação de comandos para métodos com um parâmetro. Nesse caso, mudará automaticamente o comando gerado para um IRelayCommand<T>, aceitando um parâmetro do mesmo tipo:

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

Isso resultará no seguinte código gerado:

private RelayCommand<User>? greetUserCommand;

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

O comando resultante usará automaticamente o tipo do argumento como argumento de tipo.

Comandos assíncronos

O comando [RelayCommand] também dá suporte a métodos assíncronos de encapsulamento por meio das interfaces IAsyncRelayCommand e IAsyncRelayCommand<T>. Isso é tratado automaticamente sempre que um método retorna um tipo Task. Por exemplo:

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

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

Isso resultará no seguinte código:

private AsyncRelayCommand? greetUserCommand;

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

Se o método usar um parâmetro, o comando resultante também será genérico.

Há um caso especial quando o método tem um CancellationToken, pois isso será propagado para o comando para habilitar o cancelamento. Ou seja, um método como este:

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

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

Resultará no comando gerado passando um token para o método encapsulado. Isso permite que os consumidores apenas chamem IAsyncRelayCommand.Cancel para sinalizar esse token e permitir que as operações pendentes sejam interrompidas corretamente.

Habilitar e desabilitar comandos

Muitas vezes é útil desabilitar comandos e, posteriormente, invalidar o estado e verificar novamente se eles podem ser executados ou não. Para permitir isso, o atributo RelayCommand expõe a propriedade CanExecute, que pode ser usada para indicar uma propriedade ou método de destino a ser usado para avaliar se um comando pode ser executado:

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

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

Dessa forma, o CanGreetUser é invocado quando o botão é vinculado pela primeira vez à interface do usuário (por exemplo, a um botão) e, em seguida, é invocado novamente toda vez que o IRelayCommand.NotifyCanExecuteChanged é invocado no comando.

Por exemplo, é assim que um comando pode ser associado a uma propriedade para controlar seu 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}"/>

Neste exemplo, a propriedade SelectedUser gerada invocará o método GreetUserCommand.NotifyCanExecuteChanged() sempre que seu valor for alterado. A interface do usuário tem um controle Button associado a GreetUserCommand, o que significa que toda vez que seu evento CanExecuteChanged for gerado, ele chamará novamente o método CanExecute. Isso fará com que o método CanGreetUser encapsulado seja avaliado, o que retornará o novo estado do botão com base no fato de a instância User de entrada (que na interface do usuário está vinculada à propriedade SelectedUser) é null ou não. Isso significa que sempre que SelectedUser for alterado, GreetUserCommand ficará habilitado ou não com base em se essa propriedade tem um valor, que é o comportamento desejado nesse cenário.

Observação

O comando não saberá automaticamente quando o valor retornado do método CanExecute ou propriedade for alterado. Cabe ao desenvolvedor chamar IRelayCommand.NotifyCanExecuteChanged para invalidar o comando e solicitar que o método CanExecute vinculado seja avaliado novamente para então atualizar o estado visual do controle vinculado ao comando.

Manipulação de execuções simultâneas

Sempre que um comando for assíncrono, ele poderá ser configurado para decidir se permite ou não execuções simultâneas. Ao usar o atributo RelayCommand, isso poderá ser definido por meio da propriedade AllowConcurrentExecutions. O padrão é false, ou seja, até que uma execução esteja pendente, o comando sinalizará seu estado como sendo desabilitado. Se, em vez disso, estiver definido como true, qualquer número de invocações simultâneas poderá ser enfileirado.

Observe que, se um comando aceitar um token de cancelamento, um token também será cancelado se uma execução simultânea for solicitada. A principal diferença é que se forem permitidas execuções simultâneas, o comando permanecerá habilitado e iniciará uma nova execução solicitada sem esperar que a anterior seja realmente concluída.

Manipulação de exceções assíncronas

Há duas maneiras diferentes de os comandos de retransmissão assíncrona lidarem com as exceções:

  • Aguardar e retornar (padrão): quando o comando aguarda a conclusão de uma invocação, todas exceções serão retornadas naturalmente no mesmo contexto de sincronização. Isso geralmente significa que as exceções lançadas somente travariam o aplicativo, o que é um comportamento consistente com o dos comandos síncronos (onde as exceções lançadas também travarão o aplicativo).
  • Exceções de fluxo para o agendador de tarefas: se um comando estiver configurado para gerar exceções para o agendador de tarefas, as exceções produzidas não travarão o aplicativo, mas, em vez disso, ficarão disponíveis por meio do IAsyncRelayCommand.ExecutionTask exposto, além de serem propagadas para o TaskScheduler.UnobservedTaskException. Isso permite cenários mais avançados (como ter componentes de interface do usuário vinculados à tarefa e exibir resultados diferentes com base no resultado da operação), mas é mais complexo de usar corretamente.

O comportamento padrão é fazer com que os comandos aguardem e retornem as exceções. Isso pode ser configurado por meio da propriedade FlowExceptionsToTaskScheduler:

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

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

Nesse caso, o try/catch não é mais necessário, pois as exceções não falharão mais no aplicativo. Observe que isso também fará com que outras exceções não relacionadas não sejam retornadas automaticamente; portanto, você deve decidir cuidadosamente como abordar cada cenário individual e configurar o restante do código adequadamente.

Cancelar comandos para operações assíncronas

Uma última opção para comandos assíncronos é a capacidade de solicitar que um comando de cancelamento seja gerado. Este é um ICommand encapsulando um comando de retransmissão assíncrona que pode ser usado para solicitar o cancelamento de uma operação. Esse comando sinalizará automaticamente seu estado para refletir se pode ou não ser usado a qualquer momento. Por exemplo, se o comando vinculado não estiver em execução, ele relatará seu estado como também não sendo executável. Isso pode ser usado da seguinte maneira:

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

Isso fará com que uma propriedade DoWorkCancelCommand também seja gerada. Isso pode então ser vinculado a algum outro componente de interface do usuário para permitir que os usuários cancelem facilmente as operações assíncronas pendentes.

Adicionar atributos personalizados

Assim como acontece com as propriedades observáveis, o gerador RelayCommand também inclui suporte para atributos personalizados das propriedades geradas. Para aproveitar isso, você pode simplesmente usar o destino [property: ] em listas de atributos sobre métodos anotados, e o Kit de Ferramentas MVVM encaminhará esses atributos para as propriedades de comando geradas.

Por exemplo, considere um método como este:

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

Isso gerará uma propriedade GreetUserCommand, com o atributo [JsonIgnore] sobre ela. Você pode usar quantas listas de atributos direcionadas ao método desejar, e todas elas serão encaminhadas para as propriedades geradas.

Exemplos

  • Confira o aplicativo de exemplo (para várias estruturas de interface do usuário) para ver o Kit de Ferramentas MVVM em ação.
  • Você também pode encontrar mais exemplos nos testes de unidade.