Usar comandos em um viewmodel
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 CanExecuteChanged
o .
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 GiveBonusCommand
arquivo . 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.