Alessandro Del Sole's Blog

{ A programming space about Microsoft® .NET® }
posts - 1907, 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 1

Con questo post desidero iniziare una serie di trattazioni, seppur introduttiva, relativa all’ormai famoso pattern Model-View-ViewModel da un punto di vista dello sviluppatore Visual Basic 2010, anche in considerazione di due cose:

  1. c’è sicuramente molto materiale, soprattutto in inglese e soprattutto di livello già avanzato. Ma ci vuole anche un punto per cominciare!
  2. a parte il grande webcast di Corrado, un articolo di Cristian e gli sforzi di Mauro, c’è ben poco a livello introduttivo in italiano e questo è il problema principale per chi vuole iniziare M-V-VM
  3. su M-V-VM con Visual Basic non c’è praticamente nulla :-)

Premesso che M-V-VM si applica sia a WPF che Silverlight, con alcune differenze tra le 2 tecnologie, in questi miei post mi riferirò a WPF 4. Farò altre considerazioni, alcune anche di carattere personale, nel corso della discussione.

Cos’è

Se sviluppate con Windows Presentation Foundation e Silverlight, molto probabilmente avete sentito parlare del pattern Model-View-ViewModel. Si tratta di un pattern la cui finalità è quella di fornire uno strato di separazione, il più possibile elevato, tra dati e strato di presentazione, di modo che all’interno di quest’ultimo ci sia esclusivamente codice relativo alla gestione della user interface, ma non della gestione dei dati. Questo è possibile grazie al potente motore di data-binding di WPF che consente a uno strato intermedio (ViewModel) che si pone tra dati (Model) e interfaccia (View) di eseguire le operazioni richieste attraverso binding di oggetti e tecniche di commanding.

M-V-VM è un pattern, quindi un insieme di linee guida che perseguono un obiettivo. Il che significa che non è “la” regola assoluta né che bisogna utilizzarlo sempre e comunque. Anzi, ci sono scenari in cui M-V-VM non è il massimo.

Benefici

M-V-VM è particolarmente utile per i seguenti motivi:

  1. completa separazione tra dati e interfaccia grafica
  2. completa separazione tra i ruoli di grafico e sviluppatore (uno degli scopi primari che ha portato alla nascita di XAML)
  3. testabilità: come vedremo in seguito, eseguire unit test nei confronti di un ViewModel ha decisamente senso al contrario della loro esecuzione in contesti di UI
  4. Model e ViewModel non hanno necessità di cambiare se cambia la UI
  5. Indipendentemente dal tipo di Model (oggetti business, Entity Data Model ecc.), il ViewModel mantiene la stessa logica
  6. migliore “Blendability”, ossia la possibilità di lavorare sul progetto con Expression Blend

M-V-VM è anche un pattern tecnicamente complesso. Per esempio, un semplice click su un pulsante che apre una nuova dialog ha una logica ben diversa e complessa rispetto a quanto siamo abituati a fare. Al di fuori dai punti sopra elencati, è necessario fare un’analisi puntuale delle proprie necessità prima di ricorrervi. Esistono degli interessanti toolkit che facilitano la creazione di applicazioni basate su M-V-VM, ma purtroppo supportano in via esclusiva Visual C#. Poco male, scriveremo qualche riga di codice in più ma capiremo meglio i concetti e impareremo da soli a riutilizzare alcuni componenti.

Considerazioni personali

Premetto che non sono un guru di Model-View-ViewModel, affatto. Prendo la scusa di questo blog per condividere il percorso che ho fatto e che sto tuttora facendo nell’approfondimento della tematica in questione. Mi farebbe anzi piacere che i più esperti lascino i propri commenti. Quello che posso dire è che durante gli studi di M-V-VM la cosa che balza agli occhi è che non c’è una via unica di applicarlo. Ci sono molte tecniche, molte scelte, molteplici implementazioni e molti modi di scrivere i propri oggetti. Detto questo, in questo e nei prossimi post seguirò una linea che è dettata da quella che secondo me possiamo definire come “in media stat virtus” :-) e che può essere riadattata a più contesti. Ciò premesso, è bene poi che ognuno di voi interessato alla materia utilizzi un motore di ricerca e vada ad approfondire autonomamente. E ora passiamo alle cose serie.

Come la vedo io

Questo è il primo di una serie di post. In questo verrà creata una semplicissima applicazione che carica dei dati da un documento XML e li mostra in una DataGrid. Poi la sequenza sarà:

  1. implementazione del “commanding” per aggiungere comandi e pulsanti
  2. implementazione di funzionalità di navigazione tra dati (Next, Previous ecc.)
  3. implementazione di validazione dei dati attraverso l’interfaccia IDataErrorInfo
  4. ripetizione dei punti 1, 2 e 3 nei confronti di un modello basato su ADO.NET Entity Framework invece che su oggetti custom
  5. utilizzo del M-V-VM con relazioni master-details verso Entity Framework

Per cominciare, però, ci vuole una cosa molto semplice. Intanto la necessità è creare un progetto WPF con Visual Basic 2010. Una volta fatto questo, è tempo di parlare di dati.

