Usar comandos em um viewmodel

Concluído

Você viu como obter dados de seus viewmodels para sua interface do usuário e como você pode usar a associação bidirecional para obter dados de volta para seus viewmodels.

Usar associações bidirecionais como essa é a maneira preferida de reagir a alterações da interface do usuário sempre que os dados forem alterados. Muitas coisas que lidaríamos como eventos podem ser tratadas usando ligações bidirecionais e MVVM. Outros exemplos são coisas como Switch.IsToggled e Slider.Value, que podem ser refletidas em nosso viewmodel como um valor booleano ou inteiro, sem ter que usar eventos.

Mas há algumas coisas, como uma Button ou MenuItem ativação, que não estão diretamente ligadas à alteração de dados. Essas interações ainda exigem manipulação semelhante a um evento. Como esses componentes da interface do usuário geralmente invocam algum tipo de lógica com os dados, queremos essa lógica no viewmodel. Mas não queremos tratá-los como Clicked e Selected eventos no code-behind, se possível. Queremos o máximo possível estar no viewmodel, dessa forma é testável.

Usar o padrão de comando

Muitos dos controles .NET MAUI que têm esse tipo de interação suportam a vinculação a uma propriedade que expõe uma ICommand interface. Esta propriedade é provavelmente chamada Command. O botão é um exemplo:

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

O controle sabe quando invocar o comando. Por exemplo, um botão invoca o comando quando é pressionado. O comando neste exemplo está vinculado à GiveBonusCommand propriedade do viewmodel. O tipo de propriedade tem que implementar a ICommand interface. O código seria mais ou menos assim:

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

A ICommand interface tem um Execute método que é chamado quando o botão é clicado. Dessa forma, o substitui diretamente Button.Click o ICommand.Execute código de manipulação de eventos.

A interface completa ICommand tem mais dois métodos: CanExecute e CanExecuteChanged que são usados para determinar se um controle deve aparecer ativado ou desativado.

Um botão, por exemplo, pode aparecer esmaecido se CanExecute retornar false.

Veja como é a ICommand interface em C#:

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

Usar a classe Command

Esse padrão de comando permite manter uma separação clara do comportamento da interface do usuário da implementação da interface do usuário. Mas isso pode complicar seu código se você precisar criar uma classe separada para implementar cada manipulador de eventos.

Em vez de criar várias classes personalizadas que implementam a interface, é comum usar Command ou Command<T>. Essas classes implementam ICommand , mas expõem seu comportamento como propriedades em seu viewmodel que você pode definir. Isso permite que você implemente a GiveBonusCommand propriedade descrita anteriormente inteiramente em nossa classe viewmodel:

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.
    }
}

Neste código, o Execute comportamento é fornecido pelo método GiveBonusExecute. E CanExecute é fornecido pela GiveBonusCanExecute. Os delegados a esses métodos são passados para o Command construtor. Neste exemplo, não há implementação para CanExecuteChangedo .

Simplifique com o MVVM Toolkit

A biblioteca MVVM Toolkit contém implementações conhecidas ICommand como RelayCommand e AsyncRelayCommand. Ele também fornece geradores de código-fonte para simplificar ainda mais esse código. No exemplo a seguir, o GiveBonusCommand será gerado definindo o método para chamar para executar e para chamar para ver se ele pode executar. O [RelayCommand] atributo é usado no GiveBonus método e gerará o GiveBonusCommandarquivo . Além disso, definindo a CanExecute propriedade no atributo para o nome do método que queremos conectar ao CanExecute método do ICommand, ele gerará o código para configurar isso para nós.

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;
    }
}

O MVVM Toolkit também lida com async métodos, que são comuns na programação .NET.

Comandos com parâmetros

A ICommand interface aceita um object parâmetro para os CanExecute métodos e Execute . O .NET MAUI implementa essa interface sem qualquer tipo de verificação através da Command classe. Os delegados anexados ao comando devem fazer sua própria verificação de tipo para garantir que o parâmetro correto seja passado. O .NET MAUI também fornece a Command<T> implementação onde você define o tipo de parâmetro esperado. Ao criar um comando que aceita um único tipo de parâmetro, use Command<T>.

Os controles .NET MAUI que implementam o padrão de comando fornecem a CommandParameter propriedade. Ao definir essa propriedade, você pode passar um parâmetro para o comando quando ele é invocado com Execute, ou quando ele verifica o status do CanExecute método.

Neste exemplo, o valor da cadeia de caracteres 25 é enviado para o comando:

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

O comando precisaria interpretar e converter esse parâmetro string. Há muitas maneiras de fornecer um parâmetro fortemente tipado.

  • Em vez de usar a sintaxe 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>
    
  • Vincule o CommandParameter a uma instância do tipo correto.

  • Se o CommandParameter estiver vinculado ao tipo incorreto, aplique um conversor para converter o valor para o tipo correto.

Verifique o seu conhecimento

1.

Qual é o objetivo da ICommand interface?

2.

Como as Command classes ou Command<T> podem simplificar o uso da ICommand interface?

3.

Qual é a função da CommandParameter propriedade nos controles .NET MAUI?