ASP.Net  »  Articoli  »  Silverlight 

Applicare il pattern MVVM con Silverlight: i comandi

di: Andrea Boschin     19 Maggio 2010

Comandi custom

Un'applicazione in cui sia possibile gestire esclusivamente il Click di pulsanti ha sicuramente vita breve. Ciononostante, la attuale versione di Prism mette a disposizione solamente questa implementazione, perciò se come spesso capita dobbiamo gestire qualcosa di più complesso ecco che è necessario scrivere un po' di codice aggiuntivo. Il codice che vedremo è abbastanza complesso, tuttavia una volta che si è preparato lo snippet di codice è sufficiente copiarlo con poche modifiche e i nuovi comandi nasceranno in pochi istanti.

Supponiamo perciò di voler aggiungere una nuova feature al nostro esempio. Mediante una ComboBox è possibile selezionare una categoria di ricerca per filtrare con più efficacia i prodotti. La selezione di questa ComboBox tuttavia deve applicare il filtro immediatamente, senza attendere una ulteriore pressione del pulsante Search. Questo comportamento normalmente lo si ottiene gestendo l'evento SelectionChanged ma in un'ottica MVVM dovremo scrivere un apposito comando.

La prima cosa da fare è creare una classe estendendo CommandBehaviorBase<T>. Il tipo generico indica il controllo a cui si deve applicare il behavior. Nel nostro caso potrebbe essere riferito alla ComboBox ma dato che l'evento SelectionChanged prende origine dal controllo primitivo Selector possiamo rendere il comando più ampiamente utilizzabile se lo riferiamo a quest'ultimo.

public class SelectionChangedCommandBehavior : CommandBehaviorBase<Selector>
{
  public SelectionChangedCommandBehavior(Selector targetObject) : base(targetObject)
  {
    targetObject.SelectionChanged += (s, e) => base.ExecuteCommand();
  }
}

Lo scopo di questa classe è evidente. Essa è incaricata di agganciare il giusto evento e di notificare l'esecuzione del comando. Versioni più complesse potrebbero ad esempio valorizzare il CommandParameter con un valore preso dal controllo di riferimento. L'unico limite è dato dalla fantasia di chi scrive il codice. Aggiungendo questo metodo, ad esempio, possiamo fare in modo che il controllo sia nascosto quando il comando non può essere eseguito, anziché disabilitato:

protected override void UpdateEnabledState()
{
  if(this.Command.CanExecute(this.CommandParameter))
    this.TargetObject.Visibility = Visibility.Visible;
  else
    this.TargetObject.Visibility = Visibility.Collapsed;
}

A questo punto siamo pronti per scrivere tre attached properties. Le prime due sono le ovvie Command e CommandParameter che hanno lo scopo di valorizzare questi parametri per mezzo di XAML. La terza proprietà, che sarà invisibile a livello di markup, ha il solo scopo di memorizzare l'istanza di behavior creata per questo elemento. Creiamo le proprietà:

public static class SelectionChanged
{
  public static readonly DependencyProperty SelectionChangedBehaviorProperty = 
             DependencyProperty.RegisterAttached("SelectionChangedBehaviorProperty", 
                                                 typeof(SelectionChangedCommandBehavior),
                                                 typeof(SelectionChangedCommandBehavior),
                                                 null);
                                                 
  #region CommandProperty
  
  public static readonly DependencyProperty CommandProperty = 
             DependencyProperty.RegisterAttached("Command", typeof(ICommand), 
                                                 typeof(SelectionChanged),
                                                 new PropertyMetadata(CommandProperty_Changed));
                                                 
  public static ICommand GetCommand(DependencyObject obj)
  {
    return (ICommand)obj.GetValue(CommandProperty);
  }
  
  public static void SetCommand(DependencyObject obj, ICommand value)
  {
    obj.SetValue(CommandProperty, value);
  }
  
  private static void CommandProperty_Changed(DependencyObject dependencyObject, 
                                              DependencyPropertyChangedEventArgs e)
  {
    Selector targetObject = dependencyObject as Selector;
    
    if (targetObject != null)
      GetOrCreateBehavior(targetObject).Command = e.NewValue as ICommand;
  }
  
