Alessandro Del Sole's Blog

{ A programming space about Microsoft® .NET® }
posts - 1908, comments - 2047, trackbacks - 352

My Links

News

Your host

This is me! Questo spazio è dedicato a Microsoft® .NET®, di cui sono molto appassionato :-)

Cookie e Privacy

Disabilita cookie ShinyStat

Microsoft MVP

My MVP Profile

Microsoft Certified Professional

Microsoft Specialist

Xamarin Certified Mobile Developer

Il mio libro su VB 2015!

Pre-ordina il mio libro su VB 2015 Pre-ordina il mio libro "Visual Basic 2015 Unleashed". Clicca sulla copertina per informazioni!

Il mio libro su WPF 4.5.1!

Clicca sulla copertina per informazioni! E' uscito il mio libro "Programmare con WPF 4.5.1". Clicca sulla copertina per informazioni!

These postings are provided 'AS IS' for entertainment purposes only with absolutely no warranty expressed or implied and confer no rights.
If you're not an Italian user, please visit my English blog

Le vostre visite

I'm a VB!

Guarda la mia intervista a Seattle

Follow me on Twitter!

Altri spazi

GitHub
I miei progetti open-source su GitHub

Article Categories

Archives

Post Categories

Image Galleries

Privacy Policy

WPF: Introduzione al pattern Model-View-ViewModel con Visual Basic 2010 - parte 2

Nel precedente post abbiamo fatto un’introduzione discorsiva alle caratteristiche del pattern Model-View-ViewModel in Windows Presentation Foundation e abbiamo detto come la serie di post voglia avere un taglio introduttivo e dal punto di vista dello sviluppatore Visual Basic. Abbiamo implementato un semplice modello per i dati, un ViewModel minimale per il caricamento e l’invio di dati alla View che, a sua volta, si occupa semplicemente di mostrare i dati caricati.

 

In questo post vogliamo fare un passo successivo, piuttosto complesso peraltro, ossia l’aggiunta di comandi da associare a dei pulsanti. Se avete già avuto modo di lavorare col c.d. “commanding pattern”, molti concetti vi risulteranno familiari anche se cambia un po’ la logica delle cose. Farò continui riferimenti al post precedente, per cui potrebbe esservi utile lavorare sul progetto relativo al post precedente, oppure scaricare quello bell’e pronto del presente post dall’area Download di VB T&T.

 

Risultato da raggiungere

L’applicazione che vogliamo realizzare oggi è sostanzialmente un’estensione della precedente e si presenterà come in figura:

Ci sarà un bel po’ di lavoro da fare stavolta, ma lo facciamo adesso per poi ritrovarcelo pronto nei post successivi.

 

Il Model

Il Model rimane invariato rispetto al post precedente. Quindi, classi Customer e Customers, file di dati chiamato Customers.Xml.

 

I “commands” in WPF

WPF introdusse, fin dalla prima versione, il concetto di Command. Un command è un oggetto che implementa l’interfaccia ICommand ed è un vero e proprio comando che esegue l’azione desiderata. La peculiarità di un command è che questo espone tre membri:

 

1.    Metodo Execute, che rappresenta l’azione da eseguire quando il comando viene invocato

2.    Metodo CanExecute, che ottiene lo stato del comando ossia se questo può essere eseguito o meno, a seconda che la condizione booleana specificata sia vera o falsa

3.    Evento CanExecuteChanged, che si verifica quando il valore della condizione booleana che rende eseguibile (o meno) il comando cambia

 

In questo blog abbiamo parlato già in altre occasioni dei command in WPF, qui il discorso va visto in modo diverso. Il vantaggio di utilizzare degli oggetti di tipo ICommand è che questi consentono di essere svincolati completamente dall’interfaccia. In uno scenario non M-V-VM, generalmente a un pulsante si associa un gestore di evento Click che poi esegue un’azione. Tramite un command, invece, si ricorre al data-binding per collegare il controllo al comando. Il comando può essere esposto da uno strato diverso da quello di presentazione come, guarda caso, un ViewModel. Tra l’altro, una delle caratteristiche peculiari dei command è che quando il valore di CanExecute è False, il controllo utente associato al command stesso è inattivo mentre diventa attivo quando il valore passa a True. Vi rimando alla pagina del commanding pattern su MSDN per capire come il runtime di WPF esegua il refresh dello stato dei controlli in binding.

 

Ritrasmissione della logica di esecuzione dei comandi

Al fine di avere un’infrastruttura riutilizzabile e di favorire ulteriormente la separazione tra oggetti, nel Model-View-ViewModel si usa un approccio un po’ diverso nell’implementazione dei comandi. Si implementa infatti una classe che smista le richieste di esecuzione e verifica dello stato di eseguibilità dei comandi, che poi rimanda al ViewModel l’esecuzione effettiva dei comandi stessi.

