WPF Commands

Scritto da  Pietro Libro il mercoledì 5 maggio 2010
Linguaggio: C#   •  Framework: 4.0   •  Livello: 200


Citando MSDN, "Il Commanding è un meccanismo per la gestione dell'input in Windows Presentation Foundation (WPF) che fornisce una gestione dell'input (da tastiera o mouse ad esempio) ad un livello semantico più alto rispetto alla periferica di Input". A prima lettura questa definizione potrebbe essere non del tutto chiara, ma la lettura dell'articolo dovrebbe renderla (si spera) comprensibile. Non ce ne rendiamo conto, ma utilizziamo i comandi tutti i giorni, non solo come sviluppatori di applicazioni WPF, ma anche quando utilizziamo i software installati sul nostro PC. Se ad esempio abbiamo un controllo TextBox e l'utente  esegue il comando "Cut" , il modo in cui questo viene invocato non è importante: l'utente può utilizzare Ctrl+X, selezionare il menu "Cut" o utilizzare un controllo Button, l'importante è che la risposta sia sempre la stessa: "Tagliare" il contenuto selezionato. L'importante è focalizzarsi su quello che l'utente vuole fare con l'applicazione piuttosto che chiederci come farlo. Qeusto concetto viene supportato da WPF attraverso il concetto di Command: un'azione che l'applicazione esegue alla richiesta di un utente. Durante l'articolo si farà riferimento ai termini Command e Comando, indicando se non specificato, lo stesso concetto.

I concetti fondamentali su cui si basa il Commands System di WPF, sono cinque:

  • Un Command Object, che identifica un particolare comando come "Copia" o "Incolla".
  • Input Binding, che associa un particolare input (Ctrl+C) con un Command.
  • Command Source, l'oggetto che ha invocato il comando, come un controllo Button o un Input Binding.
  • Command Target, l'elemento della UI che ha chiesto di eseguire il comando.
  • Command Binding, "colui" che  conosce come gestire un particolare comando.

Command Object

Un Command Object identifica un particolare comando, ma non conosce assolutamente come gestirlo, essendo questo, il lavoro svolto dal Command Binding. I Command Objects sono tipicamente implementati come classi che espongono proprietà statiche. Il .Net Framework, ci viene in aiuto fornendo un insieme di Commands standard, che più delle volte, non avremo necessità di creare ex novo. Ad esempio:

  • ApplicationCommands: Definisice comandi comuni a molte applicazioni, come "Copy", "Paste", "Undo", "Redo", "Print"
  • ComponentCommands: Definisce comandi per muoversi attraverso le informazioni, come "Scroll Up","Scroll Down", "Move To End"
  • EditingCommands: Definisce comandi per l'editing del testo come "Bold","Italic","Center" e "Justify"
  • MediaCommands: Operazioni di Media-Playing per la selezione della traccia, o controllo del volume
  • NavigationCommands: Comandi per la navigazione in stile browser internet: "Back", "Forward", "Refresh"

I Commands su citati, generalmente coprono buona parte delle funzionalità presenti in molte applicazioni, ma solitamente, le applicazioni custom hanno necessità di utilizzare funzioni application-specific, di conseguenza, sarà necessario svilupparne Commands custom. La struttura di una classe contenitore per i nostri comandi, sarà qualcosa di questo tipo:

public static class MieiCommands
{
private static RoutedUICommand MioCommand;
static MieiCommands()
{
InputGestureCollection commandInputs = new InputGestureCollection();
commandInputs.Add(new KeyGesture(Key.B, ModifierKeys.Control));
////Istanzia il RoutedIdCommand e associa il gestore degli Input.
MioCommand = new RoutedUICommand(
"Descrizione Mio Command", "NomeCommand",
typeof(MieiCommands), commandInputs);
}
}

E' necessario fare qualche precisazione: generalmente i Commands implementano l'interfaccia ICommand. I Commands che sono utilizzati da WPF, sono RoutedCommand, e una classe che deriva da quest'ultima è RoutedUICommand, che definisce un testo addizionale per l'interfaccia utente, che non è definito in ICommand. ICommand a sua volta definisce i metodi Execute() e CanExecute() invocati dal Target Object.

Nella classe (static) MieiCommands, definiamo i Commands (in questo caso uno) che vogliamo implementare: MioCommand ti tipo RoutedUICommand. In MieiCommands, andiamo ad instanziare un nuovo oggetto InputGestureCollection che utilizzeremo per gestire l'Input Bindings che associa la combinazione di tasti Ctrl+B per richiamare il comando "MioCommand". Il costruttore di RoutedUICommand, richiede come primo parametro la descrizione del comando e come secondo, il nome del comando stesso. Nel caso in cui sia necessario sviluppare applicazioni localizzate sarà necessario utilizzare un ResourceManager per ottenere una stringa per le impostazioni locali invece che scriverle direttamente nel codice.

