MVVM e Windows Phone – I command (5° parte)

Print Content | More

Ci siamo lasciati nel post precedente con una domanda: “Come faccio a gestire le interazioni dell’utente con la mia applicazione?”. L’approccio a cui siamo abituati, utilizzando il code behind, è tramite gli event handler: i vari controlli in grado di gestire l’interazione dell’utente (come i Button) espongono una serie di eventi (come Tap o Click), che possiamo sottoscrivere nel code behind per gestirli. Ad esempio, per gestire l’evento Click di un pulsante nello XAML dovremmo dichiarare il nome del metodo da invocare come nell’esempio:

<Button Content="Click me" Click="Button_Click" />

In questo modo Visual Studio genererà per noi un metodo nel code behind per gestire l’evento:

private void Button_Click(object sender, RoutedEventArgs e)
{
    // do something
}

Questo approccio però non può funzionare nel pattern MVVM, perchè il ViewModel non ha un accesso diretto alla View e quindi non può referenziarsi direttamente al controllo Button e gestire l’evento. Per questo motivo lo XAML mette a disposizione i Command, che rappresentano i comandi che vengono eseguiti quando l’utente interagisce con il controllo (in quelli che ereditano dalla classe ButtonBase si traduce nella pressione dello stesso).

La differenza rispetto ad un event handler è che il command è un oggetto, che implementa l’interfaccia ICommand. Essendo un oggetto, è possibile collegarlo alla proprietà Command del controllo tramite binding e, di conseguenza, possiamo definirlo all’interno del ViewModel.

I vari toolkit dedicati a MVVM contengono solitamente un’implementazione dell’interfaccia ICommand da utilizzare: MVVM Light non fa eccezione e offre la classe RelayCommand. Vediamo come utilizzarla per gestire il click di un pulsante. Ecco l’esempio di un RelayCommand definito nel ViewModel:

private RelayCommand _buttonClick;

public RelayCommand ButtonClick
{
    get
    {
        return _buttonClick
            ?? (_buttonClick = new RelayCommand(
                                  () =>
                                      {
                                          MessageBox.Show("Button clicked!");
                                      }));
    }
}

Nel momento in cui viene creata l’istanza della classe del RelayCommand dobbiamo passare come parametro la funzione da eseguire allo scatenarsi dell’evento sotto forma di Action: nell’esempio utilizziamo il meccanismo degli anonymous delegate, ma nulla vieta di dichiarla separatamente.

A questo punto il collegamento con la view è molto semplice e segue lo stesso meccanismo che abbiamo visto in precedenza per le proprietà semplici: dobbiamo associare tramite binding la proprietà ButtonClick alla proprietà Command del controllo Button, come nell’esempio.

<Button Content="Click me" Command="{Binding Path=ButtonClick}" />

Il gioco è fatto: alla pressione del pulsante la funzione dichiarata nel RelayCommand sarà eseguita e verrà mostrato a video il messaggio.

Command con parametri

Può capitare la necessità di passare un parametro di input al comando come, ad esempio, il valore della proprietà di un altro controllo. Per questo scopo esiste una variante della classe RelayCommand, definita come RelayCommand<T>, dove T è il tipo di oggetto che viene passato come parametro. La definizione del RelayCommand diventa quindi la seguente:

private RelayCommand<string> _buttonClickedWithParameter;

public RelayCommand<string> ButtonClickedWithParameter
{
    get
    {
        return _buttonClickedWithParameter
            ?? (_buttonClickedWithParameter = new RelayCommand<string>(
                                  p =>
                                      {
                                          MessageBox.Show(p);
                                      }));
    }
}

L’unica differenza rispetto a prima è che la funzione da eseguire non è più una Action, ma una Action<T>, dove T è il parametro che è stato passato all’oggetto. Nell’esempio, il comando ButtonClickedWithParameter accetta un parametro di tipo string, che viene reso disponibile alla funzione nella dichiarazione dell’anonymous method (l’oggetto p).

Per passare il parametro al comando tramite lo XAML si utilizza la proprietà CommandParameter del controllo, in combinazione con la proprietà Command per associare il comando, come nell’esempio:

<Button Content="Click me with parameter" Command="{Binding Path=ButtonClickedWithParameter}" CommandParameter="Clicked" />

Nell’esempio il parametro che viene passato al comando è il testo Clicked, che viene poi recuperato dalla funzione e mostrato a video tramite una MessageBox.

I command e la funzione CanExecute

L’oggetto RelayCommand supporta un secondo parametro opzionale, oltre alla funzione da eseguire quando il comando viene eseguito: si tratta della funzione CanExecute, che permette di determinare se il comando deve essere abilitato o meno. Si tratta di una funzionalità molto interessante, perchè, anche visivamente, il controllo si comporterà di conseguenza in base al valore di questa funzione: se il comando è disabilitato, anche il pulsante apparirà disattivato e quindi l’utente non potrà cliccarci sopra.

L’utilizzo è piuttosto semplice: dobbiamo passare come parametro una funzione che restituisca un valore booleano. Quando questo è a true, il pulsante è abilitato e il comando può essere eseguito; quando è a false, invece, il pulsante è disabilitato.

