L’altro giorno stavo chattando con Larent Bugnion riguardo nuove features da aggiungere nel suo progetto M-V-VM light toolkit ed entrambi eravamo concordi nella necessità di aggiungere un behavior che mappasse un generico evento verso un comando esposto dal ViewModel.
Questa necessità è ancor più sentita in Silverlight dove il meccanismo di commanding, ICommand a parte, non è presente.


Un esempio di possibile attached behavior è quello che segue:

   1: public static class EventCommand
   2:     {        
   3:         public static readonly DependencyProperty CommandProperty =
   4:              DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(EventCommand), new PropertyMetadata(null));
   5:         
   6:         public static ICommand GetCommand(DependencyObject d)
   7:         {
   8:             return (ICommand)d.GetValue(CommandProperty);
   9:         }
  10:         
  11:         public static void SetCommand(DependencyObject d, ICommand value)
  12:         {
  13:             d.SetValue(CommandProperty, value);
  14:         }
  15:         
  16:         public static readonly DependencyProperty RoutedEventProperty =
  17:              DependencyProperty.RegisterAttached("RoutedEvent", typeof(String),
  18:              typeof(EventCommand), new PropertyMetadata((String)String.Empty, new PropertyChangedCallback(OnRoutedEventNameChanged)));
  19:  
  20:         
  21:         public static String GetRoutedEvent(DependencyObject d)
  22:         {
  23:             return (String)d.GetValue(RoutedEventProperty);
  24:         }
  25:                 
  26:         public static void SetRoutedEvent(DependencyObject d, String value)
  27:         {
  28:             d.SetValue(RoutedEventProperty, value);
  29:         }
  30:  
  31:         
  32:         private static void OnRoutedEventNameChanged(DependencyObject d,
  33:              DependencyPropertyChangedEventArgs e)
  34:         {
  35:             String routedEvent = (String)e.NewValue;
  36:             if (d == null || String.IsNullOrEmpty(routedEvent)) return;
  37:  
  38:             EventHooker eventHooker = new EventHooker();
  39:             EventInfo eventInfo = d.GetType().GetEvent(routedEvent, BindingFlags.Public | BindingFlags.Instance);
  40:             if (eventInfo != null)
  41:             {
  42:                 eventInfo.RemoveEventHandler(d,
  43:                      eventHooker.GetNewEventHandlerToRunCommand(eventInfo));
  44:  
  45:                 eventInfo.AddEventHandler(d,
  46:                      eventHooker.GetNewEventHandlerToRunCommand(eventInfo));
  47:             }
  48:         }
  49:     }
  50:     
  51:     sealed class EventHooker
  52:     {
  53:         public Delegate GetNewEventHandlerToRunCommand(EventInfo eventInfo)
  54:         {
  55:                 return Delegate.CreateDelegate(eventInfo.EventHandlerType, this,
  56:                         GetType().GetMethod("OnEventRaised", BindingFlags.NonPublic | BindingFlags.Instance));
  57:         }
  58:  
  59:         private void OnEventRaised(object sender, EventArgs e)
  60:         {
  61:             ICommand command = (ICommand)(sender as DependencyObject). GetValue(EventCommand.CommandProperty);
  62:             if (command != null)    command.Execute(null);
  63:         }
  64:     }

Il quale può essere usato in questo modo:

   1: <ListBox Margin="10"
   2:                  Grid.Row="1"                 
   3:                  this:EventCommand.RoutedEvent="SelectionChanged"
   4:                  this:EventCommand.Command="{Binding ButtonCommand}"
   5:                  ItemsSource="{Binding Items}"
   6:                  ItemTemplate="{StaticResource DataTemplate1}" />
   7:  
   8:         <Button Content="{Binding ButtonContent}"                
   9:                 this:EventCommand.RoutedEvent="Click"
  10:                 this:EventCommand.Command="{Binding ButtonCommand}"                
  11:                 Grid.Row="2"
  12:                 Margin="10" />

Rimane da risolvere un caso altrettanto frequente in architetture basate su M-V-VM ovvero mappare più eventi dello stesso elemento verso command diversi e questo, specialmente in Silverlight, non è proprio banale, per una serie di ‘mancanze’ fondamentali rispetto a WPF.
Fortunatamente durnate la chiaccherata Laurent ipotizza che negli Expression Blend Samples ci sia già tutto quello che serve.
Scaricati, installati e ora,  anche in un progetto Silverlight per mappare un evento verso un Command non si deve far altro che selezionare un InvokeDataCommand  e draggarlo sul controllo, selezionare l’evento, Command di destinazione ed eventuale parametro.

image

image 
Ciò che rende questi behaviors ancora più interessanti è il fatto di poter associare più InvokeDataCommand allo stesso controllo risolvendo egregiamente la necessità iniziale

   1: <Button Content="{Binding ButtonContent}"
   2:                Grid.Row="2"
   3:                Margin="10" >
   4:            <i:Interaction.Triggers>
   5:                <i:EventTrigger EventName="Click">
   6:                    <si:InvokeDataCommand Command="{Binding ButtonCommand, Mode=OneWay}"/>
   7:                </i:EventTrigger>
   8:                <i:EventTrigger EventName="MouseLeave">
   9:                    <si:InvokeDataCommand Command="{Binding ButtonCommand, Mode=OneWay}"/>
  10:                </i:EventTrigger>
  11:            </i:Interaction.Triggers>            
  12:        </Button>
InvokeDataCommand è solo uno dei vari behaviors presenti, l’elenco lo trovate nella home page del progetto: http://expressionblend.codeplex.com/
 
Nota: La versione di InvokeDataCommand per WPF non funziona, Laurent ha postato una possibile soluzione che è in attesa di validazione da parte del gruppo che segue il progetto.