WPF Commands
Scritto da
Pietro Libro
il
mercoledì 5 maggio 2010
Linguaggio:
•
Framework:
•
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:

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