Un applicazione WPF/Silverlight basata su Model-View-ViewModel è il risultato di un insieme di dettagli che fanno la differenza, ad esempio una delle cose che sto iniziando ad apprezzare e grazie al quale è possibile fare delle cose veramente carine con poco sforzo è il VisualStateManager (che in WPF 4.0 diventa parte integrante della piattaforma)
Prendiamo un caso molto semplice: Cambiare il colore di un rettangolo (ma la cosa potrebbe essere complessa a piacere) alla pressione di un tasto.
Usando Blend, definiamo due stati Normal e Fail e in ognuno di essi cambiamo il colore del rettangolo (Rosso=Fail, Verde=Normal) giocando eventualmente con transizioni, easing e tutto ciò che il VisualStateManager ci permette di fare.
A questo punto associamo alla nostra pagina un ViewModel che esporrà un comando ChangeStateCommand da associare ad un pulsante che aggiungeremo alla nostra pagina.
public class MyViewModel:ViewModelBase
{
private string stateName;
private bool isFailState;
public MyViewModel ()
{
this.ChangeStateCommand = new RelayCommand(this.OnChangeState);
}
public RelayCommand ChangeStateCommand { get; private set; }
public string StateName
{
get {return this.stateName;}
set
{
if (value != this.stateName)
{
this.stateName = value;
this.RaisePropertyChanged("StateName");
}
}
}
private void OnChangeState()
{
if (!this.isFailState)
this.StateName = "Fail";
else
this.StateName = "Normal";
this.isFailState = !this.isFailState;
}
}
A questo punto ci manca un dettaglio fondamentale: Come facciamo, a far cambiare di stato il VisualStateManager quando lo stato del ViewModel cambia? ovvero nel nostro esempio quando premiamo il pulsante…
Ci sono varie possibilità ma quella più comoda è sicuramente quella di ricorrere ad un Blend Behavior, ad esempio il GotoStateAction che permette di selezionare gli stati di un VisualStateManager
Trasciniamo due oggetti GotoStateAction sulla griglia che contiene il rettangolo e il button e impostiamo i relativi stati da selezionare:
A questo punto rimane un problema evidente, come faccio a comandare il behavior in base allo stato del mio viewmodel? se date un occhiata alla figura a fianco noterete che, by default, il GotoStateAction permette di mappare lo stato del VisualStateManager verso un evento, ma questo non è quello che vogliamo.
Come risolviamo il problema?: Creando un custom trigger bindato alla proprietà StateName del nostro ViewModel, trigger che potremo poi riutilizzare anche con altri behaviors nel caso ci faccia comodo.
Ecco il codice del Blend trigger:
public class StatePropertyTrigger : TriggerBase<DependencyObject>
{
public static readonly DependencyProperty BoundPropertyProperty =
DependencyProperty.Register("BoundProperty", typeof(string), typeof(StatePropertyTrigger),
new PropertyMetadata(new PropertyChangedCallback(OnBoundPropertyChanged)));
public string BoundProperty
{
get { return (string)GetValue(BoundPropertyProperty); }
set { SetValue(BoundPropertyProperty, value); }
}
public string Value { get; set; }
private static void OnBoundPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
StatePropertyTrigger target = (StatePropertyTrigger)d;
if (e.NewValue.ToString() == target.Value)
target.InvokeActions(null);
}
}
Il trigger ha due proprietà:
- BoundProperty: E’ la proprietà del ViewModel che lo alimenta.
- Value: Il valore da confrontare.
Quando il valore di BoundProperty è uguale a Value, l’azione associata viene invocata, nel nostro caso specifico l’azione è la GotoStateAction che seleziona lo stato del VisualStateManager.
Una volta compilato il progetto, in Blend è sufficiente selezionare “New” alla voce TriggerType e selezionare il nostro custom trigger StatePropertyTrigger.
Ora configuriamo le proprietà del nostro trigger, collegandolo alla proprietà StateName e impostando il rispettivo valore da confrontare, il tutto ripetuto per entrambi i GotoStateAction draggati in precedenza.
Fatto questo, la pressione del tasto, invocherà il Command esposto dal ViewModel che di conseguenza cambiera alternativamente il valore della proprietà StateName provocando l’intervento del trigger e la conseguente selezione dello stato.
Può a prima vista sembrare complesso, ma tutto quello che abbiamo scritto è completamente riutilizzabile in altre applicazioni e sopratutto va ad arricchire la library dei propri behaviors che, vi assicuro, fanno la differenza quando si realizzano interface utente “evolute”.