#121 - Utilizzare il KeyBinding di WPF con il pattern M-V-VM

Il pattern Model-View-ViewModel è un interessante nuovo modo di organizzare e realizzare applicazioni in Windows Presentation Foundation e il suo punto di forza è dato dalla facilità con cui calza le caratteristiche di WPF senza costringere lo sviluppatore a costruire o utilizzare un framework di supporto.

Vi sono però delle eccezioni a cui bisogna porre rimedio; per esempio ogni UIElement dispone di una collezione InputBindings nella quale si possono inserire uno o più KeyBinding per associare un ICommand ad una precisa gesture della tastiera.
Purtroppo la proprietà Command non è una DependencyProperty e non è possibile effettuare quindi il binding sul sottostante ViewModel associato alla View corrente, a differenza di come si può invece fare con pulsanti, caselle di selezione, ecc.

Una possibile soluzione a questo problema consiste nel creare una classe che implementi ICommand e da istanziare all'interno della View come risorsa che faccia poi da tramite tra l'ICommand vero e il KeyBinding. In questo modo si può associare la risorsa tramite StaticResource al KeyBinding, mentre si può effettuare il Binding su tale risorsa legandola al ViewModel.
Ecco un esempio di utilizzo:

<Window x:Class="WpfApplication2.Window1" 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  xmlns:local="clr-namespace:MyWpfApplication"> 
  <Window.Resources> 
    <local:CommandReference Command="{Binding SaveCommand}" 
                x:Key="saveCommand" /> 
  </Window.Resources> 
  <Grid> 
    <Grid.InputBindings> 
      <!-- Shortcut Ctrl-S per salvare --> 
      <KeyBinding Command="{StaticResource saveCommand}" 
            Modifiers="Ctrl" 
            Key="S" /> 
    </Grid.InputBindings> 
    <!-- Pulsante per salvare --> 
    <Button Command="{Binding SaveCommand}">Salva</Button> 
  </Grid> 
</Window>

La creazione della classe CommandReference è piuttosto semplice. Consiste in un classe DependencyObject che implementa l'interfaccia ICommand e con una proprietà Command la quale mappa i metodi Execute e CanExecute su quelli effettivi:

public class CommandReference : Freezable, ICommand 
{ 
 
  public static readonly DependencyProperty CommandProperty = 
    DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandReference), new PropertyMetadata(new PropertyChangedCallback(OnCommandChanged))); 
 
  // Comando sottostante da usare 
  public ICommand Command 
  { 
    get { return (ICommand)GetValue(CommandProperty); } 
    set { SetValue(CommandProperty, value); } 
  } 
 
  public bool CanExecute(object parameter) 
  { 
    // Redirige la richiesta sul commando sottostante 
    if (Command != null) 
      return Command.CanExecute(parameter); 
    return false; 
  } 
 
  public void Execute(object parameter) 
  { 
    Command.Execute(parameter); 
  } 
 
  public event EventHandler CanExecuteChanged; 
 
  private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
  { 
    CommandReference commandReference = d as CommandReference; 
    ICommand oldCommand = e.OldValue as ICommand; 
    ICommand newCommand = e.NewValue as ICommand; 
 
    // Mi aggancio per notificare quando il comando è eseguibile o meno 
    if (oldCommand != null) 
    { 
      oldCommand.CanExecuteChanged -= commandReference.CanExecuteChanged; 
    } 
    if (newCommand != null) 
    { 
      newCommand.CanExecuteChanged += commandReference.CanExecuteChanged; 
    } 
  } 
 
  protected override Freezable CreateInstanceCore() 
  { 
    throw new NotImplementedException(); 
  } 
 
}

IL CONTENUTO
SCRIPT VIA E-MAIL

Iscriviti alle nostre newsletter unoscript@lgiorno e Xcript per ricevere gli script via e-mail.

MEDIA
IN EVIDENZA
MISC