Il Model

Il Model rappresenta essenzialmente i nostri dati. Può essere di vario tipo, ad esempio una classe che rappresenti un oggetto business, un modello a oggetti basato su Entity Framework o su LINQ to SQL, una classe POCO (plain-old-CLR-objects). Ipotizziamo di avere una classe Customer, che rappresenti un nostro cliente, e che sia definita in questo modo:

Public Class Customer

    Public Property CompanyName As String

    Public Property CustomerID As Integer

    Public Property Address As String

    Public Property Representative As String

 

End Class

Ipotizziamo poi che tale classe serva a rappresentare dei dati provenienti da un file XML, molto semplificato, come questo:

<?xml version="1.0" encoding="utf-8" ?>

<Customers>

  <Customer CompanyName="Del Sole Spa" CustomerID="1" Address="Cremona" Representative="Alessandro Del Sole" />

  <Customer CompanyName="RM Consulenza" CustomerID="2" Address="Varese" Representative="Renato Marzaro" />

  <Customer CompanyName="Catucci Snc" CustomerID="3" Address="Milano" Representative="Antonio Catucci" />

</Customers>

Infine, implementiamo una collezione chiamata Customers che contenga tutti gli oggetti Customer caricati. Tale classe eredita da ObservableCollection(Of Customer) e definisce un metodo condiviso che si occupa di caricare i dati:

Imports System.Collections.ObjectModel

 

'Implementa di suo INotifyPropertyChanged

Public Class Customers

    Inherits ObservableCollection(Of Customer)

 

    Public Shared Function LoadCustomers() As Customers

 

        Dim customerCollection As New Customers

 

        Dim doc = XDocument.Load("Data\Customers.xml")

        Dim query = From cust In doc...<Customer>

                  Select New Customer With {.Address = cust.@Address, .CompanyName = cust.@CompanyName, .CustomerID = CInt(cust.@CustomerID), .Representative = cust.@Representative}

 

        For Each cust In query

            customerCollection.Add(cust)

        Next

        Return customerCollection

    End Function

End Class

L’importanza di utilizzare la ObservableCollection è che questa, come noto, implementa l’interfaccia INotifyPropertyChanged e quindi è in grado di inviare una notifica ogni qual volta il suo contenuto si modifica.

Il ViewModel

Nella maggior parte dei tutorial che ho letto, dopo il Model si parla della View. Non è un approccio che mi piace, quindi ora passo al ViewModel :-) Il suo compito è essenzialmente quello di eseguire operazioni sul Model e inviare notifiche relative alle operazioni eseguite. Il ViewModel invia notifiche, ma non sa chi le riceverà. Affinchè un ViewModel sia in grado di inviare modifiche, la classe che lo definisce deve implementare l’interfaccia INotifyPropertyChanged. Poiché in un progetto potremmo avere decine di ViewModel, una buona idea è quella di costruire una gerarchia di classi riutilizzabili.

Ad esempio, potrei avere un ViewModel che lavori sugli ordini di un’azienda e un’altro ViewModel che lavori sulle anagrafiche dei clienti, ma entrambi avranno delle caratteristiche in comune per cui si è soliti ricorrere all’implementazione di una classe base che poi può essere ereditata da altri ViewModel. Nella demo di questo post, che è molto essenziale, ci sarà un solo ViewModel ma l’approccio serve per capire alcuni passaggi. Definiamo quindi una classe base chiamata per convenzione ViewModelBase, tecnica adottata nella maggior parte dei casi:

Imports System.ComponentModel

 

Public Class ViewModelBase

    Implements INotifyPropertyChanged

 

    Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

 

    Protected Sub OnPropertyChanged(ByVal strPropertyName As String)

 

        If Me.PropertyChangedEvent IsNot Nothing Then

            RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(strPropertyName))

        End If

 

    End Sub

End Class

L’implementazione di tale classe in questo modo è minimale, ma in seguito vedremo alcuni approfondimenti che ci consentiranno di validare i nomi delle proprietà. Ora ci occorre un ViewModel che esegua le operazioni sul nostro Model e che invii le notifiche alla/e View collegata/e. Tale classe si chiamerà CustomerViewModel ed eredita da ViewModelBase. Il codice è il seguente:

Public Class CustomerViewModel

    Inherits ViewModelBase

 

    Private _objCustomer As Customer

    Private _customers As Customers

    Dim _selectedCustomer As Customer

 

    Public Property Selection() As Customer

        Get

            Return _selectedCustomer

        End Get

        Set(ByVal value As Customer)

            If value Is _selectedCustomer Then

                Return

            End If

 

            _selectedCustomer = value

            MyBase.OnPropertyChanged("Selection")

        End Set

    End Property

 

    Public Property Customers As Customers

        Get

            Return _customers

        End Get

        Set(ByVal value As Customers)

            Me._customers = value

            OnPropertyChanged("Customers")

        End Set

    End Property

 

    Public Property Customer() As Customer

        Get

            Return _objCustomer

        End Get

        Set(ByVal Value As Customer)

            _objCustomer = Value

            MyBase.OnPropertyChanged("Customer")

        End Set

    End Property

 

    Public Property Address() As String

        Get

            Return _objCustomer.Address

        End Get

        Set(ByVal Value As String)

            _objCustomer.Address = Value

            MyBase.OnPropertyChanged("Address")

        End Set

    End Property

 

    Public Property CompanyName() As String

        Get

            Return _objCustomer.CompanyName

        End Get

        Set(ByVal Value As String)

            _objCustomer.CompanyName = Value

            MyBase.OnPropertyChanged("CompanyName")

        End Set

    End Property

 

    Public Property CustomerID() As Int32

        Get

            Return _objCustomer.CustomerID

        End Get

        Set(ByVal Value As Int32)

            _objCustomer.CustomerID = Value

            MyBase.OnPropertyChanged("CustomerID")

        End Set

    End Property

 

    Public Property Representative() As String

        Get

            Return _objCustomer.Representative

        End Get

        Set(ByVal Value As String)

            _objCustomer.Representative = Value

            MyBase.OnPropertyChanged("Representative")

        End Set

    End Property

 

    Public Sub New()

        Me._customers = Customers.LoadCustomers

    End Sub

 

    Public Sub New(ByVal customerCollection As Customers)

        Me._customers = customerCollection

    End Sub

End Class

 

 

Ci sono alcune cose da notare:

  1. la classe espone una proprietà chiamata Selection e che rappresenta il customer corrente, utile in fase di data-binding con la View
  2. la classe espone una proprietà Customers che redirige alla View il contenuto della collezione di customer
  3. la classe espone tante proprietà quante sono quelle del Model, inviando in modo esplicito una notifica invocando il metodo OnPropertyChanged della classe base
  4. il costruttore della classe si occupa di caricare i dati e popolare le proprietà, di modo che queste possano essere data-bound

Il concetto di fondo è questo: il ViewModel si occupa di caricare i dati e di esporli. In post successivi vedremo anche come si occupi di modificarli. Non è l’interfaccia che fa il lavoro, è il ViewModel. Che, come detto, non sa a chi invia le notifiche e quindi è pienamente svincolato dall’interfaccia stessa. Ora bisogna dare una destinazione a quanto esposto dal ViewModel, ossia una View.

La View

Con il termine View intendiamo lo strato di presentazione nell’ambito di M-V-VM e generalmente è rappresentato da un oggetto Window, quindi una finestra. Tale oggetto espone, come sapete, una proprietà DataContext. Grazie a tale proprietà, tutti i controlli presenti nell’interfaccia andranno a popolarsi leggendo i propri dati collegati partendo proprio dal DataContext. E poiché i dati sono esposti dal ViewModel, questo andrà a costituire il valore del DataContext stesso.

Se andiamo nello XAML della nostra Window principale, possiamo ipotizzare di voler visualizzare in una DataGrid i nostri dati (per questa volta uso la DataGrid solo per semplicità espositiva). Lo XAML è il seguente:

<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">

    <Grid>

        <DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Path=Customers}" SelectedItem="{Binding Path=Selection, Mode=TwoWay}"

                  Name="DataGrid1" >

        </DataGrid>

    </Grid>

</Window>

 

In sostanza, la proprietà ItemsSource è popolata col contenuto della proprietà Customers del DataContext della Window e lo stesso vale per la proprietà SelectedItem, che è popolata col valore della proprietà Selection del DataContext. Ma come abbiamo detto, il valore del DataContext è proprio il nostro ViewModel. Quindi il costruttore della Window (o se preferite farlo lato XAML, va bene lo stesso) si limiterà ad istanziare il ViewModel e ad assegnarlo al DataContext:

    Private Sub MainWindow_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        Dim custViewModel As New CustomerViewModel()

        Me.DataContext = custViewModel

    End Sub

Come vedete questo è l’unico codice contenuto nella Window. Tutto il lavoro effettivo è fatto nel ViewModel, raggiungendo quindi lo scopo della separazione completa tra strati. Ora potete avviare l'applicazione per vedere i dati caricati all'interno della DataGrid. Quindi è successo che l’interfaccia è semplicemente in binding, ma chi svolge il lavoro vero e proprio è solamente il ViewModel.

Download del codice e fine della prima parte

In questa prima parte abbiamo messo un po’ di carne al fuoco e forse iniziato a capire alcuni concetti fondamentali. L’applicazione di esempio è ovviamente banale, ma nel prossimo post avremo modo di dedicarci all’implementazione di comandi che renderanno più completo il nostro lavoro. Nel frattempo potete scaricare il codice sorgente relativo all’esempio proposto, da questo indirizzo dell’area Download di VB T&T.

Alessandro

Print | posted on giovedì 10 giugno 2010 14:31 | Filed Under [ Visual Basic Windows Presentation Foundation Visual Studio 2010 ]

Powered by:
Powered By Subtext Powered By ASP.NET