L’oggetto RelayCommand espone poi il metodo RaiseCanExecuteChanged, da invocare ogni qualvolta abbiamo fatto una modifica che comporta una variazione nella funzione e, di conseguenza, un possibile cambiamento nello stato del pulsante.

Vediamo come mettere insieme tutti questi concetti in un esempio concreto: realizziamo un pulsante che viene attivato solo quando un controllo di tipo CheckBox è stato abilitato.

Nel ViewModel andiamo ad inserire una nuova proprietà, che sarà messa in binding con la proprietà IsChecked del controllo CheckBox.

private bool _isChecked;

public bool IsChecked
{
    get { return _isChecked; }
    set
    {
        if (_isChecked != value)
        {
            _isChecked = value;
            RaisePropertyChanged(() => IsChecked);
        }
    }
}

Dopodichè andiamo a creare un RelayCommand che viene inizializzato con due parametri: il primo è la funzione da eseguire, il secondo quella da valutare per determinare se il comando deve essere attivo o meno.

private RelayCommand _buttonClickedCanExecute;

public RelayCommand ButtonClickedCanExecute
{
    get
    {
        return _buttonClickedCanExecute
               ?? (_buttonClickedCanExecute = new RelayCommand(
                                                  () =>
                                                      {
                                                          MessageBox.Show("Clicked");
                                                      },
                                                  () =>
                                                      {
                                                          return IsChecked;
                                                      }
                                                  ));
    }
}

 

La funzione CanExecute deve ritornare un valore booleano, facciamo ritornare perciò direttamente il valore della proprietà IsChecked. Ora possiamo collegare i controlli nello XAML con le proprietà del view model che abbiamo definito:

<Button Content="Click me with can execute" Command="{Binding Path=ButtonClickedCanExecute}" />
<CheckBox Content="Enable button" IsChecked="{Binding Path=IsChecked, Mode=TwoWay}" />

Come vedete, allo XAML la funzione CanExecute è completamente trasparente: il comando ButtonClickedCanExecute è semplicemente in binding con la proprietà Command del controllo Button.

Manca un ultimo passaggio: dobbiamo comunicare al RelayCommand quando la funzione CanExecute deve essere nuovamente valutata (nel nostro esempio, deve essere verificato se la proprietà IsChecked è cambiata). Per farla andiamo a chiamare il metodo RaiseCanExecuteChanged ogni qualvolta il valore della proprietà IsChecked cambia. Di conseguenza, ecco come diventerà la sua dichiarazione nel ViewModel:

private bool _isChecked;

public bool IsChecked
{
    get { return _isChecked; }
    set
    {
        if (_isChecked != value)
        {
            _isChecked = value;
            RaisePropertyChanged(() => IsChecked);
            ButtonClickedCanExecute.RaiseCanExecuteChanged();
        }
    }
}

Lanciando il progetto di test che accompagna questo post potete vedere il codice all’opera:

screenshot1screenshot2

In conclusione

Con questo possiamo considerare concluse le “basi” necessarie per comprendere e applicare il pattern MVVM. Più avanti vedremo qualche scenario avanzato per approfondire la conoscenza acquisita: ad esempio, come gestire la navigazione tra pagine o come usare la dependency injection per risolvere le dipendenze dei view model.


Windows Phone , MVVM

2 comments

Related Post

  1. #1 da Tiziano Monday August 2012 alle 10:02

    Ottima spiegazione :)

    Ci sono dei controlli (tipo l'ApplicationBar e il ContextMenu della Telerik) in cui non riesco ad usare i Command.

    Per esempio nel pulsante per il refresh dell'appbar devo utilizzare l'evento del click per poter richiamare il metodo apposito del ViewModel:

    private void RefreshButton_Click(object sender, EventArgs e)
    {
    _vm.Refresh();
    }

    Questo implica che il ViewModel sia dichiarato nella stato valorizzato nel costruttore della vista:
    public MainView()
    {
    _vm = this.DataContext as MainViewModel;
    }

    Di conseguenza, per abitutine, mi comporto in questo modo, anche quando posso usare i RelayCommand..

  2. #2 da Matteo Pagani Monday August 2012 alle 10:08

    Ciao Tiziano,
    la tua è una soluzione valida, ma preferisco usare un altro approccio per i casi in cui non si riescono ad usare i command.

    Per quanto riguarda l'application bar, la sostituisco con un controllo custom che eredita dalla ApplicationBar originale ma supporta il binding, come ho descritto in questo post:

    http://www.qmatteoq.com/blog/post/bindableapplicationbar-unalternativa-alla-applicationbar-di-windows-phone

    Nel prossimo post invece spiegherò il meccanismo messo a disposizione da MVVM Light per usare il binding anche con gli eventi che non sono esposti da un command.

    Stay tuned :-)


(will not be published)
(es: http://www.mysite.com)


  1. #1 da http://enzocontini.wordpress.com/2014/10/13/universal-app/

    Windows Phone 8.1 &#8211; Some useful links for developing Windows &#8220;Universal Apps&#8221; | Enzo Contini Blog