Fino adesso abbiamo costruito solo l'identificatore del nostro comando, non abbiamo ancora scritto nessun codice per come gestirlo!. Vediamo come possiamo associare nel codice XAML il comando a livello di Window:

<Window.CommandBindings>
<CommandBinding Command="local:MieiCommands.MioCommand" />
</Window.CommandBindings>

E al livello di controllo utente (Button nel nostro caso):

<Button Content="Button" Command="local:MieiCommands.MioCommand"/>

Input Binding

L'Input Binding , associa un particolare input in entrata, come ad esempio uno shortcut, ad un comando. Nell'esempio precedente, abbiamo considerato un input da tastiera che abbiamo gestito tramite KeyGesture, ma è bene ricordare che esiste la classe MouseGesture che permette di gestire l'input proveniente dal mouse, ad esempio per reagire quando viene premuto il tasto sinistro del mouse.

Command Source

Il Command Source è l'oggetto che è stato utilizzato per invocare il comando, può essere un elemento dell'interfaccia utente come un Button, ma può anche essere un InputGesture (KeyGesture o MouseGesture). I Command Source implementano l'interfaccia ICommandSource.

public interface ICommandSource
{
ICommand Command { get; }
object CommandParameter { get; }
IInputElement CommandTarget { get; }
}

Se impostiamo la proprietà Command su un Command Object (ad esempio il controllo Button sopra citato) la sorgente invocherà il comando quando cliccato, o nel caso in cui sia gestito l'input tramite InputGesture quando l'utente immetterà un input adatto. Utilizzando la proprietà CommandParameter è possibile impostare un oggetto da passare come parametro. Quest'ultimo potrà essere recuperato in fase di esecuzione sfruttando la proprietà Parameter dell'istanza della classe ExecutedRoutedEventArgs che riceverà l'handler del gestore del comando. La proprietà CommandTarget può essere utilizzata per assicurarci che il Command specificato da un particolare CommandSource sia direttamente passato ad uno specifico target a prescindere quale controllo abbia il focus.

Command Bindings

Il discorso è quasi completo, dato che, un qualsiasi comando, per essere utile, deve trovare "da qualche parte" qualcuno che risponde quando invocato. Ci sono controlli, come ad esempio TextBox e RichTextBox che gestiscono automaticamente comandi come Cut, Copy e Paste, ma ad esempio per quelli specifici dell'applicazione, quelli creati ad hoc, è necessario scrivere del codice. Non resta quindi che collegare un comando con l'opportuno gestore evento. Potremmo farlo direttamente, ma il problema è che l'handler collegato verrebbe eseguito ogni qual volta, in qualsiasi punto dell'applicazione, viene richiamato il comando. E' anche necessario inibire uno o più comandi quando ci si trova in particolari contesti. Per risolvere questi problemi, ci viene incontro la classe CommandBinding che associa uno specifico comando ad un particolare handler. Questa classe offre gli eventi PreviewExecuted ed Executed sollevati dall'UI in modalità Tunnel e Bubble che permettono di gestire il codice, prima e dopo l'esecuzione dell'handler associato al comando. Altri eventi che possono essere utilizzati sono PreviewCanExecute e CanExecute (in Tunneling o Bubbling) per determinare se un particolare comando è abilitato o meno. Un utile articolo per comprendere la modalità Tunneling e Bubbling è il seguente: http://www.wpfmentor.com/2008/11/understand-bubbling-and-tunnelling-in-5.html .

Riprendendo l'esempio precedente, il codice del gestore associato al nostro comando potrebbe essere il seguente:

private void MioCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Esecuzione del codice associato al comando...", sender.ToString());
}

A livello di Window, modifichiamo la definizione del Command nel modo seguente:

<Window.CommandBindings>
<CommandBinding Command="local:MieiCommands.MioCommand" Executed="MioCommandHandler" />
</Window.CommandBindings>

Se eseguiamo la nostra applicazione e premiamo Ctrl+B, verrà visualizzato il messaggio:

WPF_Commanding_Fig_1

Se proviamo ad associare il Command ad un controllo Button (ad esempio) è necessario specificare l'handler per CanExecuted. Nell'esempio seguente, il comando verrà eseguito solo se il TextBox contenuto all'interno della stessa UI contiene una stringa:

<Button Command="local:MieiCommands.MioCommand"
CommandManager.Executed="MioCommandHandler"
CommandManager.CanExecute="MioCommand_CanExecute"

private void MioCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = !string.IsNullOrEmpty(textBox1.Text);
}

Per verificare il corretto funzionamento dell'applicazione, è necessario rimuovere l'associazione del commando a livello di Window.

Se utilizziamo spesso funzioni comuni nello sviluppo delle nostre applicazioni, una buona idea è sicuramente quella di creare una libreria di Commands, pronta per essere utilizzata ogniqualvolta ne abbiamo bisogno.

E' importante sottolineare, che l'uso dei Comands è un concetto fondamentale per una corretta applicazione del pattern MVVM per WPF.


Tags: WPF

 
x