M-V-VM: Commanding
L’ultimo requisito che abbiamo, per questa parte, è la possibilità di cancellare un indirizzo dalla lista degli indirizzi della persona corrente.
Commanding
Prima di addentrarci nei tecnicismi dell’implementazione introduciamo una delle “novità”, alla faccia della novità… 6 anni sono passati, di Wpf, l’accoppiata ICommand/ICommandSource.
ICommand
Un command è una classe che rappresenta un’azione, concetto semplicissimo per chi arriva da Delphi in cui esistono proprio le Action. Per Wpf un command è una qualsiasi classe che implementa l’interfaccia ICommand che è caratterizzata da:
- Boolean CanExecute( Object ): l’infrastruttura di Wpf chiama CanExecute per sapere se in quel determinato momento il comando debba essere considerato “disponibile”;
- void Execute( Object ): Execute viene chiamato per effettivamente invocare il comando e permettergli di eseguire l’azione per cui è pensato, Execute verrà invocato se e solo se CanExecute ritorna true;
- EventHanlder CanExecuteChanged: Un comando può sollevare questo evento per notificare al motore di Wpf che il suo stato è cambiato ed è quindi il caso che Wpf venga a controllare nuovamente con una chiamata a CanExecute;
n.d.r.: CanExecute ed Execute prendono in ingresso un parametro di tipo object che possiamo usare sulla falsa riga di un command argument che potrebbe essere utile per pilotare dall’esterno parte della logica interna de command.ICommandSource
La controparte di un ICommand è ICommandSource. Una classe che implementa ICommandSource è qualcosa, tipicamente un controllo della UI, che è in grado di invocare un comando. ICommandSource è così definita:
- ICommand Command{ get; set; }: è la dependency property, quindi con pieno supporto per data binding, che ci consente di impostare quale sia il comando che vogliamo venga eseguito;
- Object CommandParameter{ get; set; }: è la dependency property che ci consente di impostare l’eventuale parametro da passare al comando;
- IInputElement CommandTarget{ get; set; }: Ha senso, e viene utilizzata, solo ed esclusivamente se il comando è un RoutedCommand, tipologia di comando che non ci interessa trattare;
Ogni controllo è libero di implementare a suo piacimento la logica e il comportamento da seguire a seguito della variazione di stato di un comando, come abbiamo detto, ad esempio, il bottone si disabilita.
DelegateCommand
Wpf mette a disposizione un largo set di command già pronti all’uso, questi sono tutti di tipo RoutedCommand che, in un mondo basato su Model View ViewModel, ha una serie di difetti: in primis quello di non avere nessuna possibilità di controllo sul processo di notifica della variazione di stato del comando. I limiti di RoutedCommand e di RoutedUICommand hanno portato alla nascita di svariate implementazioni dell’interfaccia ICommand, implementazioni che tipicamente cadono sotto il nome di DelegateCommand o RelayCommand.
Il tutto si “limita” ad un’implementazione più o meno stilisticamente bella che consente di generare un comando agganciando al volo dei delegati, tipicamente facendo largo uso di una sintassi basata sulle lambda, che verranno eseguiti sia per determnare lo stato del comando (CanExecute) sia per eseguire il comando (Execute); inoltre tutti espongono un meccamismo per poter triggherare la notifica della varizione di stato del comando stesso.
Anche Radical non è da meno e offre un’interessante implementazione.
AddressDeleteCommand
Adesso che sappiamo un po’ meglio la teoria non ci resta che metterla in pratica. La prima cosa che ci interessa fare è esporre la funzionalità dal nostro MainViewModel, semplicemente esponendo una proprietà di tipo ICommand:
contestualmente agganciando, via DataBinding, la nuova proprietà ad un nuovo elemento della UI:public ICommand DeleteAddressCommand { get; private set; }
l’implementazione del nostro comando è decisamente semplice anche se dietro le quinte succedono parecchie cose:<Button Content="Delete" Command="{Binding Path=DeleteAddressCommand}" Margin="0,0,317,0" />
Radical offre un entry point statico, e anche un costruttore volendo, per la creazione di un nuovo comando. La cosa comoda è il supporto per le fluent interface che vi permette di definire le caratteristiche del comando in maniera decisamente intuitiva:this.DeleteAddressCommand = DelegateCommand.Create() .OnCanExecute( o => this.SelectedAddress != null ) .OnExecute( o => { var add = this.SelectedAddress; this.SelectedAddress = null; this.Person.Addresses.Remove( add ); } ) .AddMonitor ( PropertyObserver.For( this ) .Observe( vm => vm.SelectedAddress ) );
- OnCanExecute si aspetta un Func
- OnExecute accetta invece come parametro una Action
La parte veramente interessante, di cui ho già parlato (sintassi a parte che è leggermente cambiata), è AddMonitor che vi permette di agganciare dei “trigger” al command, trigger che altro non fanno che automatizzare il processo di notifica, alla UI, della variazione di stato del comando stesso.
Fatto tutto ciò se mandiamo in esecuzione la nostra bellissima applicazione (bellissima è auto-consolatorio ) quello che otteniamo è questo:
Come tradizione allego i sorgenti: Mvvm.Application 1.3
Il prossimo requisito? essere in grado di creare una nuova “Person” con la sua simpatica lista di “Address”.
.m