WPF mette a disposizione una infrastruttura per la gestione dei comandi (cioè delle funzionalità che stanno dietro i controlli di una finestra applicativa) decisamente più evoluta rispetto a quella presente nelle Windows Forms in cui tale gestione è demandata ad un utilizzo oculato dei gestori di eventi.
In una applicazione ben progettata, il task applicativo che sta dietro ad un comando, non dovrebbe essere direttamente eseguito all’interno di un gestore di evento ma in maniera disgiunta da questo.
Il comando applicativo, quindi, dovrebbe essere astratto rispetto al gestore di evento che lo gestisce in modo che sia molto facile fornire agli utenti nuovi controlli che eseguono lo stesso task.
Il modello dei comandi di WPF è composto dai seguenti pilastri:
· Commands : un comando rappresenta un task applicativo e contiene le informazioni necessarie per conoscere quando tale comando può essere eseguito o meno. Un comando non contiene il task vero e proprio (che, di solito, è contenuto in una apposita classe);
· Command Bindings : il command binding lega il comando all’effettiva logica applicativa. Grazie al command binding un comando può essere utilizzato in differenti punti della nostra applicazione;
· Sources : sono i controlli che generano il comando (button, menu, etc., etc.);
· Targets : sono gli elementi per cui il comando viene eseguito. Ad esempio l’apertura della lista delle stampanti in un comando Print.
L’interfaccia ICommand (contenuta nel namespace System.Windows.Command) definisce il comportamento di un comando. Questa interfaccia definisce i seguenti metodi ed eventi:
- Public Interface ICommand
- ' Events
- Custom Event CanExecuteChanged As EventHandler
- ' Methods
- Function CanExecute(ByVal parameter As Object) As Boolean
- Sub Execute(ByVal parameter As Object)
- End Interface
Il metodo Execute() contiene il task da eseguire mentre il metodo CanExecute() viene richiamato dal framework quando lo stesso framework ha la necessità di conoscere se il comando può essere eseguito o meno. Infine, l’evento CanExecuteChanged() viene invocato quando lo stato del comando cambia.
Possiamo definire un nostro comando implementando l’interfaccia ICommand ma possiamo utilizzare la classe RoutedCommand che ci mette a disposizione una serie di metodi accessori (come, ad esempio, la gestione del bubbling del comando che permette al comando di navigare la gerarchia dei controlli WPF fino a trovare l’opportuno gestore) che semplificano la realizzazione del comando.
Altra classe fondamentale per la gestione dei comandi è la RoutedUICommand che deriva dalla RoutedCommand ma che fornisce la possibilità di definire un testo del comando (con eventuale localizzazione del testo).
Il framework WPF ci mette a disposizione una serie di comandi predefiniti (che troviamo nella maggior parte delle applicazioni Windows) e che possono essere utilizzati senza dover usare direttamente la classe RoutedUICommand. Per accedere a questi comandi possiamo utilizzare le classi ApplicationCommands (per i normali comandi quali Copy, Paste, Open, New, etc., etc.), NavigationCommands (per i comandi di navigazione), EditingCommand (per i comandi di edit dei documenti), ComponentCommands e MediaCommands.
Vediamo ora, come eseguire un comando.
Per prima cosa abbiamo bisogno è una sorgente, cioè un oggetto che implementa l’interfaccia ICommandSource.
L’interfaccia ICommandSource prevede i seguenti membri:
· Command : oggetto ICommand che l’ICommandSource deve eseguire;
· CommandParameter : permette di inviare un qualsiasi oggetto al comando;
· CommandTarget : oggetto da cui il comando è effettivamente eseguito.
Il terzo pilastro per la gestione dei comandi in WPF è il CommandBinding. Il command binding permette di legare il comando al controllo o ai controlli che lo eseguono.
Cominciamo a vedere un esempio di interfaccia XAML con l’utilizzo di un comando:
- <Window x:Class="MainWindow"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="MainWindow" Height="350" Width="525">
- <Window.CommandBindings>
- <CommandBinding Command="Print"
- Executed="PrintCommand_Executed"
- CanExecute="PrintCommand_CanExecute"></CommandBinding>
- </Window.CommandBindings>
- <Grid>
- <Grid.RowDefinitions>
- <RowDefinition Height="20" />
- <RowDefinition Height="*" />
- <RowDefinition Height="Auto" />
- </Grid.RowDefinitions>
- <Menu Grid.Row="0">
- <MenuItem Command="Print" VerticalAlignment="Center"></MenuItem >
- </Menu>
- <StackPanel Grid.Row="2">
- <Button Command="Print"
- Content="{Binding Path=Command.Name, RelativeSource={RelativeSource Self}}"></Button>
- </StackPanel>
- </Grid>
- </Window>
Se eseguiamo l’applicazione, osserviamo che i due controlli sono disabilitati:
Questo perché il framework, quando deve capire qual è lo stato del comando esegue il metodo PrintCommand_CanExecuted che, in questo caso, restituisce il valore di default (che è quello dello stato non abilitato).
Per attivare il comando ci basta scrivere il seguente codice:
- Private Sub PrintCommand_CanExecute(ByVal sender As System.Object,
- ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs)
- e.CanExecute = True
- End Sub
- Private Sub PrintCommand_Executed(ByVal sender As System.Object,
- ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)
- MessageBox.Show("Print")
- End Sub
Il primo dei due gestori di evento comunica al framework che il comando è sempre attivo (e.CanExecute=true) mentre il secondo gestore esegue il comando.
Il risultato è il seguente:
In un’applicazione reale, lo stato del comando dipenderà dallo stato dell’applicazione.
I vantaggi sono notevoli: non dobbiamo implementare il codice che aggiorna lo stato dei controlli in base allo stato del comando, aggiungere nuovi controlli che eseguono lo stesso comando è una banalità e, infine, il task applicativo non è inserito nel gestore dell’evento dei controlli disaccoppiando il codice dall’interfaccia.
Inoltre, come possiamo osservare dal precedente XAML, anche il testo che appare nei controlli è standardizzato e un’eventuale localizzazione coinvolgerebbe tutti i controlli dello stesso comando in un colpo solo.
Il binding dei comandi si può eseguire anche da codice come mostrato di seguito:
- Dim binding = New CommandBinding(ApplicationCommands.Close)
- With binding
- AddHandler .CanExecute, AddressOf Close_CanExecute
- AddHandler .Executed, AddressOf Close_Executed
- End With
E’, possibile, inoltre eseguire un comando direttamente da codice utilizzando l’istruzione:
- ApplicationCommands.Print.Execute(Nothing, targetElement)
Esistono , infine, alcuni controlli che hanno già dei specifici comandi predefiniti. Un esempio di questo è il TextBox. Quando inseriamo il controllo TextBox in una pagina, abbiamo la possibilità di inserire dei comandi tipo Edit, Paste e Undo la cui attivazione è gestita direttamente dal framework in base al fatto che un utente selezioni del testo, abbia del testo nella clipboard o annulli l’ultima operazione. Ad esempio:
- <Window x:Class="Toolbar"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Toolbar" Height="300" Width="300">
- <Grid>
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto"></RowDefinition>
- <RowDefinition Height="*"></RowDefinition>
- </Grid.RowDefinitions>
- <ToolBar>
- <Button Command="Copy">Copy</Button>
- <Button Command="Paste">Paste</Button>
- <Button Command="Undo">Undo</Button>
- </ToolBar>
- <StackPanel Grid.Row="1" Margin="10">
- <TextBox>
- <TextBox.Background>
- <RadialGradientBrush>
- <GradientStop Color="cyan" Offset="1"></GradientStop>
- <GradientStop Color="white" Offset="0"></GradientStop>
- </RadialGradientBrush>
- </TextBox.Background>
- </TextBox>
- </StackPanel>
- </Grid>
- </Window>
Per finire questo tutorial vediamo come creare rapidamente un comando custom.
Supponiamo di voler implementare il comando di Export. Definiamo la seguente classe:
- Public Class ExportCommand
- Private Shared _Export As RoutedUICommand
- Shared Sub New()
- Dim inputs As New InputGestureCollection()
- inputs.Add(New KeyGesture(Key.E, ModifierKeys.Control, "Ctrl+E"))
- _Export = New RoutedUICommand("Export", "Export", GetType(ExportCommand), inputs)
- End Sub
- Public Shared ReadOnly Property Export As RoutedUICommand
- Get
- Return _Export
- End Get
- End Property
- End Class
Per utilizzare questo comando è necessario importare il namespace della nostra applicazione e impostare l’apposito binding come mostrato nel seguente XAML:
- <Window x:Class="Export"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:l="clr-namespace:WpfApplication"
- Title="Export" Height="300" Width="300">
- <Window.CommandBindings>
- <CommandBinding Command="l:ExportCommand.Export"
- Executed="CommandBinding_Executed"
- CanExecute="CommandBinding_CanExecute"></CommandBinding>
- </Window.CommandBindings>
- <Grid>
- <Button Command="l:ExportCommand.Export">Export</Button>
- </Grid>
- </Window>
Commenti