  #endregion
  
  #region CommandParameterProperty
  
  public static readonly DependencyProperty CommandParameterProperty =  
             DependencyProperty.RegisterAttached("CommandParameter", typeof(object),
                                                 typeof(SelectionChanged),
                                                 new PropertyMetadata(CommandParameterProperty_Changed));

  public static ICommand GetCommandParameter(DependencyObject obj)
  {
    return (ICommand)obj.GetValue(CommandParameterProperty);
  }
  
  public static void SetCommandParameter(DependencyObject obj, ICommand value)
  {
    obj.SetValue(CommandParameterProperty, value);
  }
  
  private static void CommandParameterProperty_Changed(DependencyObject dependencyObject, 
               DependencyPropertyChangedEventArgs e)
  {
    Selector targetObject = dependencyObject as Selector;
    
    if (targetObject != null)
      GetOrCreateBehavior(targetObject).CommandParameter = e.NewValue;
  }
  
  #endregion
  
  private static SelectionChangedCommandBehavior GetOrCreateBehavior(Selector targetObject)
  {
    SelectionChangedCommandBehavior behavior =  
                   targetObject.GetValue(SelectionChangedBehaviorProperty) as 
                   SelectionChangedCommandBehavior;
    
    if (behavior == null)
    {
      behavior = new SelectionChangedCommandBehavior(targetObject);
      targetObject.SetValue(SelectionChangedBehaviorProperty, behavior);
    }
    
    return behavior;
  }
}

Il metodo GetOrCreateBehavior, riportato verso la fine della classe, verrò chiamato ogni volta le proprietà Command e CommandParameter sono valorizzate. In questo modo la prima volta il metodo crea una istanza del behavior e di conseguenza aggancia gli eventi del controllo.

Le volte successive l'istanza del behavior viene recuperata dalla apposita attached property dove è stato memorizzato precedentemente. In questo modo, quanto l'evento SelectionChanged viene sollevato, il relativo comando viene eseguito. Ora non resta che inserire il comando nel markup e scrivere il codice che lo gestisca:

<ComboBox Grid.Row="1" Grid.ColumnSpan="3" Margin="3,0,3,3" 
          ItemsSource="{Binding Categories}"
          code:SelectionChanged.Command="{Binding CategoryChangedCommand}"
          SelectedItem="{Binding SelectedCategory, Mode=TwoWay}" 
          DisplayMemberPath="Name" />

Il comando apparirà a tutti gli effetti come quello di Click dei pulsanti, anche se ovviamente nel namespace che rappresenta il nostro codice. Lascio al codice allegato a questo articolo la parte di implementazione del ViewModel che a questo punto dovrebbe essere abbastanza chiara.

Come dicevo in precedenza il codice da scrivere per ogni comando è molto e soprattutto ripetitivo. Per questo motivo è un ottimo candidato per uno snippet di VisualStudio. Nel link riportato in [1] potrete trovare lo snippet già confezionato, pronto da essere importato in Visual Studio.

Riferimenti

Guide ASP.Net

Guida Windows Azure Code Snippets

Le migliori pratiche per far girare le applicazioni "in the cloud",...

Guida ASP.NET MVC Best Practices

Un workflow dettagliato e ricco di suggerimenti pratici per...

Guida ASP.NET Starter Kit

Un modo semplice per imparare ad utilizzare le tecnologie Microsoft...

Altre guide

Newsletter @Microsoft Dev

Ogni giovedì, direttamente nella tua e-mail: articoli, guide, tutorial e script ASP, ASP.Net, SQL server e IIS.

Iscriviti alla newsletter

Altre newsletter

Corsi in aula

Corso Progettazione database

11 Maggio 2012 a Milano
Disponibilità: 6 Posti

Amministratore di Reti Windows Server 2008

11 Giugno 2012 a Milano
Disponibilità: 5 Posti

Nessun corso previsto