Qui c’è uno dei primi problemi: ci sono almeno due scuole di pensiero e quindi due tipi diverse di implementazione della classe di smistamento. Personalmente userò quella semplificata che, oltre ad essere più comoda dal mio punto di vista, è anche più adatta per capire alcuni concetti.

 

E’ quindi necessario implementare una classe che smisti le richieste. Tale classe si chiama per convenzione RelayCommand e può avere una versione non generica e una generica. Quella generica la vedremo successivamente, per ora vediamo l’implementazione della non generica:

 

Public Class RelayCommand

    Implements ICommand

 

    Private ReadOnly _execute As Action(Of Object)

    Private ReadOnly _canExecute As Predicate(Of Object)

 

    Public Sub New(ByVal execute As Action(Of Object))

        Me.New(execute, Nothing)

    End Sub

 

    Public Sub New(ByVal execute As Action(Of Object), ByVal canExecute As Predicate(Of Object))

        If execute Is Nothing Then

            Throw New ArgumentNullException("execute")

        End If

 

        _execute = execute

        _canExecute = canExecute

    End Sub

 

    <DebuggerStepThrough()> _

    Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute

        Return If(_canExecute Is Nothing, True, _canExecute(parameter))

    End Function

 

    Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged

        AddHandler(ByVal value As EventHandler)

            AddHandler CommandManager.RequerySuggested, value

        End AddHandler

        RemoveHandler(ByVal value As EventHandler)

            RemoveHandler CommandManager.RequerySuggested, value

        End RemoveHandler

        RaiseEvent(ByVal sender As System.Object, ByVal e As System.EventArgs)

        End RaiseEvent

    End Event

 

    Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute

        _execute(parameter)

    End Sub

 

End Class

 

La classe implementa ICommand e questo è necessario per smistare richieste provenienti da classi dello stesso tipo. Il costruttore, in particolare il secondo overload, riceve come argomento un delegate che rappresenta l’azione da eseguire e un altro delegate che rappresenta lo stato di eseguibilità del comando. Mentre i metodi Execute e CanExecute sono di semplice comprensione, l’evento CanExecuteChanged merita più attenzione. Sia AddHandler che RemoveHandler puntano a un evento chiamato CommandManager.RequerySuggested che si verifica quando il runtime di WPF rileva situazioni che possono modificare lo stato, vero o falso, di eseguibilità del comando. Passiamo ora a definire i comandi veri e propri.

 

Definizione dei comandi

Apriamo il file CustomerViewModel.vb definito la volta scorsa. Lasciando invariato tutto quanto già scritto, dobbiamo definire 3 comandi da collegare successivamente alla View. Ci occorre un comando per il salvataggio dei dati, uno per l’aggiunta di nuovi dati e uno per la rimozione. Quindi, in primis, aggiungiamo i seguenti campi che “sorreggeranno” i comandi:

 

    Private _cmdAddCommand As ICommand

    Private _cmdRemoveCommand As ICommand

    Private _cmdSaveCommand As ICommand

 

L’aggiunta di campi lascia presupporre che i comandi verranno esposti come proprietà. Questo perchè, come sapete, in WPF le proprietà hanno supporto specifico per il data-binding. In effetti i tre comandi vengono esposti dal ViewModel nel modo seguente:

 

    Public ReadOnly Property AddCommand() As ICommand

        Get

            If _cmdAddCommand Is Nothing Then

                _cmdAddCommand = New RelayCommand(AddressOf AddExecute, AddressOf CanAddExecute)

            End If

            Return _cmdAddCommand

        End Get

    End Property

 

    Public ReadOnly Property RemoveCommand() As ICommand

        Get

            If _cmdRemoveCommand Is Nothing Then

                _cmdRemoveCommand = New RelayCommand(AddressOf Remove, AddressOf CanRemove)

            End If

            Return _cmdRemoveCommand

        End Get

    End Property

 

    Public ReadOnly Property SaveCommand() As ICommand

        Get

            If _cmdSaveCommand Is Nothing Then

                _cmdSaveCommand = New RelayCommand(AddressOf Save, AddressOf CanSave)

            End If

            Return _cmdSaveCommand

        End Get

    End Property

 

Le proprietà sono di sola lettura e sono di tipo ICommand. Qualora il relativo campo non contenga già un’istanza, ne viene creata una della classe RelayCommand passando i riferimenti di due metodi, uno che esegue il comando e uno che restituisce lo stato di eseguibilità del comando. Tra le varie cose, la RelayCommand si occuperà anche di richiamare il command manager di WPF per verificare se il comando può essere eseguito o meno. Cominciamo con il primo comando, che viene gestito dai seguenti metodi:

 

    'Restituisce sempre True perchè si può sempre

    'aggiungere un nuovo elemento

    Private Function CanAddExecute(ByVal param As Object) As Boolean

        Return True

    End Function

 

    Private Sub AddExecute(ByVal param As Object)

        Dim cust As New Customer

        Me.Customers.Add(cust)

    End Sub

 

Vedete come il primo metodo restituisca la condizione booleana che determina se il comando può essere eseguito o meno. In questo caso è sempre True, perchè voglio garantire sempre la possibilità di aggiungere nuovi elementi. Il metodo che esegue il comando, invece, istanzia un nuovo customer e lo aggiunge alla collezione. Il successivo comando rimuove un elemento. Notate come la verifica della condizione si faccia sul valore non nullo della proprietà Selection, che sarà in binding nella View:

 

    Private Function CanRemove(ByVal param As Object) As Boolean

        Return Me.Selection IsNot Nothing

    End Function

 

    Private Sub Remove(ByVal param As Object)

        Me.Customers.Remove(Me.Selection)

    End Sub

 

Da ultimo, il comando per il salvataggio dei dati. Dovendo scrivere un documento Xml, facciamo uso degli XML literals e delle espressioni incorporate:

 

    Private Function CanSave(ByVal param As Object) As Boolean

        Return True

    End Function

 

    Private Sub Save(ByVal param As Object)

        Dim doc = <?xml version="1.0"?>

                  <Customers>

                      <%= From cust In Me.Customers

                          Select <Customer CompanyName=<%= cust.CompanyName %>

                                     CustomerID=<%= cust.CustomerID %>

                                     Address=<%= cust.Address %>

                                     Representative=<%= cust.Representative %>/>

                      %>

                  </Customers>

 

        doc.Save(AppDomain.CurrentDomain.BaseDirectory + "Data\Customers.xml")

    End Sub

 

In tutti i casi l’argomento param dei metodi non viene utilizzato ma le firme previste dall’interfaccia lo richiedono comunque. Alla fine di tutto questo codice, qual è la conclusione? Semplice: siamo in grado di eseguire dei comandi sui dati senza neanche aver toccato l’interfaccia grafica, separando nettamente le due cose. Grande beneficio dei command in WPF.

 

Interfaccia, pulsanti e data-binding

Una volta implementati i comandi, dobbiamo collegarli ai controlli dell’interfaccia. Apriamo quindi la nostra View, ossia la finestra principale, e appena dopo il codice della DataGrid aggiungiamo il seguente:

 

        <Button Content="Add"  Command="{Binding AddCommand}"

                Height="30" HorizontalAlignment="Left" Margin="373,62,0,0" Name="Button1" VerticalAlignment="Top" Width="116" />

        <Button Content="Save"  Command="{Binding SaveCommand}"

                Height="30" HorizontalAlignment="Left" Margin="373,102,0,0" Name="SaveButton" VerticalAlignment="Top" Width="116" />

        <Button Content="Remove"  Command="{Binding RemoveCommand}"

                Height="30" HorizontalAlignment="Left" Margin="373,142,0,0" Name="RemoveButton" VerticalAlignment="Top" Width="116" />

 

 

Notate come la proprietà Command di ciascun pulsante sia in binding con la proprietà di tipo ICommand appropriata, esposta proprio dal ViewModel. Non serve altro codice, se non il binding. Se ora avviate l’applicazione noterete che:

 

1.    Il pulsante Remove è disabilitato finchè non selezionate un elemento nella DataGrid e questo è possibile grazie al motore di detection dello stato di eseguibilità del comando, sia per mezzo di ICommand che del runtime di WPF

2.    Facendo clic sui pulsanti vengono eseguiti i rispettivi comandi, però da parte del ViewModel e non da altro come invece eravamo abituati a fare

 

Ah, dimenticavo! Non ho toccato il code-behind della Window della scorsa volta... :-) quindi significa che ho lasciato inalterata l’assegnazione dell’istanza del ViewModel al DataContext della Window per far sì che tutto venga popolato, comandi compresi, tramite data-binding.

 

Download del codice

Il codice sorgente del progetto completo con le modifiche fatte in questa seconda parte è disponibile a questo indirizzo dell’area Download di VB T&T. As usual, se avete commenti, feedback, migliorie da suggerire... ben venga.

 

Alessandro

Print | posted on sabato 12 giugno 2010 15:47 | Filed Under [ Visual Basic Windows Presentation Foundation Visual Studio 2010 ]

Powered by:
Powered By Subtext Powered By ASP.NET