SlideShare ist ein Scribd-Unternehmen logo
1 von 93
Downloaden Sie, um offline zu lesen
UNIVERSITA' DEGLI STUDI DI TRIESTE

             FACOLTA' DI INGEGNERIA
     CORSO DI LAUREA IN INGEGNERIA INFORMATICA




Separazione dei ruoli tra Designer e Developer nello
 sviluppo di applicazioni Desktop: uso di WPF e del
           pattern Model-View-ViewModel




Laureando:                   Relatore:
Alessandro Andreose'         Chiar.mo Prof. Maurizio Fermeglia




                 Anno Accademico 2008 – 2009
Sommario
Introduzione............................................................................................................ 6

   Un po’ di storia .................................................................................................... 7

Capitolo uno ............................................................................................................ 8

   Windows Presentation Foundation .................................................................... 8

       Perché WPF ..................................................................................................... 9

       XAML ............................................................................................................. 10

       Layout e controlli .......................................................................................... 11

       Logical Tree e Visual Tree ............................................................................. 11

       Command ...................................................................................................... 13

       Routed Event................................................................................................. 14

       Dependency Property ................................................................................... 14

       Attached Property (o Attached Behaviour) .................................................. 15

       Object Resources e Resource Dictionary ...................................................... 15

       Data Binding .................................................................................................. 16

       Data Template............................................................................................... 18

       Separazione dei ruoli .................................................................................... 19

   Model – View – ViewModel .............................................................................. 20

       Model ............................................................................................................ 21

       ViewModel .................................................................................................... 21

       View .............................................................................................................. 22

       Separazione dei ruoli .................................................................................... 23

Capitolo due: Il toolkit .......................................................................................... 24

   INotifyPropertyChanged ................................................................................... 24


                                                                                                                           2
Delegate Command .......................................................................................... 27

      Scrivere il codice del comando nel ViewModel ............................................ 29

      Sapere quando un comando può essere eseguito ....................................... 30

      Aggiungere degli shortcut ............................................................................. 31

      Associare il comando al controllo nel codice XAML e minimizzare la scrittura
      di codice ripetitivo ........................................................................................ 32

      Come si usa ................................................................................................... 36

   IMessageBroker ................................................................................................ 36

   UI Composition ................................................................................................. 48

      IRegion .......................................................................................................... 49

      IModule ......................................................................................................... 54

      ViewModel .................................................................................................... 57

      Associare una region al ViewModel ............................................................. 58

      Application Controller ................................................................................... 60

Capitolo tre: L’applicazione .................................................................................. 63

   Obiettivo dell'applicazione ............................................................................... 63

   Architettura hardware e software dell'ambiente di produzione ..................... 63

   Requisiti tecnici non funzionali ......................................................................... 64

   Architettura dell'applicazione........................................................................... 64

      Application Server / Web Server .................................................................. 66

      Client ............................................................................................................. 68

   Attori ................................................................................................................. 69

   Struttura dell'applicazione ................................................................................ 71

   Alcune user story .............................................................................................. 72



                                                                                                                            3
Conclusioni ............................................................................................................ 80

Appendice uno: come scrivere un'applicazione WPF ........................................... 82

   XAML Conventions ............................................................................................ 82

       Utilizzare x:Name al posto di Name ............................................................ 82

       Posizionare il primo attributo di un elemento nella riga sotto il nome
       dell'elemento ................................................................................................ 82

       Preferire StaticResource a DynamicResource .............................................. 83

       Naming Convention per gli elementi ............................................................ 84

       Unire le risorse a livello di Applicazione ....................................................... 84

   Organizzazione delle risorse ............................................................................. 84

       Strutturare le risorse ..................................................................................... 84

       Naming Convention ...................................................................................... 86

Appendice due: come disegnare una Window in WPF ........................................ 87

   Grid.................................................................................................................... 88

Bibliografia ............................................................................................................ 92

   Web ................................................................................................................... 93




                                                                                                                            4
5
Introduzione

L’obiettivo primario di questa tesi è lo sviluppo di un toolkit per scrivere
applicazioni desktop in WPF applicando il pattern Model-View-ViewModel
(MVVM). Questo toolkit permette sia di non dover ogni volta riscrivere quelle
parti di codice che naturalmente si ripetono in ogni progetto, sia di avere del
codice testato, aggiornato e facile da manutenere.


Lo sviluppo di applicazioni senza l’utilizzo di pattern come Model View Controller
(MVC)1, Presentation Model, Model-View-ViewModel e derivati soffre di tre
gravi problemi:


         Non c’è separazione dei ruoli tra Designer e Developer.
         Non c’è una gestione dello stato dei controlli.
         È molto difficile testare le funzionalità legate alla user interface.


Tutti questi pattern cercano di risolvere questi tre problemi, però hanno un
costo: si deve scrivere molto più codice che poi deve essere manutenuto.


Inoltre, l’utente di oggi si aspetta di utilizzare applicazioni che abbiano
un’interfaccia utente accattivante e user-friendly. Ora più che mai c’è bisogno
che figure diverse partecipino alla realizzazione dell’intero progetto software.
Per quanto riguarda lo sviluppo della interfaccia utente (user interface, UI) sono
due i ruoli fondamentali: il developer e il designer. Il primo è in grado di creare
tutta la logica, il secondo invece deve curare l'aspetto grafico.


Il problema principale di questo approccio è che developer e designer devono
lavorare assieme, ma non devono, per quanto possibile, intralciarsi a vicenda.
Negli anni sono state sviluppate varie tecniche, sia architetturali, che
tecnologiche. Nel mondo web ad esempio la tecnologia dei Cascading Style Sheet


1
    http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller


                                                                                  6
(CSS) aiuta a suddividere i compiti delle due figure professionali. Dal punto di
vista architetturale esistono modelli di riferimento per la scrittura del codice
(pattern) come il Model View Controller, che separa il codice di visualizzazione
(View) dalle entità di business (Model): per comunicare hanno bisogno di un
Controller. Ed è proprio in quest’ultimo che viene inserita la maggior parte della
logica. La view ha poco codice (sperabilmente nessuno) e la maggior parte del
lavoro lo deve fare il designer.


Quando si sviluppano applicazioni con WPF, non è obbligatorio implementare il
pattern MVVM e gli altri principi che saranno spiegati in questa tesi. Tutti questi
principi, però, definiscono una metodologia di sviluppo che aiuta a separare il
ruolo del developer da quello del designer. Il toolkit, insomma, è la
formalizzazione di una metodologia che il developer applicherà ripetutamente
nelle sue applicazioni desktop con WPF.


Un po’ di storia

La separazione tra l’interfaccia utente (view) e il modello di business (model) non
è una nuova idea dello sviluppo software: ha circa trenta anni2. Recentemente è
rinato l’interesse per le architetture view-model, soprattutto a causa della
crescita della complessità dei sistemi software moderni e della necessità di
visualizzare un prodotto software su diverse interfacce utente (desktop, web, …)
senza per questo motivo dover riscrivere l’intera applicazione.


Il pattern Model-View-ViewModel è una variazione del pattern Model View
Controller, nato verso la fine degli anni settanta come framework sviluppato in
Smalltalk, e presentato come pattern da Martin Fowler nel suo libro Patterns of
Enterprise Application Architecture3.




2
    http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller#History
3
    http://martinfowler.com/books.html#eaa


                                                                                 7
Il pattern MVVM è stato presentato per la prima volta al pubblico nell’ottobre
del 2005 in un post4 sul blog di John Gossman, ed è stato utilizzato per la prima
volta in Microsoft per sviluppare Expression Blend, primo software Microsoft a
essere sviluppato interamente in Windows Presentation Foundation (WPF) ed
orientato alla figura del designer.


Capitolo uno

Windows Presentation Foundation

Niente è più importante della user experience di un'applicazione. Mentre molti
professionisti sono più interessati a come lavora un'applicazione, gli utenti che
utilizzano il software si preoccupano dell'interfaccia utente. L'interfaccia di
un'applicazione compone la stragrande maggioranza della user experience di un
software. Per molti utenti l'interfaccia e l'esperienza sono l'applicazione. Se si
produce una buona esperienza utente attraverso l'interfaccia, si aumenta la
produttività del software stesso.


Windows Presentation Foundation (WPF) è una parte del Framework .NET 3.0
uscito nel novembre 2006, aggiornato in seguito con il Framework 3.5 nel
novembre 2007 e con il Framework 3.5 SP1 nell’agosto 2008. WPF è la nuova
tecnologia Microsoft .NET per sviluppare l’interfaccia utente di un’applicazione
desktop ed è l’unica che viene aggiornata con l’aggiunta di nuove feature. Si
possono creare interfacce utente (UI) sia scrivendo codice managed, sia
utilizzando un linguaggio di markup XAML (eXtensible Application Markup
Language), una sintassi Xml per un approccio "dichiarativo", più da designer. È
possibile anche utilizzare una tecnica mista che è quella più comunemente
utilizzata. Per ogni window o user control di solito si hanno 2 file: un file .xaml




4
    http://blogs.msdn.com/johngossman/archive/2005/10/08/478683.aspx


                                                                                 8
che contiene il codice XAML e un file .xaml.cs (o .xaml.vb) che contiene il codice
associato (nel linguaggio scelto).


La prima parte di questo capitolo spiega le caratteristiche principali di WPF e
l’ausilio che WPF può dare per separare il lavoro del designer da quello dello
sviluppatore. La seconda parte del capitolo, invece, introduce il pattern Model –
View – ViewModel e come si collega a WPF.


Perché WPF

Lo scopo di Windows Presentation Foundation è di facilitare lo sviluppo
dell'interfac-cia utente, per migliorare la user experience. Utilizzando WPF
sviluppatori e designer possono creare interfacce che incorporino documenti,
video, immagini, grafica 2D e 3D, animazioni e molto altro.


Piattaforma unificata

Prima di WPF la creazione di interfacce utente seguendo i requisiti appena citati,
richiedeva l'utilizzo di molte tecnologie differenti. Questo di per sé non è un
problema.




                                                                                9
Il problema è che molto spesso uno sviluppatore non conosce tutte queste
tecnologie e quindi non le usa. Questo comportamento ha come conseguenza
diretta una minore user experience che va tutta a danno dell'utente finale.


WPF unifica tutte queste aree in un'unica tecnologia. Gli sviluppatori possono
creare applicazioni accattivanti conoscendo a fondo una singola tecnologia.


XAML

XAML è un linguaggio di markup utilizzato per istanziare oggetti .NET.
Nonostante XAML sia una tecnologia che può essere applicata a differenti domini
di problemi, la sua principale applicazione è di costruire interfacce utente in
WPF: i documenti XAML definiscono la creazione e il posizionamento di pannelli,
bottoni e controlli che vivono nelle window di un’applicazione WPF.


Lo standard XAML si fonda sull'applicazione di alcune semplici regole di base:


      Ogni elemento in un documento XAML mappa un’istanza di una classe
       .NET. Il nome del tag di ogni elemento è identico al nome della classe.
      Come per tutti i documenti XML, è possibile inserire un elemento
       all’interno di un altro.
      È possibile settare le proprietà di ogni classe attraverso gli attributi.
       Comunque, in certe situazioni un attributo non è sufficientemente
       potente. In questi casi è possibile inserire dei tag all’interno
       dell’elemento con una sintassi speciale.


L'utilizzo di codice XAML per creare interfacce utente permette a developer e
designer di lavorare sullo stesso file utilizzando strumenti diversi. Questo porta
ad una maggiore interazione tra le due figure professionali.




                                                                                 10
Layout e controlli

Per organizzare le varie parti di un'interfaccia, WPF utilizza dei contenitori per il
layout. Ogni panel può contenere dei figli, inclusi dei controlli come bottoni e
blocchi di testo o altri panel. Tutto ciò, crea una struttura ad albero che ha come
padre la window che contiene un panel che a sua volta è il padre di tutti gli altri
controlli.


Logical Tree e Visual Tree

La struttura ad albero principale in WPF è la struttura ad albero dell'elemento. In
Windows Presentation Foundation, sono disponibili due modi di elaborazione e
concettualizzazione della struttura ad albero dell'elemento: come albero logico e
come struttura ad albero visuale. Le distinzioni tra albero logico e struttura ad
albero visuale non sono sempre necessariamente importanti. Tuttavia possono
talvolta causare problemi ad alcuni sottosistemi WPF e influire sulle scelte fatte
nel markup o nel codice.


Logical Tree

In WPF è possibile aggiungere contenuti agli elementi utilizzando le proprietà. Ad
esempio, è possibile aggiungere elementi a un controllo ListBox utilizzando la
proprietà Items. In questo modo, gli elementi vengono collocati nell'oggetto
ItemCollection del controllo ListBox. Per aggiungere elementi a un



                                                                                  11
oggetto DockPanel è necessario utilizzare la relativa proprietà Children. In
questo caso, gli elementi vengono aggiunti all'oggetto UIElementCol-
lection dell'oggetto DockPanel.




Grazie all'albero logico, i modelli di contenuto possono scorrere prontamente i
possibili elementi figlio e quindi essere estendibili. Inoltre, l'albero logico fornisce
un framework per alcune notifiche, ad esempio quando tutti gli elementi
dell'albero logico sono caricati.


Ancora, i riferimenti di risorsa5 vengono risolti cercando verso l'alto nell'albero
logico gli insiemi di risorse relativi all'elemento di richiesta iniziale e succes-
sivamente gli elementi padre.


Visual Tree

Nella struttura ad albero visuale viene descritta la struttura degli elementi visivi
rappresentati dalla classe base Visual. Un'esposizione della struttura ad albero
visuale come parte della programmazione di applicazioni WPF convenzionale è
necessario, poiché su di esso è implementato il meccanismo degli eventi,




5
 Per avere maggiori informazioni sulle risorse, vedere il paragrafo Object Resources e Resource
Dictionary, presente in questo capitolo.


                                                                                            12
chiamato "routed events"6. Gli "eventi instradati" percorrono la struttura
dell'albero visuale e non dell'albero logico.


Command

In un'applicazione reale, le funzionalità sono divise in attività (task) di alto livello.
Questi task possono essere attivati da varie azioni differenti e da molti elementi
dell’interfaccia utente, inclusi i menù, i bottoni, le scorciatoie da tastiera e le
toolbar.


WPF permette di definire questi task, conosciuti come command, ed associare
ad essi i controlli, così non c’è bisogno di scrivere codice ripetitivo (code bloat)
quando si sottoscrive un evento. Ancora più importante, i comandi controllano lo
stato dell’interfaccia utente e automaticamente disabilitano i controlli a essi
collegati quando il task non è permesso.


L'esecuzione di comandi è un meccanismo di input di WPF che fornisce la
gestione di input a un livello semantico maggiore rispetto all'input del
dispositivo. Le operazioni Copia, Taglia e Incolla presenti in molte applicazioni
sono esempi di comandi.




La differenza tra i comandi e un semplice gestore eventi associato a un pulsante
o a un timer consiste nel fatto che i comandi separano la semantica e la creazio-



6
 Per avere maggiori dettagli sui ruoted event, vedere il paragrafo Routed Event, presente in
questo capitolo.


                                                                                         13
ne di un'azione dalla relativa logica. In questo modo, più codici sorgente diversi
possono richiamare la stessa logica di comando che pertanto può essere
personalizzata per obiettivi differenti.


Routed Event

Ci sono tre tipi di routed event: direct, bubbling e tunneling. I Direct Event sono
eventi che possono essere intercettati solo dall'elemento che ha creato l'evento.
I Bubbling Event sono eventi che viaggiano verso l'alto attraverso il visual tree e
possono essere intercettati da ogni parent dell'elemento sorgente. Infine, i
Tunneling Event sono eventi che viaggiano verso il basso attraverso il visual tree
e possono essere intercettati da ogni child dell'elemento sorgente.


Dependency Property

Le Dependency Properties evolvono il concetto standard di proprietà in .NET e
sono caratteristiche di WPF. Utilizzano lo storage in maniera più efficiente e
supportano alcune feature di alto livello come per esempio la notifica quando
cambia il valore e la capacità di propagare i valori di default verso il basso per
tutto l’albero di elementi. Le dependency property sono alla base di molte
caratteristiche di WPF, come il data binding e gli stili.


public string MyProperty
{
   get { return (string)GetValue(MyPropProperty); }
   set { SetValue(MyPropProperty, value); }
}

public static readonly DependencyProperty
                                    MyPropProperty =
   DependencyProperty.Register("MyProp",
typeof(string),
             typeof(MyClass), new
PropertyMetadata(""));




Dal codice si può osservare che una property, anziché memorizzare il valore in un


                                                                                14
campo privato, definisce una variabile di tipo DependencyProperty. Per conven-
zione, la DependencyProperty termina con la parola "Property".


Il metodo Register() accetta una classe di tipo PropertyMetadata che,
fra le altre cose, setta il valore di default della property.


Attached Property (o Attached Behaviour)

Le attached properties sono delle dependency properties particolari che possono
essere agganciate ad un oggetto. La cosa importante è che non devono essere
definite nella classe alla quale devono essere aggiunte.


Le attached properties sono particolarmente utili per agganciare i command
anche agli oggetti WPF e agli eventi che non supportano nativamente i
Command. Per esempio la TextBox non accetta nativamente un comando per
l’evento KeyPress: tramite un’attached property si può aggiungere. Può essere
utile, anche se si utilizza una TextBox come campo di ricerca: è più comodo per
l’utente scrivere il testo e premere invio senza dover prima spostare il focus su
un bottone “Cerca”.


Un altro esempio potrebbe essere il doppio clic su una ListView: è utile per
esempio per visualizzare il dettaglio di un oggetto visualizzato in una lista. WPF
non lo supporta nativamente, ma tramite le attached property si può aggiungere
questa caratteristica.


Object Resources e Resource Dictionary

WPF introduce un nuovo sistema di risorse che si integrano strettamente con
XAML. Questo sistema permette di definire risorse in molti posti all’interno del
markup (in un controllo specifico, in una window o per tutta l’applicazione) e
riutilizzarle facilmente.


Le Object Resource hanno un numero importante di benefici:



                                                                               15
   Efficienza. Le risorse permettono di definire un oggetto una volta sola e
        utilizzarlo in molti posti nel markup.
       Manutenibilità. Le risorse permettono di avere pochi dettagli di
        formattazione legati direttamente al controllo (come per esempio la
        dimensione del font) e di spostarli in un luogo centrale dove è più facile
        cambiarli. È l’equivalente XAML di creare costanti nel codice.
       Adattabilità.   Se    certe   informazioni     sono    separate    dal      resto
        dell’applicazione e sono salvate in una resource section, è possibile
        modificarle dinamicamente.


Ogni elemento include una proprietà Resources di tipo Dictionary, in cui è
possibile mettere le risorse. Questa collection può contenere qualsiasi tipo di
oggetto .NET. Ogni oggetto è indicizzato da una stringa.


Nonostante ogni oggetto ha una proprietà Resources, è più comune inserire le
risorse a livello di Window. Questo perché ogni oggetto figlio condivide le risorse
esposte dal padre.


Se si vogliono condividere le risorse, è possibile creare un Resource Dictionary.
Un resource dictionary è un documento XAML che fa da contenitore per le
risorse che si vogliono utilizzare.


La cosa importante delle risorse è che possono essere create e recuperate
direttamente tramite markup. Le risorse possono essere create direttamente nel
file xaml di una view, oppure in un file esterno. Per fare un paragone si possono
immaginare i file di risorse come un file CSS, nel quale sono salvati vari stili.


Data Binding

Il Data Binding è la possibilità di collegare un dato direttamente all’interfaccia
utente, senza doversi preoccupare di aggiornare la UI quando cambia il dato e
viceversa. In WPF non c’è limite a cosa sia un “dato”: può essere una stringa, un



                                                                                      16
numero, un oggetto di business più o meno complesso, un altro controllo utente,
eccetera. L’unico accorgimento è che si può fare il collegamento solo delle
proprietà e non dei campi pubblici.


 <TextBlock Text="{Binding Person, Path=FirstName}" />


Il data binding in WPF può essere di vari tipi:


      "OneWay" indica che l'interfaccia utente è aggiornata quando l'oggetto
       cambia.
      "OneWayToSource" è aggiornato l'oggetto quando l'interfaccia utente
       cambia.
      "TwoWay" l'aggiornamento è bidirezionale.
      "OneTime" è visualizzato il valore quando l’interfaccia utente viene
       visualizzata per la prima volta e poi non viene più aggiornato.




Un oggetto (source) collegato tramite data binding a una proprietà di un control-
lo (target) che risiede nell’interfaccia utente per funzionare correttamente deve
avere una serie di caratteristiche:


      La proprietà dell’oggetto target deve essere una Dependency Property;
      Se si tratta di TwoWay o OneWayToSource allora la proprietà da mettere
       in binding deve avere definito anche il setter;
      Se si tratta di OneWay o TwoWay, source deve implementare
       INotifyPropertyChanged o INotifyCollectionChanged,



                                                                               17
rispettivamente se la proprietà restituisce un singolo valore o una lista di
       oggetti;
      Se si vuole supportare la gestione degli errori, una soluzione possibile è
       far implementare a source IDataErrorInfo, oppure si crea una classe che
       eredita da ValidationRule e si associa al binding che interessa.


È possibile che si debba fare una conversione tra il valore della proprietà del
source e la proprietà del target. Questo può accadere per esempio perché da
una parte si ha una stringa, ma dall’altra parte si vuole avere un bool, oppure
perché si vuole formattare in un determinato modo una stringa. Per fare questo
esistono i ValueConverter, cioè degli oggetti che implementano l’interfaccia
IValueConverter.




Data Template

I Data Template sono dei descrittori: descrivono come deve essere visualizzato
un determinato oggetto. Per esempio si può decidere che un oggetto Person sia
visualizzato come una Label contenente la stringa Cognome, Nome. A questo
punto se in una listbox si inseriscono degli oggetti Person, verranno visualizzate
una lista di Label contente la stringa precedentemente creata. Unendo i
concetti di Data Template e risorse, è possibile creare una serie di resource
dictionary in file separati con i vari Data Template. In questo modo si definisce
una volta sola il template per un determinato oggetto.




                                                                                18
Separazione dei ruoli

Con il solo utilizzo di tutte le caratteristiche di WPF, designer e developer
possono lavorare assieme senza intralciarsi troppo. Il file con il code behind può
essere ridotto al minimo grazie all’utilizzo del Data Binding e dei command.


Sia il designer che il developer lavorano sul codice XAML: l’unica cosa che deve
fare il designer per agevolare il lavoro del developer è quello di associare (o non
modificare) gli handler d’evento ai controlli ed eventi corretti. Sarà poi il
developer a scrivere il codice all’interno degli handler nel code behind. Inoltre
dovrà associare i controlli tramite data binding ai command e agli oggetti
corretti. Grazie agli stili, i resource dictionary e ai template il designer può
modificare l‘interfaccia utente modificando poco il file xaml.


Il developer dal canto suo deve evitare di modificare gli stili associati ai vari
controlli nel file xaml.




                                                                                19
Model – View – ViewModel

Il pattern Model-View-ViewModel (MVVM) ha come scopo principale quello di
separare l’interfaccia utente dall’implementazione. È stato creato pensando
principalmente al Data Binding, in modo che sia possibile minimizzare (fino ad
annullare) il codice nel code behind della View. Tutto il codice applicativo si trova
nel ViewModel, che fa da ponte con il Model. Il designer lavora solo sulla View, il
developer su ViewModel. Per collegare View e ViewModel si utilizzano Data
Binding e i Command.


Lo scopo del pattern MVVM non è quello di non scrivere codice nel code behind
della View e nemmeno quello di scrivere migliaia di righe di codice. Ci sono
alcune cose che è corretto scrivere nella View, come per esempio la modifica del
focus di un controllo. Quest'operazione è possibile farla sia nella View che nel
ViewModel: il posto corretto è nella View (ed è molto più semplice). Inoltre se si
scrive il codice per gestire il focus nel ViewModel si crea una dipendenza del
ViewModel alla View: questo, però è un esempio di cosa NON fare, poiché
questo non è un compito del ViewModel, ma della View.




                                                                                  20
La cosa importante quando ci si chiede dove inserire una funzionalità è di
prendere in considerazione anche il code behind delle View e non escluderlo a
priori perché si sta utilizzando il pattern Model-View-ViewModel.


Model

Il model rappresenta il dominio applicativo, gli oggetti di business dell’applica-
zione, i dati. È completamente indipendente dall’interfaccia utente. Per esempio
la classe Person di seguito riportata è un esempio di classe appartenente al
modello ed ha due proprietà, FirstName e LastName.




ViewModel

Il ViewModel (VM) è un “Model of a View”. Può essere immaginato come
un’astrazione della view, ma nello stesso tempo è anche una specializzazione del
model che la view utilizza per il data binding. Il VM è particolarmente utile
quando il Model è complesso, o è già esistente e non si può modificare, oppure
quando i tipi di dato del model non sono facilmente collegabili alla view.


Come regola, ogni User Story ha il suo ViewModel. Mentre non è detto che ci sia
una corrispondenza uno a uno tra View e ViewModel. Può capitare di avere
molte View con un solo ViewModel o viceversa.


Il ViewModel si pone in mezzo tra View e Model e fa da ponte tra i due mondi:
quello del dominio applicativo e l’interfaccia utente. Non ha bisogno di
conoscere la view: tramite i Data Template si collega il ViewModel alla view. Il
ViewModel deve avere delle proprietà che rappresentano gli elementi che


                                                                               21
interessano alla View, oppure esporre direttamente parte del Model. Per
esempio deve avere le proprietà FirstName e LastName oppure una
proprietà che restituisce un oggetto Person. La prima soluzione è più elegante
perché si disaccoppia completamente la View dal Model, ma richiede la scrittura
di più codice. La seconda invece obbliga a scrivere gli oggetti di business
implementando determinate interfacce, se si vuole che il Data Binding, la
visualizzazione degli errori e altre cose funzionino correttamente. Se non si può
modificare il Model, la prima soluzione diventa una scelta obbligata.




Il ViewModel deve anche avere delle proprietà che restituiscono degli oggetti
che implementano l’interfaccia ICommand. Per esempio SaveCommand e
CloseCommand.


View

La view è formata prevalentemente da codice xaml. È formata da elementi
visuali, bottoni, finestre e controlli più complessi. Codifica le scorciatoie da
tastiera e controlla gli input dei vari device che è responsabilità del controller nel
MVC. Spesso è definita dichiarativamente, spesso tramite tool.


Ogni View deve conoscere il suo ViewModel associato perché deve agganciare,
tramite Data Binding, tutte le proprietà che servono: per esempio FirstName
e LastName se la View rappresenta l’anagrafica di un cliente. Inoltre deva




                                                                                   22
agganciare, sempre tramite Data Binding, i command ai vari eventi, come per
esempio il clic sul bottone “Ok” per salvare le modifiche.




Separazione dei ruoli

Unendo le potenzialità di WPF al pattern Model – View – ViewModel si ottiene
un code behind della Window, o View, che non contiene codice. Questo
permette di separare maggiormente il lavoro del designer da quello del
developer.


Il developer deve creare la logica a partire dal ViewModel. Il designer invece
deve fare l’interfaccia utente. L’unico punto di contatto tra i due ruoli si ha nel
file XAML: devono coesistere la parte di data binding e command da una parte e
la parte di stili e template dall’altra.


Se la View è in mano completamente al designer, l’unica cosa che deve sapere
per far funzionare correttamente l’interfaccia utente è il contratto esposto dal
ViewModel. A questo punto sta al designer agganciare i command che servono,
dove servono e recuperare i dati tramite binding.


Il developer dal canto suo, può dimenticarsi dell’esistenza della View, il suo
lavoro termina con la definizione del contratto e l’implementazione del
ViewModel.




                                                                                23
Capitolo due: Il toolkit

In questo capitolo si parlerà in dettaglio del toolkit che è stato costruito per
agevolare lo sviluppo di applicazioni basate sul pattern Model-View-ViewModel
per non dover riscrivere quelle parti di codice che naturalmente si ripetono in
ogni progetto. Sarà spiegato quali sono i problemi che si possono riscontrare e
come sono stati risolti.


Creare un'applicazione WPF senza utilizzare il pattern MVVM ovviamente è
possibile, e molto probabilmente l'interfaccia utente è identica a un'applicazione
scritta utilizzando il pattern. La vera differenza si nota soprattutto quando si
deve manutenere l'applicazione. Se si applica il pattern, ci sarà una divisione
netta tra l'interfaccia utente (e le operazioni proprie della UI) e il modello a
oggetti. In caso contrario, una modifica fatta a una tabella del database,
potrebbe essere molto più difficile da gestire.


INotifyPropertyChanged

Come già detto in precedenza, se si vuole aggiornare l'interfaccia utente quando
cambia il valore di una proprietà messa in binding, si deve implementare
l'interfaccia INotifyPropertyChanged.


Questo è indipendente dall'uso del pattern MVVM. La differenza risiede in quale
classe deve implementare questa interfaccia.




                                                                               24
Se non si utilizza il pattern, e si collegano alla finestra, tramite data binding,
direttamente gli oggetti di dominio, allora saranno gli oggetti di dominio stessi a
dover implementare l'interfaccia.


Se invece si utilizza il pattern MVVM, View e Model non si conoscono, quindi
deve essere il ViewModel a implementare INotifyPropertyChanged.




Una soluzione per fare implementare questa interfaccia a tutti i ViewModel è
fare in modo che tutti loro ereditino da una classe base ViewModelBase.
Questa classe avrà tutte le funzionalità comuni, fra cui l'implementazione delle
varie interfacce.


using System.Componentmodel;
using System.Diagnostics;

public class ViewModelBase : INotifyPropertyChanged
{
     public ViewModelBase()
     {
     }

     public event PropertyChangedEventHandler
                           PropertyChanged = delegate {
};

   protected virtual void OnPropertyChanged(
                                   string
propertyName)
   {
      this.VerifyPropertyName(propertyName);
      var handler = this.PropertyChanged;
         if (handler != null)



                                                                                25
{
               handler(
               this,
               new PropertyChangedEventArgs(propertyName));
         }
    }

    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    private void VerifyPropertyName (string propName)
    {
       if (TypeDescriptor
              .GetProperties(this)[propName] == null)
       {
         var msg = "Invalid property name: " + propName;
         Debug.Fail(msg);
       }
    }
}


Il metodo VerifyPropertyName serve per bloccare il debug con un errore se
il nome della proprietà passato non esiste. Questo può accadere quando si
modifica il nome di una proprietà, ma ci si dimentica di cambiare la stringa che si
passa a OnPropertyChanged.


Di seguito un breve esempio di come si usa questa classe.


public class MyViewModel : ViewModelBase
{
   private string myProperty;

    public string MyProperty
    {
       get
       {
           return this.myProperty;
       }

         set
         {
               if (value != this.myProperty)
               {
                  this.OnPropertyChanged("MyProperty");


                                                                                26
this.myProperty = value;
             }
         }
    }
}



Delegate Command

Si supponga di dover eseguire quest'operazione: la finestra View1 ha un
bottone, al clic bisogna scrivere in una TextBox "Bottone premuto".




Se non si applica il pattern MVVM la soluzione più semplice è quella di
intercettare l'evento Click del bottone e scrivere il testo nella TextBox.


Questo è il flusso di esecuzione:


    1. L'utente preme il bottone.
    2. Si esegue l'handler associato all'evento Click del bottone.
    3. Il codice setta la proprietà Text della TextBox.


                                                                             27
Se invece si implementa il pattern, il primo problema che si incontra è quello di
associare il command all'evento Click del bottone. L'altro problema è come fare a
scrivere il codice nel ViewModel e non nella View.




Ecco il flusso che si vuole ottenere per aggiornare la TextBox al clic del
bottone:


   1. L'utente preme il bottone.
   2. Viene scatenato il comando associato al bottone.



                                                                              28
3. Si esegue il metodo Execute del comando.
    4. Il codice setta la proprietà Text di tipo string.
    5. Tramite data binding viene aggiornata l'interfaccia utente.
    6. La TextBox contiene il testo "Bottone premuto".


Ci sono poi altre due cose che uno sviluppatore vorrebbe avere quando associa
un command ad un evento tramite codice xaml: la possibilità di sapere quando si
può eseguire quel comando e la possibilità di aggiungere degli shortcut da
tastiera.


Ci sono, quindi, quattro problemi da risolvere:


    1. Scrivere il codice del comando nel ViewModel.
    2. Sapere quando un comando può essere eseguito.
    3. Aggiungere degli shortcut.
    4. Associare il comando al controllo nel codice XAML e minimizzare la
        scrittura di codice ripetitivo.


Scrivere il codice del comando nel ViewModel

La soluzione a questo problema è di creare un proprio comando che implementa
l'interfaccia System.Windows.Input.ICommand.


public interface ICommand
{
   void Execute(object parameter);
   bool CanExecute(object paramenter);
   event CanExecuteChanged;
}
L'unica accortezza è di accettare un delegato nel quale è scritto il codice che si
deve eseguire quando si richiama il metodo Execute di ICommand.


public class DelegateCommand : ICommand
{
   private readonly Action<object> executeMethod;


                                                                               29
public DelegateCommand(Action<object>
executeMethod)
   {
      this.executeMethod = executeMethod;
   }

    public void ExecuteMethod(object parameter)
    {
       if (this.executeMethod == null) return;
       this.executeMethod(parameter);
    }

    // ...
}


In questo modo, per creare un comando basta passare al costruttore un
delegato.
Sarà poi compito dell'infrastruttura chiamare il metodo Execute dell'interfac-
cia ICommand.


Sapere quando un comando può essere eseguito

Quest'operazione è già prevista dall'interfaccia ICommand, che ha il metodo
CanExecute e l'evento CanExecuteChanged.


Bisogna quindi lavorare in maniera analoga al punto precedente, facendo in
modo che il costruttore accetti un secondo delegato che verrà poi eseguito dal
metodo CanExecute. Inoltre, bisogna far sapere all'infrastruttura di WPF
quando deve controllare nuovamente se può eseguire o no il comando. La
soluzione più semplice è quella di dire a WPF di controllare ogni volta che
succede qualche modifica.


Di seguito c'è il nuovo codice:


public class DelegateCommand : ICommand
{
   // ...


                                                                           30
private readonly Func<object, bool>
canExecuteMethod;

    public void CanExecuteMethod(object parameter)
    {
       if (this.canExecuteMethod == null) return;
       this.canExecuteMethod(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
       add { CommandManager.requerySuggested += value;
}
    remove { CommandManager.requerySuggested -= value;
}
    }
}


Aggiungere degli shortcut

Per risolvere questo problema si deve creare una nuova interfaccia
IBindableCommand che eredita da ICommand.


public interface IBindableCommand : ICommand
{
   InputBindingCollection InputBindings { get; }
   string DisplayText { get; }
}


Fatto ciò, si fa implementare a DelegateCommand questa interfaccia e non
ICommand.


Seguono le aggiunte alla classe DelegateCommand.


using System;
using System.Windows.Input;

public class DelegateCommand : IBindableCommand
{
   // ...



                                                                     31
public void AddGesture(InputGesture gesture)
    {
       if (this.inputBindings == null)
       {
          this.inputBindings =
             new InputBindingCollection();
       }

         this.inputBindings.Add(
               new InputBinding(this, gesture);
    }

    public InputBindingCollection Inputbindings
    {
       get { return this.inputBindings; }
    }

    public string DisplayText { get; private set; }
}



Associare il comando al controllo nel codice XAML e minimizzare la
scrittura di codice ripetitivo

Qui le cose da fare sono due: creare una Markup Extension per il codice XAML e
associare agli shortcut ai controlli.


Una markup extension è un'estensione alla sintassi del codice XAML.


È possibile implementare una markup extension per fornire valori per le proprie-
tà nell'utilizzo di un attributo, per le proprietà nell'utilizzo di un elemento pro-
prietà o in entrambi i casi.


Quando è utilizzata per fornire un valore di attributo, la sintassi che distingue
un'estensione di markup in un processore XAML è la presenza delle parentesi
graffe di apertura e chiusura { e }. Il tipo di estensione di markup è quindi
identificato tramite il token di stringa immediatamente successivo alla parentesi
graffa di apertura.



                                                                                 32
Quando è utilizzata nella sintassi degli elementi proprietà, un'estensione di
markup corrisponde visivamente a qualsiasi altro elemento utilizzato per fornire
un valore di elemento proprietà, ovvero una dichiarazione di elementi XAML che
fa riferimento alla classe dell'estensione di markup come elemento, racchiuso tra
parentesi angolari (<>).


Spesso si utilizzano le markup extension per trasformare molte righe di codice
XAML in un'espressione molto più concisa.


Per fare in modo di poter associare direttamente nello XAML un
IBindableCommand, si crea una classe BindCommand che eredita da
System.Windows.Markup.MarkupExtension.
Nel metodo HandleLoaded si associa lo shortcut.


using   System;
using   System.ComponentModel;
using   System.Windows;
using   System.Windows.Controls;
using   System.Windows.Input;
using   System.Windows.Markup;
using   ComponentModel;

[MarkupExtensionReturnType(typeof(IBindableCommand))]
public class BindCommand : MarkupExtension
{
   public BindCommand()
   {
   }

    public BindCommand(string commandName)
    {
       this.Name = commandName;
    }

    public string Name { get; set; }

    public override object ProvideValue(
       IServiceProvider serviceProvider)
    {
       var pvt =
          serviceProvider.GetService(
             typeof(IProvideValueTarget))
          as IProvideValueTarget;



                                                                              33
if (pvt != null)
         {
            var fe = pvt.TargetObject as FrameworkElement;
            if (fe != null)
            {
               fe.Loaded += this.HandleLoaded;
            }
         }

         return null;
     }

  private void HandleLoaded(object sender, RoutedEventArgs
e)
   {
      var fe = sender as FrameworkElement;
      if (fe == null ||
          DesignerProperties.GetIsInDesignMode(fe))
      {
         return;
      }

         if (fe.DataContext == null)
         {
            return;
         }

          // Use reflection to retrieve provided Command.
         var property =
             fe.DataContext.GetType().GetProperty(this.Name);

         // Get root elements.
         var rootElement = this.GetRootElement(fe);
         var command = (IBindableCommand)
              property.GetValue(fe.DataContext, null);
         var source = sender as ICommandSource;
          if (source != null)
          {
             var button = fe as Button;
             var menuItem = fe as MenuItem;

             // Is it a button?
             if (button != null)
             {
                button.Command = command;
                if (!String.IsNullOrEmpty(command.DisplayText))
                {
                   button.Content =
                   new AccessText { Text = command.DisplayText
};
                 }
             }



                                                                34
else if (menuItem != null)
             {
                // MenuItem.
                menuItem.Command = command;
                if (!String.IsNullOrEmpty(command.DisplayText))
                {
                   menuItem.Header =
                   new AccessText { Text = command.DisplayText
};
               }

               if (command.InputBindings != null)
               {
                  var keyGesture =
                 command.InputBindings[0].Gesture as
KeyGesture;
                   if (keyGesture != null)
                   {
                      menuItem.InputGestureText =

keyGesture.DisplayString;
               }
            }
         }

             // Add commandbindings to root element.
             if (command.InputBindings != null)
             {
               foreach (InputBinding ib in
                                        command.InputBindings)
               {
                  rootElement.InputBindings.Add(ib);
               }
             }
         }

         // De-register loaded event.
         fe.Loaded -= this.HandleLoaded;
     }

     private FrameworkElement GetRootElement(
                                        FrameworkElement fe)
     {
        if (fe.Parent == null)
        {
           return fe;
        }

         return this.GetRootElement(
                             fe.Parent as FrameworkElement);
     }
}



                                                                 35
Come si usa

Per utilizzare queste classi, nel ViewModel si crea un BindableCommand.


public class MyViewModel
{
   public ICommand MyCommand { get; private set; }

    public MyViewModel()
    {
       this.MyCommand = new DelegateCommand(
          o =>
             {
                 // TODO: Add Code here...
             });
    }
}


Nel codice XAML si associa a un controllo il comando.


<UserControl
   x:Class="MyView"
   xmlns="..."
   xmlns:x="..."
   xmlns:cmd=
       "clr-
namespace:Toolkit.Commands;assembly=Toolkit"
   >
   <Button
      Command="{cmd:BindCommand Name=MyCommand}"
      />
</UserControl>




IMessageBroker

Si supponga di dover eseguire quest'operazione: una finestra, View1 ha un
bottone. Al clic del bottone si deve visualizzare un'altra finestra, View2.


Se non si è applica il pattern MVVM, la soluzione più semplice è creare e visualiz-
zare View2 direttamente dall'handler associato all'evento Click del bottone.


                                                                                36
Se però si implementa il pattern MVVM, per eseguire un'operazione bisogna
passare dal ViewModel.




Una prima soluzione che può venire in mente è:



                                                                      37
1. Associare un comando al clic del bottone e nel metodo Execute, che si
       trova nel ViewModel e non nella View.
   2. Creare ViewModel2 (VM associato a View1).
   3. Nel metodo Show di ViewModel2, creare View2 e visualizzare la
       finestra.


Questo scenario però ha un problema:


      I due moduli non sono indipendenti: ViewModel1 conosce View-
       Model2.


Per risolvere questo problema si utilizza il broker. Esso è utilizzato per far comu-
nicare due moduli che non si conoscono.


La soluzione è creare un terzo attore, il broker appunto, che si mette in mezzo ed
è conosciuto da entrambi. In questo modo il modulo A se deve inviare qualcosa
al modulo B, invia un messaggio al broker che lo consegna al modulo B.


Più in dettaglio un modulo sottoscrive un messaggio e quando qualcuno invia
quel particolare messaggio, il broker lo recapita a tutti quelli che lo hanno
sottoscritto.


Il sistema di comunicazione del broker, sottoscrizione tramite delegato e invio
del messaggio, è molto simile agli eventi: sottoscrizione tramite handler e lancio
dell'evento. L'utilizzo del broker, quindi, è molto simile ad utilizzare gli eventi,
però è stato ottenuto il disaccoppiamento tra i moduli.


Il compito del "passacarte" è svolto da una classe che implementa l'interfaccia
IMessageBroker.




                                                                                 38
L'interfaccia ha due metodi per sottoscrivere un messaggio: il sottoscrittore
chiede al message broker di essere notificato, invocando il delegato passato
come parametro, quando un messaggio di tipo T è inviato; ha cinque metodi per
cancellare la sottoscrizione e un metodo per inviare un messaggio.




                                                                          39
Gli unici metodi che hanno bisogno di una spiegazione sono i cinque metodi
Unsubscribe.


void Unsubscribe(object subscriber);
Cancella tutte le sottoscrizioni di un sottoscrittore.


void Unsubscribe<T>(object subscriber)
     where T : IMessage;
Cancella tutte le sottoscrizioni di un sottoscrittore per un determinato messaggio
T.


void Unsubscribe(object subscriber, object sender);
Cancella tutte le sottoscrizioni di un sottoscrittore per un determinato sender.


void Unsubscribe<T>(object subscriber, object sender)
     where T : IMessage;
Cancella tutte le sottoscrizioni di un sottoscrittore per un determinato sender e
solo per un certo tipo di messaggio T.


void Unsubscribe<T>(
       object subscriber,
       System.Action<T> callback)
     where T : IMessage;
Cancella tutte le sottoscrizioni di un sottoscrittore per una determinata callback.


L'interfaccia IMessage è così definita.




                                                                                   40
L'utilizzo del message broker per comunicare tra più viewmodel ha anche il
vantaggio che il broker stesso non deve sapere a priori, quali saranno i messaggi
da inviare. Questo permette di creare messaggi ad hoc per ogni esigenza,
conosciuti solo da sender e sottoscrittore.


Di seguito è mostrato un esempio di utilizzo del broker per sottoscrivere un
messaggio.


class LoadGuardianCostListViewModel : IModule
{
   public void Initialize(
      object sender,
      IUnityContainer container)
   {
      var broker = container.Resolve<IMessageBroker>();
      broker.Subscribe<GetAllItemsMessage<GuardianCost>>(
                sender,
                message =>
                {
                    var vm = new GuardianCostListViewModel(
                        container,
                        message.Context);

                            vm.Show();
                      });
     }
}


La classe LoadGuardianListViewModel ha un unico metodo che crea una
sottoscrizione a un messaggio che quando viene inviato causa la visualizzazione
della view GuardianCostListViewModel.


Per inviare invece un messaggio ecco un esempio.


this.Broker.Dispatch(
   new GetAllItemsMessage<GuardianCost>(
      this,
      ServerContextRegistry.GetNew(container)))




                                                                              41
ServerContextRegistry.GetNew restituisce una nuova unit of work da
utilizzare per lavorare con il database. Container è un container di inversion of
control (IoC), nello specifico Unity di Microsoft.


Dependency Inversion Principle (DIP) e Inversion of Control (IoC)

Secondo il Dependency Inversion Principle7 ogni modulo di alto livello non
dovrebbe dipendere da un modo di livello più basso. Entrambi dovrebbero
dipendere da un'astrazione. L'astrazione deve essere indipendente dai dettagli. I
dettagli dovrebbero dipendere dall'astrazione.


In seguito sono mostrati due esempi: il primo non rispetta questo principio, il
secondo sì. La grossa differenza sta nel fatto che nel secondo esempio gli oggetti
di basso livello sono iniettati nel costruttore e non creati dall'oggetto di alto
livello.


public class FinanceInfoService
{
   public string GenerateAsHtml(string symbol)
   {
      SomeStockFinder finder = new SomeStockFinder();
      StockInfo[] stocks =
         finder.FindQuotesInfo(symbol);

           HtmlTableRenderer renderer =
              new HtmlTableRenderer ();
           return renderer.RenderQuoteInfo(stocks);
     }
     //...
}


L'obiettivo è ottenere una struttura molto simile all'immagine seguente.




7
  Il DIP è stato formalizzato da Robert Martin. È possibile leggere altri dettagli sul sito web
http://www.objectmentor.com/resources/articles/dip.pdf


                                                                                            42
In questo modo, GenerateAsHtml diventa indipendente da SomeStock-
Finder e HtmlTableRenderer.


public class FinanceInfoService
{
   IFinder finder;
   IRenderer renderer;

    public FinanceInfoService(
       IFinder finder,
       IRenderer renderer)
    {
       this.finder = finder;
       this.renderer = renderer;
    }

    public string GenerateAsHtml(string symbol)
    {
       StockInfo[] stocks =
          finder.FindQuotesInfo(symbol);

       return renderer.RenderQuoteInfo(stocks);
    }
    //...
}


L'Inversion of Control è l'applicazione del DIP. Spesso IoC è anche detta
Dependency Injection (DI). A volte in letteratura si trova che la DI è il principio,




                                                                                 43
mentre IoC è l'applicazione del principio. In ogni caso IoC è basato sul pattern
DIP.


Oggi IoC/DI è spesso associato con dei framework particolari che hanno una serie
di caratteristiche.


Esistono vari framework di IoC, ma tutti hanno delle caratteristiche comuni: sono
costruiti attorno ad un container che, in base ad alcune informazioni di
configurazione, risolve le dipendenze. Il chiamante istanzia il container e gli
passa l'interfaccia desiderata come parametro. Come risposta, il framework di
IoC/DI restituisce un oggetto concreto che implementa quell'interfaccia.


In questa tesi, come framework di IoC è stato utilizzato Microsoft Unity
Application Block.


Struttura del broker

Il broker ha un campo privato: un dictionary chiamato subscriptions.


private IDictionary<Type, IList<Action<T>>> subscriptions;



Questo dictionary usa come chiave il tipo del messaggio che ha una sottoscrizio-
ne e come valore una lista di delegati, che devono essere invocati quando è in-
viato un messaggio del tipo presente nella chiave.


In WPF non è possibile modificare un oggetto dell'interfaccia utente da un
thread diverso da quello che ha creato l'oggetto stesso, pena una
CrossThreadException. In WPF esiste quindi un oggetto Dispatcher,
che evita questo problema. Il suo metodo Invoke, accetta un delegato, che
viene eseguito sempre nel thread visuale dell'applicazione.




                                                                              44
Per questo motivo, il broker ha anche un altro campo privato, un dispatcher,
che serve al metodo Dispatch per evitare le CrossThreadException.


private readonly Dispatcher dispatcher;




Sottoscrivere un messaggio

Per sottoscrivere un messaggio, si deve chiamare un overload del metodo
Subscribe.




                                                                         45
Questo metodo ha almeno un parametro di tipo Action<T> che è l'azione da
eseguire quando viene inviato il messaggio e un parametro generico di tipo T
che indica a quale messaggio ci si deve sottoscrivere.


Quando qualcuno chiama Subscribe, il broker, scopre se il dictionary
subscriptions contiene già il messaggio T, e in caso contrario lo aggiunge.
Poi aggiunge il delegato alla lista di delegati di quel messaggio.


public void Subscribe<T>(Action<T> callback)
   where T : IMessage
{
   if (this.subscriptions.ContainsKey(typeof(T)))
   {
      var subscribers = this.subscriptions[typeof(T)];
      subscribers.Add(callback);
   }
   else
   {
      this.subscriptions.Add(
         typeof(T),
         new List<Action<T>>() { callback });
   }
}



Inviare un messaggio

Per inviare un messaggio si chiama il metodo Dispatch<T>().


Quando viene chiamato questo metodo, il broker cerca nel dictionary
subscriptions se qualcuno ha sottoscritto il messaggio T. In caso
affermativo chiama tutti i delegati associati.




                                                                         46
public void Dispatch<T>() where T : IMessage
{
   if (this.subscriptions.ContainsKey(typeof(T)))
   {
      var subscribers = this.subscriptions[typeof(T)];
      subscribers
         .ToList()
         .ForEach(callback =>
         {
             try
             {
                 this.dispatcher.Invoke(callback);
             }
             catch (Exception e)
             {
                 if (e.InnerException != null)
                    throw e.InnerException;
                 else throw;
             }
         });
   }
}




                                                     47
UI Composition

Un'applicazione desktop spesso è formata da molte finestre distinte. Una cosa
importante nello sviluppo di quel genere di applicazioni è cercare di tenere le
varie finestre fra loro indipendenti.


Di seguito ci sono alcune definizioni.


Modulo
Un modulo è una parte dell'applicazione, generalmente visuale. I moduli fra loro
devono essere indipendenti e nel caso di moduli visuali sono visualizzati nella
shell all'interno di una region.


Region
La region contiene i moduli visuali.


Shell
Finestra principale dell'applicazione. Esiste solo una shell e contiene, in varie
region, gli altri moduli. La shell non deve conoscere i moduli, conosce solo le
region che li conterranno.


Se i moduli sono indipendenti fra loro, ci sono una serie di vantaggi:


       Possono essere sviluppati indipendentemente l'uno dall'altro.
       È possibile mostrare certi moduli a certi utenti ed altri ad altri senza
        difficoltà: basta non caricare il modulo che non si vuole visualizzare.
       È possibile caricare i moduli che interessano solo a runtime, magari
        utilizzando un file di configurazione.


Per implementare il pattern MVVM non c'è nessuna necessità di scrivere
applicazioni composite. Creando applicazioni di questo tipo, però, si riesce a
modificare o aggiungere una parte d'interfaccia utente senza interferire con
tutto il resto dell'applicazione. E questo è un grosso vantaggio.


                                                                                  48
Il pattern MVVM è un buon punto di partenza per creare applicazioni composite
per due motivi:


      Il broker: poiché i moduli non si possono conoscere, sarà lui l'unico in
       grado di farli comunicare.
      L'indipendenza intrinseca dei ViewModel (Moduli se si parla in termini di
       UI Composition) che è collegata alla regola "Una User Story, un
       ViewModel": se si aggiungono, o si modificano alcune User Story (cioè
       alcuni requisiti del software), basta modificare solo il VM direttamente
       interessato (e la View naturalmente) lasciando inalterato tutto il resto.


Per avere un'applicazione composita mancano ancora alcuni dettagli, che
saranno spiegati nei prossimi paragrafi.


IRegion

Il primo problema che si riscontra nello sviluppo di applicazioni composite è
quello di fare in modo che la Shell non abbia alcuna conoscenza dei moduli che
andrà a visualizzare.




                                                                                   49
Per fare ciò, la Shell avrà una o più region. All'interno di ogni region è possibile
visualizzare uno o più moduli.


Naturalmente anche un modulo a sua volta può contenere una region, nella
quale ci sono altri moduli, e così via.


Si supponga di dover eseguire quest'operazione: nella finestra principale
(Shell), quando si preme un bottone, si deve inserire nella shell stessa una
view (View1).




Se non applichiamo la UI Composition, la soluzione più semplice è creare View1
e associarlo alla region nell'handler associato evento Click del bottone.


Questo è il flusso di esecuzione:


   1. L'utente preme il bottone.
   2. Si esegue l'handler associato all'evento Click.



                                                                                 50
3. Il codice, che si trova nella Shell, crea un'istanza di View1 e la inserisce
       nella region.




Quest'approccio ha due problemi:


      La Shell crea il modulo View1.
      Il codice per inserire un modulo nella region si trova sempre nella Shell.


Quello che si vuole ottenere è qualcosa di simile all'immagine sottostante.




                                                                                    51
Quello che cambia rispetto alla situazione precedente è tutta racchiusa nel punto
due:


      Non si crea più il modulo nella Shell, ma si recupera un'istanza dal
       container di IoC.
      Il codice per inserire un modulo non si trova più nella Shell, ma nel
       metodo ShowViewModel della classe region.


Se si implementa anche il pattern MVVM, l'unica differenza è che il codice in 2 si
troverebbe nel ViewModel e non nella View.


Implementazione

Una region implementa l'interfaccia IRegion.


public interface IRegion
{
   IEnumerable<IWorkspaceViewModel> ViewModels { get;
}
   void ShowViewModel(IWorkspaceViewModel item);
   bool? ShowDialogViewModel(IWorkspaceViewModel
item);
}


L'interfaccia ha una proprietà che restituisce la lista di moduli che contiene, e
due metodi: ShowViewModel e ShowDialogViewModel. Il secondo apre
una finestra modale.


Come si può notare entrambi i metodi accettano come unico parametro un
oggetto che implementa l'interfaccia IWorkspaceViewModel. Questa interfaccia
è la base di tutti i moduli visuali che devono essere visualizzati in una region.


public interface IWorkspaceViewModel : IViewModel
{
   event EventHandler Closed;
   ICommand CloseCommand { get; }



                                                                                    52
object Content { get; }
    void Show();
    bool? ShowDialog();
}


Utilizzando questa struttura, la shell ha almeno una region con all'interno
almeno un modulo da visualizzare. Manca ancora qualcosa però: la region ha una
lista di ViewModel, mentre bisogna visualizzare le rispettive View. Questo si
risolve utilizzando una caratteristica di WPF. In Windows Presentation
Foundation può essere visualizzato qualunque oggetto. Se è un oggetto visuale,
è visualizzato, altrimenti è chiamato ToString() dell'oggetto per visualizzare
una stringa. C'è una terza possibilità: per un oggetto si può definire quale sarà la
sua visualizzazione utilizzando i Data Template.


<DataTemplate DataType="{x:Type vm:PhoneViewModel}">
   <ScrollViewer
      HorizontalScrollBarVisibility="Auto"
      VerticalScrollBarVisibility="Auto"
      >
        <vw:PhoneView />
   </ScrollViewer>
</DataTemplate>


Questo template nello specifico, visualizza uno ScrollViewer e all'interno la
view associata a PhoneViewModel.


Il toolkit contiene tre classi diverse che implementano l'interfaccia IRegion:


       ContentContainer: può contenere un solo viewmodel.
       ListContainer: può contenere più viewmodel.
       DialogContainer: apre una finestra modale e può contenere un solo
        viewmodel.




                                                                                 53
IModule

Quando si scrivono applicazioni composite formate da tanti moduli, bisogna
trovare un modo semplice per inizializzare i moduli stessi. Portando all'estremo
deve essere possibile indicare in un file di configurazione quali moduli
inizializzare. Quest'ultima cosa non è presente nel toolkit. Quello che è presente,
invece, è la possibilità di scoprire a runtime quali sono i moduli e come
inizializzarli indicando a compile time solo gli assembly che contengono i moduli
stessi.


Inizializzazione di un modulo

Tornando all'esempio della figura precedente, manca qualcosa: anziché creare
un oggetto View, si utilizza il container di IoC per risolvere la dipendenza.
Bisogna però ancora capire come si fa a inizializzare View1.




In altre parole, manca chi deve eseguire RegisterInstance(new
View1());


public interface IModule
{
   void Initialize(object sender, IUnityContainer
container);
}


L'interfaccia ha solo un metodo che inizializza il modulo. Una piccola nota. Il
metodo Initialize non restituisce nulla, quindi se si deve recuperare il



                                                                                54
modulo in un secondo momento, nel metodo suddetto si deve inserire il modulo
nel container di IoC. In questo modo poi nell'applicazione sarà possibile
recuperare il modulo inizializzato.


Di seguito c'è un esempio di inizializzazione di un modulo visuale.


internal sealed class LoadPersonViewModel : IModule
{
   public void Initialize(
        object sender,
        IUnityContainer container)
   {
      var broker = container.Resolve<IMessageBroker>();
      broker.Subscribe<EditingMessage<Person>>(
          sender,
          message =>
          {
             var myContainer = container;
             var pvm =
                new PersonViewModel(
                   message.Context,
                   myContainer,
                   message.Item,
                   true);
             pvm.Show();
          });
   }
}


Questa classe è un modulo non visuale che recupera l'istanza del message
broker, e sottoscrive un messaggio. Il delegato che deve essere eseguito quando
il messaggio è inviato altro non fa che creare un ViewModel e visualizzarlo.
Questo ViewModel è un modulo visuale che sarà visualizzato all'interno di una
region.


Discovery dei moduli

Naturalmente, dopo aver scritto il codice di inizializzazione dei moduli si deve
eseguire il codice stesso. Bisogna prima però scoprire quali moduli sono stati
creati per l'applicazione.




                                                                             55
Per fare questo, bisogna dire al toolkit quali assembly contengono le classi che
implementano l'interfaccia IModule.


public static void RegisterModules(
      IUnityContainer container,
      Assembly assembly)
{
   if (container == null)
      throw new ArgumentNullException("container");

     if (assembly == null)
        throw new ArgumentNullException("assembly");

    assembly
        .GetTypes()
        .Where(t => t.IsInterfaceImplemented<IModule>())
        .ForEach(t =>
               container.RegisterType(
                  typeof(IModule),
                  t,
                  t.FullName,
                  new
ContainerControlledLifetimeManager()));
}


Questo metodo controlla tutti i tipi di un assembly alla ricerca di classi che
implementano l'interfaccia IModule. Quando ne trova una, la inserisce nel
container di IoC come singleton.


ForEach e IsInterfaceImplemented sono due extension method.


Gli Extension Method consentono di "aggiungere" metodi ai tipi esistenti senza
creare un nuovo tipo derivato, ricompilare o modificare in altro modo il tipo
originale. Gli extension method sono uno speciale tipo di metodo statico, ma
sono chiamati come se fossero metodi d'istanza sul tipo esteso.


L'ultima cosa da fare è quella di recuperare i moduli dal container di IoC e
inizializzarli.


foreach (var item in this.Container.ResolveAll<IModule>())



                                                                             56
{
    item.Initialize(this, this.Container);
}


ViewModel

Tutti i ViewModel implementano l'interfaccia IViewModel. Questa interfaccia
ha quattro proprietà. IsInDesignMode serve per indicare se il ViewModel è
stato creato a runtime oppure se è stato creato all'interno di un designer come
Visual Studio o Expression Blend. Quest'operazione è utile soprattutto in
Expression Blend 3 per poter associare il ViewModel direttamente alla View: così
facendo il designer elenca tutte le property del ViewModel che possono essere
messe in Data Binding con la View. Inoltre nel codice è possibile inizializzare il
ViewModel con alcuni valori di test solo quando il ViewModel è utilizzato
all'interno di un designer.


public interface IViewModel
   : IDisposable,
     INotifyPropertyChanged
{
   string DisplayName { get; }
   IMessageBroker Broker { get; }
   IUnityContainer Container { get; }
   bool IsInDesignMode { get; }
}




Ci sono tre interfacce che ereditano da IViewModel:


       ICommandViewModel: si utilizza quando un singolo comando deve
        avere una view per essere inserito in una view container.
       IWorkspaceViewModel: tutti i ViewModel che devono essere inseriti
        in una region devono implementare questa interfaccia che altro non fa
        che aggiungere alcune funzionalità di visualizzazione e chiusura della
        view.




                                                                               57
   IShellViewModel:          il   ViewModel     associato   alla   Shell   deve
       implementare questa interfaccia.




Associare una region al ViewModel

Quando si crea una classe che implementa IWorkspaceViewModel è
indispensabile anche indicare in quale region deve essere inserito. Questa
informazione però interessa solamente a runtime, quindi si può utilizzare un
attributo per salvare questa informazione e un po' di reflection per recuperarla a
runtime.


L'attributo ha due proprietà: una che indica il tipo di region, l'altra, opzionale,
che rappresenta il nome di quella region all'interno del container di IoC.


A runtime si devono recuperare le informazioni presenti nell'attributo per
inserire il ViewModel nella region corretta.


                                                                                58
[Region(typeof(ListContainer), Name = "MyRegion")]
class MyViewModel : IWorkspaceViewModel
{
   private IRegion region;

   public MyViewModel()
   {
      RegionAttribute regionAttribute;
      if (this.GetType()
            .TryGetCustomAttribute(out
regionAttribute))
      {
         if
(string.IsNullOrEmpty(regionAttribute.Name))
         {
            this.region =
              (IRegion)container.Resolve(
                  regionAttribute.Region);
         }
         else
         {
            this.region =
              (IRegion)container.Resolve(
                  regionAttribute.Region,
                  regionAttribute.Name);
         }
      }
   }

    public void Show()
    {
       this.region.ShowViewModel(this);
    }

    // ...
}
Il codice recupera le informazioni sulla region a runtime e risolve la dipendenza
utilizzando il framework di IoC. E questa region sarà poi utilizzata dal metodo
Show. TryGetCustomAttribute è un extension method che altro non fa
che valorizzare regionAttribute se il tipo è decorato con l'attributo
RegionAttribute.


L'ultima cosa da fare è quella di creare un'istanza della region nella quale inserire
il ViewModel e registrare la stessa nel framework di IoC.


                                                                                  59
this.Container.RegisterType<ListContainer>(
   "MyRegion",
   new ContainerControlledLifetimeManager());
Questo codice va inserito nella fase di inizializzazione dell'applicazione, cioè
nell'Application Controller del quale parleremo a breve.


Application Controller

L'ultima cosa che rimane da fare è l'inizializzazione dell'applicazione.


Le operazioni da fare sono le seguenti:


       Creare un container di IoC e registrare tutto quello che serve.
       Creare un'unica istanza del MessageBroker.
       Creare un'unica istanza della Shell.
       Cercare e inizializzare tutti i moduli.


Per eseguire queste operazioni per prima cosa si crea una classe base astratta
ApplicationControllerBase: questa classe ha tutte le funzionalità
indipendenti dalla particolare applicazione.


public abstract class ApplicationControllerBase
{
   private readonly IUnityContainer container;
   private IShell shell;
   private IMessageBroker broker;

    protected ApplicationControllerBase(
       IUnityContainer container)
    {
           this.container = container;
    }

    public IShell Shell
    {
       get { return this.shell; }
    }




                                                                             60
protected IUnityContainer Container
    {
       get { return this.container; }
    }

    public void Run()
    {
       // Registra il MessageBroker.
       this.container
          .RegisterType<IMessageBroker, MessageBroker>(
              new ContainerControlledLifetimeManager(),
              new InjectionConstructor(
                 Dispatcher.CurrentDispatcher));

         // Trova tutti i moduli presenti negli assembly
         // e li registra nel container di IoC.
         foreach (var assembly in
               this.GetViewModelAssemblies())
            Unity.Discovery.RegisterModules(
               this.Container,
               assembly);

         this.broker =
            this.Container.Resolve<IMessageBroker>();
         this.shell = this.CreateShell();

         // Recupera tutti i moduli e li inizializza.
         foreach (var item in
               this.Container.ResolveAll<IModule>())
            item.Initialize(this, this.Container);
    }

    protected abstract IShell CreateShell();
    protected abstract
       IEnumerable<Assembly> GetViewModelAssemblies();
}
Questa classe ha due metodi astratti che devono essere implementati dalla
classe che eredita da essa, ApplicationController, che personalizza il
comportamento di default creando la shell e indicando quali sono gli assembly
nei quali si devono cercare i moduli.


class ApplicationController :
ApplicationControllerBase
{



                                                                          61
public ApplicationController(
           IUnityContainer container)
        : base(container)
     {
        this.Container.RegisterType<ListContainer>(
           new ContainerControlledLifetimeManager());
     }

     protected override IShell CreateShell()
     {
        this.Container.RegisterType<IShell, Shell>(
           new ContainerControlledLifetimeManager());

         var shell = this.Container.Resolve<IShell>();
         shell.DataContext =
            new ShellViewModel(this.Container);
         return shell;
     }

     protected override IEnumerable<Assembly>
        GetViewModelAssemblies()
     {
         return new[] { Assembly.GetExecutingAssembly()
};
     }
}


Il costruttore registra nel container di IoC l'unica region dell'applicazione. In
questa region saranno visualizzati tutti i moduli visuali.


Il metodo CreateShell crea un'istanza della shell e setta anche il ViewModel.
Il metodo GetViewmodelAssemblies in questo caso restituisce semplice-
mente l'assembly corrente.


Infine, all'avvio dell'applicazione, si crea un'istanza della classe Application-
Controller al quale si passa un'istanza del container di IoC e si chiama il
metodo Run().


var controller =
   new ApplicationController(new UnityContainer());
controller.Run();


                                                                              62
Capitolo tre: L’applicazione

Questo capitolo parlerà di un'applicazione sviluppata utilizzando il toolkit
esposto nel capitolo due per realizzare un'interfaccia utente modulare in
Windows Presentation Foundation, basata sul pattern Model-View-ViewModel.


Obiettivo dell'applicazione

Lo scopo primario dell'applicazione è di gestire le palestre di Trieste. Il
committente deve gestire le palestre, assegnare i turni alle varie società che
vogliono utilizzarle, inviare dei custodi ad aprire e chiudere gli edifici, pagare i
custodi stessi per il loro lavoro e farsi pagare dalle società per l'utilizzo delle
palestre.


Architettura hardware e software dell'ambiente di produzione

L'ambiente di produzione è un dominio Active Directory formato da tre
macchine server con Windows Server 2008: la prima è un database server con
installato SQL Server 2008, la seconda è un web server con IIS 7.0. Il terzo server
è il domain controller della rete.




                                                                                 63
Inoltre ci sono una serie di macchine client con Windows Vista. Tutti i computer,
client e server, hanno installato il .NET Framework 3.5 Service pack 1.


Requisiti tecnici non funzionali

Il committente vuole un'applicazione desktop progettata in ambiente Microsoft.
In questo momento i client sono pochi, ma in un prossimo futuro è molto
probabile che aumentino. Quest'applicazione deve essere visibile e utilizzabile
solo all'interno della rete interna.


L'interfaccia utente dell'applicazione deve essere personalizzabile in funzione
dell'utente che utilizzerà l'applicazione, per esempio user o admin. Inoltre, deve
essere estendibile con nuove interfacce e moduli in futuro.


Architettura dell'applicazione

Visti l'architettura hardware e i requisiti tecnici non funzionali, l'applicazione
deve essere sviluppata utilizzando una serie di tecnologie.


   1. Deve avere un database SQL Server 2008.
   2. Deve essere sviluppata in .NET.
   3. Deve avere, se serve, un Web Server IIS 7.0


Il non sapere a priori quanti siano i client e il fatto che possano aumentare
molto, porta come conseguenza diretta che il server non deve avere alcuna
conoscenza dei client e soprattutto non deve fare nessuna assunzione su di essi.


Infine, l'interfaccia utente deve essere modulare: questo per migliorare
l'estendibilità e la personalizzazione.


Detto questo, l'applicazione è sviluppata secondo un'architettura three-tier,
ovvero con un database su una macchina, una parte di application server su un
secondo server e la parte client da installare su tutti i computer client della rete.



                                                                                   64
La comunicazione tra l'application server e i client avviene tramite servizi REST su
http.


Representational State Transfer – REST


REST è l’acronimo di Representational Transfer State, ed è un paradigma per la
realizzazione di applicazioni Web che permette la manipolazione delle risorse per
mezzo dei metodi GET, POST, PUT e DELETE del protocollo HTTP. Basando le
proprie fondamenta sul protocollo HTTP, il paradigma REST restringe il proprio
campo d’interesse alle applicazioni che utilizzano questo protocollo per la
comunicazione con altri sistemi.




                                                                                 65
Il termite REST è stato coniato nel 2000 da Roy Fielding, uno degli autori del
protocollo HTTP, per descrivere un sistema che permette di descrivere ed
identificare le risorse web8.


REST prevede che la scalabilità del Web e la crescita siano diretti risultati di pochi
principi chiave di progettazione:


          Lo stato dell'applicazione e le funzionalità sono divisi in Risorse WEB.
          Ogni risorsa è unica e indirizzabile usando sintassi universale per uso dei
           link ipertestuali.
          Tutte le risorse sono condivise come interfaccia uniforme per il
           trasferimento di stato tra client e risorse, questo consiste in:
               o un insieme vincolato di operazioni ben definite;
               o un insieme vincolato di contenuti, opzionalmente supportato da
                   codice on demand.
          Un protocollo che è:
               o Client-Server;
               o Stateless;
               o Cachable;
               o A livelli.


Application Server / Web Server

La parte di application server è formata dall'accesso ai dati utilizzando un O/RM,
nello specifico Entity Framework di Microsoft. La parte invece di creazione dei
servizi REST è creata utilizzando ADO.NET Data Services v1.0, sempre di
Microsoft.




8
    http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm


                                                                                      66
Object / Relational Mapping – O/RM


L'Object-Relational Mapping (ORM) è una tecnica di programmazione per
convertire dati fra RDBMS e linguaggi di programmazione orientati agli oggetti.
In buona sostanza, associa a ogni operazione ed elemento usato nella gestione
del database degli oggetti con adeguate proprietà e metodi, astraendo l'utilizzo
del database dal DBMS specifico.


I principali vantaggi nell'uso di questo sistema sono i seguenti.


      Il superamento (più o meno completo) dell'incompatibilità di fondo tra il
       progetto orientato agli oggetti ed il modello relazionale sul quale è basata
       la maggior parte degli attuali DBMS utilizzati; con una metafora legata al
       mondo dell'elettrotecnica, si parla in questo caso di disadattamento
       dell'impedenza (Impedence Mismatch) tra paradigma relazionale e ad-
       oggetti (object/relational impedance mismatch).
      Un'elevata portabilità rispetto alla tecnologia DMBS utilizzata: cambiando
       DBMS non devono essere riscritte le routine che implementano lo strato




                                                                                67
di persistenza; generalmente basta cambiare poche righe nella
         configurazione del prodotto per l'ORM utilizzato.
        Facilità d'uso, poiché non si è tenuti a utilizzare direttamente un
         linguaggio di query come l'SQL (che comunque rimane disponibile per
         eseguire compiti complessi o che richiedono un'elevata efficienza).


I prodotti per l'ORM in questo momento più diffusi, offrono spesso nativamente
funzionalità     che    altrimenti    andrebbero      realizzate   manualmente    dal
programmatore:


        caricamento automatico del grafo degli oggetti secondo i legami di asso-
         ciazione definiti a livello di linguaggio;
        gestione della concorrenza nell'accesso ai dati durante conversazioni;
        meccanismi di caching dei dati (con conseguente aumento delle presta-
         zioni dell'applicazione e riduzione del carico sul sistema RDBMS).


Client

Il client è sviluppato in Windows Presentation Foundation, utilizzando il toolkit
presentato nel capitolo due.


Questo tier è formato da tre livelli:


        Accesso ai dati e model.
        ViewModel.
        View.


La parte di accesso ai dati e Model è creata utilizzando la parte client di ADO.NET
Data Services; i ViewModel e le View si basano sul toolkit. Cosa fondamentale,
poiché si parla di MVVM, la View non conosce il Model.




                                                                                  68
Attori

In questa parte sono riassunti tutti gli attori, siano essi persone fisiche, società o
luoghi o oggetti.


Committente

Il committente è la società che gestisce le palestre situate
sul territorio della provincia di Trieste, siano esse
comunali o provinciali.


Società

La società è chi utilizza la palestra. Una società può
utilizzare più palestre diverse in orari differenti. Ogni
società deve pagare il committente per l’utilizzo della
palestra. Il costo dipende dalla categoria della palestra e




                                                                                   69
se la società è affiliata al CONI e/o socia del committente. A ogni società sono
associati dei turni.


Custode

Un custode apre e chiude una palestra. Alla fine di ogni mese
deve consegnare al committente un documento, nel quale si
attesta quante ore ha fatto.


Federazione

Una federazione sportiva è paragonabile a una società.
L'unica differenza è che una federazione fa parte del
CONI.


Istituto Comprensivo

Rappresenta l’ente che ha in gestione le
varie scuole del Comune (e le relative
palestre).
Ogni istituto può avere uno o più palestre.
Solo le palestre comunali hanno un istituto
comprensivo.


Palestra / Scuola

Una palestra può essere collegata a un istituto
comprensivo e può avere uno o più custodi.
Una scuola (o palestra) è o del comune o della
provincia.
Ogni palestra ha degli orari di disponibilità.




                                                                             70
Sala CONI

Una Sala CONI è paragonabile a una palestra. Ci
sono in totale tre sale, tutte di proprietà del
CONI.


È previsto un custode per la pulizia e la custodia.


Turno

Un turno in una palestra è un orario della settimana nel quale una determinata
società può svolgere attività in palestra. Un turno si deve riferire a un anno
sportivo.


Struttura dell'applicazione

Poiché questa tesi è incentrata sull'interfaccia utente, in quest'ultima parte del
capitolo si parlerà solamente del client.


Il client è un'applicazione modulare. La shell dell'applicazione è formata da due
region:


         Una ribbon region, nella quale sono contenuti tutti i comandi.
         Una tab region che contiene, all'interno di un TabControl, tutte le
          view che devono essere visualizzate.


L'immagine sottostante, per esempio, visualizza cinque view. Ognuna di esse è
uno UserControl che viene iniettato nella shell e naturalmente ogni view non ha
nessuna conoscenza né della shell, né delle altre view.




                                                                               71
Alcune user story

In quest'ultima parte del capitolo saranno elencate tre user story che producono
dei ViewModel molto diversi fra loro.


Una User Story9 è un requisito di un sistema software formato da una o due frasi
scritte il linguaggio corrente o nel linguaggio di business dell'utente. Le user story
sono utilizzate nelle metodologie di sviluppo agili10. Ogni user story è scritta
generalmente in un piccolo pezzo di carta, per assicurarsi che non cresca troppo.
Le user story dovrebbero essere scritte dai clienti di un progetto software, e
sono lo strumento principale per influenzare lo sviluppo del software.




9
    http://www.agilemodeling.com/artifacts/userStory.htm
10
     http://agilemanifesto.org/ e http://www.agilemodeling.com/


                                                                                   72
Le user story sotto elencate rappresentano la maggior parte dei ViewModel
possibili. La prima user story produrrà un ViewModel che è collegato a un solo
oggetto del modello per eseguire operazioni d'inserimento e modifica dei dati.
Anche il secondo è collegato a un solo oggetto del modello, ma esegue delle
operazioni di ricerca, e non visualizza tutti i dati presenti nell'oggetto di dominio.
Infine, la terza user story è collegata a molti oggetti del modello e visualizza
solamente i dati che interessano.


US1 – Inserimento e modifica di un custode

Un utente deve poter inserire un nuovo custode. Deve poter anche modificare i
dati inseriti.




                                                                                   73
L'immagine precedente mostra la View quando si deve inserire un nuovo
custode, quella sottostante, invece, visualizza la stessa View quando si deve
modificare un custode in precedenza inserito.




La prima cosa da notare è che la View è identica: indipendentemente
dall'operazione da eseguire, non cambia. L'unica differenza degna di nota è il
titolo del Tab che passa da "Nuovo" a "Carla Riboni". Capire in quale stato ci si
trova è compito del ViewModel, che fra le altre cose modifica anche il titolo della
View.


Questo è il primo tipo di ViewModel che capita spesso di trovare. Ecco le sue
caratteristiche:




                                                                                74
   Comanda più operazioni (inserimento e aggiornamento) su una sola
       View.
      È associato a un solo oggetto del modello.
      Espone tutti i campi dell'oggetto.


L'utilizzo principale di questo ViewModel è di eseguire operazioni d'inserimento
e aggiornamento di un singolo oggetto del dominio.




                                                                             75
Infatti, il toolkit contiene una classe base, ItemViewModelBase, che
incapsula tutte le caratteristiche fondamentali per eseguire queste operazioni.


US2 – Lista dei custodi

Un utente deve poter cercare un custode per cognome. Deve inoltre poter
modificare o eliminare il custode appena trovato.


Come si può vedere dall'immagine, la View è molto semplice: ha un'area di
ricerca e una zona nella quale sono visualizzati i risultati. Per eseguire la ricerca è
possibile cliccare sul bottone cerca o premere il tasto Invio dopo aver scritto
qualcosa nel box di ricerca. Facendo doppio clic su un custode, si apre una nuova
View per modificare i dati. Infine c'è un bottone per eliminare un custode.




                                                                                    76
Questo è il secondo tipo di ViewModel molto frequente. Ecco le sue
caratteristiche:


      Permette la ricerca su un unico oggetto del dominio.
      Visualizza i risultati in una lista.
      Permette operazioni di aggiornamento e cancellazione.
      Espone solo alcuni campi dell'oggetto.




                                                                77
Poiché anche questa serie di operazioni sono molto frequenti, esiste una classe
nel toolkit che raggruppa tutte queste funzioni di base: ListViewModelBase.


US3 – Creazione Turno

Si deve poter assegnare un turno a una società presso una palestra e il costo che
la società deve accollarsi per usufruire dello spazio. Contestualmente si deve
anche associare il custode e quanto deve essere dovuto allo stesso per il lavoro
svolto.




Questa View permette di scegliere ora, giorno e tipo di turno. A essi associa una
palestra, una società e un custode. È possibile fare una ricerca per trovare quello
che serve. In oltre, in automatico il sistema propone un costo per la palestra e
per il custode; costi che possono essere modificati se necessario.



                                                                                78
Questo è l'ultimo tipo di ViewModel. Sono ViewModel generici, che hanno solo
una cosa in comune fra loro: recuperano i dati da molti oggetti diversi del
modello e poi visualizzano solo quello che è necessario.


Per questi ViewModel non esiste una classe base e spesso sono molto complessi
da realizzare perché fanno molte cose diverse.


In questo caso specifico, queste sono le caratteristiche del ViewModel:


      Legge i dati da sei oggetti diversi.
      Esegue tre tipi di ricerche manuali.
      Esegue due ricerche automatiche.
      Permette la modifica delle ricerche automatiche.
      Inserisce il turno nel database.




                                                                          79
Conclusioni

In questa tesi si è parlato in dettaglio del pattern Model-View-ViewModel, delle
sue caratteristiche, vantaggi e svantaggi ed è stata anche proposta un'imple-
mentazione. Il pattern è molto giovane, non esistono ancora delle linee guida:
per adesso ci sono solamente una serie di problemi e la comunità degli svilup-
patori sta proponendo soluzioni diverse.


Anche Windows Presentation Foundation è giovane come tecnologia, e,
nonostante Microsoft sia spingendo molto, non è ancora entrata a far parte del
bagaglio di tutti gli sviluppatori di applicazioni desktop in ambiente Windows.
Tutto ciò ha una serie di cause, prima fra tutte, la curva di apprendimento di
WPF molto ripida. Inoltre, mancano ancora una serie di controlli che sono
presenti in Windows Forms e rendono ancora questa tecnologia appetibile.
Infine solo a metà luglio 2009 è uscito Expression Blend 3, il primo designer che
finalmente riesce ad esprimere tutte le potenzialità di WPF.


In rete si trovano tutta una serie di articoli e toolkit che implementano il pattern
MVVM. Tra i più famosi ci sono il MVVM Light Toolkit11 di Laurent Bugnion,
Cinch12 di Sasha Barber e Goldlight13 di Peter O'Hanlon. Questi tre toolkit hanno
implementazioni anche molto diverse fra loro, ma tutte risolvono i problemi che
sono stati affrontati in questa tesi.


Se invece, oltre ad affrontare i problemi associati al pattern MVVM, si vuole
anche creare un'applicazione modulare sfruttando la UI composition, il toolkit di
riferimento e Prism14 sviluppato dal team di Pattern and Practices di Microsoft.



11
  Per maggiori informazioni è possibile consultare il sito
http://www.galasoft.ch/mvvm/getstarted/
12
     Per maggiori informazioni è possibile consultare il sito http://cinch.codeplex.com/
13
     Per maggiori informazioni è possibile consultare il sito http://goldlight.codeplex.com/
14
     Per maggiori informazioni è possibile consultare il sito http://compositewpf.codeplex.com/


                                                                                                  80
La giovane età di WPF e del pattern MVVM possono far pensare che non sia
ancora il caso di sviluppare un'applicazione enterprise utilizzando queste
tecniche e tecnologie. Io ritengo invece, che nonostante tutti i problemi che la
loro giovinezza può portare, ha assolutamente senso sviluppare una nuova
applicazione in WPF in sinergia con il pattern MVVM. Questo perché la versatilità
e l'espandibilità, sia nel senso delle caratteristiche, che del layout
dell'applicazione portano un vantaggio intrinseco che supera notevolmente
qualsiasi problema.




                                                                              81
Appendice uno: come scrivere un'applicazione WPF

Quest'appendice contiene alcuni suggerimenti per scrivere applicazioni WPF.


XAML Conventions

XAML è XML e come tutti i formati XML ha una formattazione standard che è
bene utilizzare. Ci sono, però, un paio di cose da prendere in considerazione.


Utilizzare x:Name al posto di Name

Nonostante entrambi gli attributi facciano la stessa cosa, Name può essere
utilizzato solo per alcuni elementi, mentre x:Name funziona per tutti gli
elementi (eccetto gli elementi in un resource dictionary). Quando si guarda una
lunga lista di attributi di un elemento, gli attributi che cominciano con il prefisso
"x:" tendono ad essere più facili da vedere. Per rendere più facile trovare il
nome di un elemento è sempre meglio utilizzare x:Name.


Posizionare il primo attributo di un elemento nella riga sotto il nome
dell'elemento

Questa regola esiste perché la maggior parte degli editor XML indenta ogni riga
allo stesso livello del primo attributo. Per esempio:


<StackPanel DockPanel.Dock="Top"
            Orientation="Horizontal"
            Margin="3"
            Background="{TemplateBinding Background}">
    <Button Content="Hello"
            Background="{StaticResource
Brush_BtnBackground}"
            Width="300" />
    <ImageButton Source="{StaticResource Image_Copy}"
                 Width="16"
                 Height="16" />




                                                                                  82
WPF MVVM Toolkit
WPF MVVM Toolkit
WPF MVVM Toolkit
WPF MVVM Toolkit
WPF MVVM Toolkit
WPF MVVM Toolkit
WPF MVVM Toolkit
WPF MVVM Toolkit
WPF MVVM Toolkit
WPF MVVM Toolkit
WPF MVVM Toolkit

Weitere ähnliche Inhalte

Was ist angesagt?

Progetto e Realizzazione di un Software per la Rilevazione Automatica di Codi...
Progetto e Realizzazione di un Software per la Rilevazione Automatica di Codi...Progetto e Realizzazione di un Software per la Rilevazione Automatica di Codi...
Progetto e Realizzazione di un Software per la Rilevazione Automatica di Codi...danielenicassio
 
[MWT] Il web e la Pubblica Amministrazione
[MWT] Il web e la Pubblica Amministrazione[MWT] Il web e la Pubblica Amministrazione
[MWT] Il web e la Pubblica AmministrazioneSilvio D'Orazio
 
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...artemedea
 
Tesi di laurea Mariela Nasi
Tesi di laurea Mariela NasiTesi di laurea Mariela Nasi
Tesi di laurea Mariela NasiMariela Nasi
 

Was ist angesagt? (6)

Progetto e Realizzazione di un Software per la Rilevazione Automatica di Codi...
Progetto e Realizzazione di un Software per la Rilevazione Automatica di Codi...Progetto e Realizzazione di un Software per la Rilevazione Automatica di Codi...
Progetto e Realizzazione di un Software per la Rilevazione Automatica di Codi...
 
[MWT] Il web e la Pubblica Amministrazione
[MWT] Il web e la Pubblica Amministrazione[MWT] Il web e la Pubblica Amministrazione
[MWT] Il web e la Pubblica Amministrazione
 
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...
 
ECDL-modulo6
ECDL-modulo6ECDL-modulo6
ECDL-modulo6
 
Guida a pasw2013
Guida a pasw2013Guida a pasw2013
Guida a pasw2013
 
Tesi di laurea Mariela Nasi
Tesi di laurea Mariela NasiTesi di laurea Mariela Nasi
Tesi di laurea Mariela Nasi
 

Andere mochten auch

Silverlight m v-vm @ DotNetteria
Silverlight m v-vm @ DotNetteriaSilverlight m v-vm @ DotNetteria
Silverlight m v-vm @ DotNetteriaMauro Servienti
 
Writing apps for android with .net
Writing apps for android with .net Writing apps for android with .net
Writing apps for android with .net Leonardo Alario
 
UI Composition - Prism
UI Composition - PrismUI Composition - Prism
UI Composition - PrismDotNetMarche
 
Iter documentale per gli iscritti alla sezione E del RUI (collaborazione con ...
Iter documentale per gli iscritti alla sezione E del RUI (collaborazione con ...Iter documentale per gli iscritti alla sezione E del RUI (collaborazione con ...
Iter documentale per gli iscritti alla sezione E del RUI (collaborazione con ...Fabrizio Callarà
 
Model-View-ViewModel
Model-View-ViewModelModel-View-ViewModel
Model-View-ViewModelDotNetMarche
 

Andere mochten auch (10)

Introduzione WPF
Introduzione WPFIntroduzione WPF
Introduzione WPF
 
WPF 4 fun
WPF 4 funWPF 4 fun
WPF 4 fun
 
Silverlight m v-vm @ DotNetteria
Silverlight m v-vm @ DotNetteriaSilverlight m v-vm @ DotNetteria
Silverlight m v-vm @ DotNetteria
 
Writing apps for android with .net
Writing apps for android with .net Writing apps for android with .net
Writing apps for android with .net
 
UI Composition - Prism
UI Composition - PrismUI Composition - Prism
UI Composition - Prism
 
m-v-vm @ dotNetMarche
m-v-vm @ dotNetMarchem-v-vm @ dotNetMarche
m-v-vm @ dotNetMarche
 
Iter documentale per gli iscritti alla sezione E del RUI (collaborazione con ...
Iter documentale per gli iscritti alla sezione E del RUI (collaborazione con ...Iter documentale per gli iscritti alla sezione E del RUI (collaborazione con ...
Iter documentale per gli iscritti alla sezione E del RUI (collaborazione con ...
 
Model-View-ViewModel
Model-View-ViewModelModel-View-ViewModel
Model-View-ViewModel
 
WPF MVVM Toolkit
WPF MVVM ToolkitWPF MVVM Toolkit
WPF MVVM Toolkit
 
Anti Corruption Layers
Anti Corruption LayersAnti Corruption Layers
Anti Corruption Layers
 

Ähnlich wie WPF MVVM Toolkit

Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...
Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...
Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...Idriss Riouak
 
Cloud Computing e Modelli di Business
Cloud Computing e Modelli di Business Cloud Computing e Modelli di Business
Cloud Computing e Modelli di Business Andrea Cavicchini
 
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...Filippo Muscolino
 
Openfisca Managing Tool: a tool to manage fiscal sistems
Openfisca Managing Tool: a tool to manage fiscal sistemsOpenfisca Managing Tool: a tool to manage fiscal sistems
Openfisca Managing Tool: a tool to manage fiscal sistemsLorenzo Stacchio
 
Il tutorial di Python
Il tutorial di PythonIl tutorial di Python
Il tutorial di PythonAmmLibera AL
 
Estrazione automatica di informazioni da documenti cartacei: progetto e reali...
Estrazione automatica di informazioni da documenti cartacei: progetto e reali...Estrazione automatica di informazioni da documenti cartacei: progetto e reali...
Estrazione automatica di informazioni da documenti cartacei: progetto e reali...Luca Bressan
 
Tesi Triennale - Grid Credit System: un portale per la sostenibilità di COMPCHEM
Tesi Triennale - Grid Credit System: un portale per la sostenibilità di COMPCHEMTesi Triennale - Grid Credit System: un portale per la sostenibilità di COMPCHEM
Tesi Triennale - Grid Credit System: un portale per la sostenibilità di COMPCHEMDavide Ciambelli
 
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...daniel_zotti
 
Imparare c n.104
Imparare c  n.104Imparare c  n.104
Imparare c n.104Pi Libri
 
Implementazione di protocolli e simulatori MATLAB per lo sviluppo del livello...
Implementazione di protocolli e simulatori MATLAB per lo sviluppo del livello...Implementazione di protocolli e simulatori MATLAB per lo sviluppo del livello...
Implementazione di protocolli e simulatori MATLAB per lo sviluppo del livello...michael_mozzon
 
Piano Nazionale Scuola Digitale (risorse integrative)
Piano Nazionale Scuola Digitale (risorse integrative)Piano Nazionale Scuola Digitale (risorse integrative)
Piano Nazionale Scuola Digitale (risorse integrative)Ministry of Public Education
 
Tecniche per la rilevazione e correzione di errori nell'elaborazione automati...
Tecniche per la rilevazione e correzione di errori nell'elaborazione automati...Tecniche per la rilevazione e correzione di errori nell'elaborazione automati...
Tecniche per la rilevazione e correzione di errori nell'elaborazione automati...Matteo Gazzin
 
Profilazione utente in ambienti virtualizzati
Profilazione utente in ambienti virtualizzatiProfilazione utente in ambienti virtualizzati
Profilazione utente in ambienti virtualizzatiPietro Corona
 
Imparare asp.net 107
Imparare asp.net 107Imparare asp.net 107
Imparare asp.net 107Pi Libri
 
Analisi e realizzazione di uno strumento per la verifica di conformità su sis...
Analisi e realizzazione di uno strumento per la verifica di conformità su sis...Analisi e realizzazione di uno strumento per la verifica di conformità su sis...
Analisi e realizzazione di uno strumento per la verifica di conformità su sis...Davide Bravin
 

Ähnlich wie WPF MVVM Toolkit (20)

repairpdf_Oy51nCFX
repairpdf_Oy51nCFXrepairpdf_Oy51nCFX
repairpdf_Oy51nCFX
 
Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...
Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...
Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...
 
Cloud Computing e Modelli di Business
Cloud Computing e Modelli di Business Cloud Computing e Modelli di Business
Cloud Computing e Modelli di Business
 
Compas Project
Compas ProjectCompas Project
Compas Project
 
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
 
Openfisca Managing Tool: a tool to manage fiscal sistems
Openfisca Managing Tool: a tool to manage fiscal sistemsOpenfisca Managing Tool: a tool to manage fiscal sistems
Openfisca Managing Tool: a tool to manage fiscal sistems
 
Il tutorial di Python
Il tutorial di PythonIl tutorial di Python
Il tutorial di Python
 
Estrazione automatica di informazioni da documenti cartacei: progetto e reali...
Estrazione automatica di informazioni da documenti cartacei: progetto e reali...Estrazione automatica di informazioni da documenti cartacei: progetto e reali...
Estrazione automatica di informazioni da documenti cartacei: progetto e reali...
 
Tesi Triennale - Grid Credit System: un portale per la sostenibilità di COMPCHEM
Tesi Triennale - Grid Credit System: un portale per la sostenibilità di COMPCHEMTesi Triennale - Grid Credit System: un portale per la sostenibilità di COMPCHEM
Tesi Triennale - Grid Credit System: un portale per la sostenibilità di COMPCHEM
 
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...
 
Imparare c n.104
Imparare c  n.104Imparare c  n.104
Imparare c n.104
 
Implementazione di protocolli e simulatori MATLAB per lo sviluppo del livello...
Implementazione di protocolli e simulatori MATLAB per lo sviluppo del livello...Implementazione di protocolli e simulatori MATLAB per lo sviluppo del livello...
Implementazione di protocolli e simulatori MATLAB per lo sviluppo del livello...
 
Piano Nazionale Scuola Digitale (risorse integrative)
Piano Nazionale Scuola Digitale (risorse integrative)Piano Nazionale Scuola Digitale (risorse integrative)
Piano Nazionale Scuola Digitale (risorse integrative)
 
Corso java
Corso javaCorso java
Corso java
 
Tecniche per la rilevazione e correzione di errori nell'elaborazione automati...
Tecniche per la rilevazione e correzione di errori nell'elaborazione automati...Tecniche per la rilevazione e correzione di errori nell'elaborazione automati...
Tecniche per la rilevazione e correzione di errori nell'elaborazione automati...
 
TesiEtta
TesiEttaTesiEtta
TesiEtta
 
Profilazione utente in ambienti virtualizzati
Profilazione utente in ambienti virtualizzatiProfilazione utente in ambienti virtualizzati
Profilazione utente in ambienti virtualizzati
 
Imparare asp.net 107
Imparare asp.net 107Imparare asp.net 107
Imparare asp.net 107
 
Analisi e realizzazione di uno strumento per la verifica di conformità su sis...
Analisi e realizzazione di uno strumento per la verifica di conformità su sis...Analisi e realizzazione di uno strumento per la verifica di conformità su sis...
Analisi e realizzazione di uno strumento per la verifica di conformità su sis...
 
Manuale sicurnet duvri
Manuale sicurnet duvriManuale sicurnet duvri
Manuale sicurnet duvri
 

Kürzlich hochgeladen

Daniele Lunassi, CEO & Head of Design @Eye Studios – “Creare prodotti e servi...
Daniele Lunassi, CEO & Head of Design @Eye Studios – “Creare prodotti e servi...Daniele Lunassi, CEO & Head of Design @Eye Studios – “Creare prodotti e servi...
Daniele Lunassi, CEO & Head of Design @Eye Studios – “Creare prodotti e servi...Associazione Digital Days
 
Gabriele Mittica, CEO @Corley Cloud – “Come creare un’azienda “nativa in clou...
Gabriele Mittica, CEO @Corley Cloud – “Come creare un’azienda “nativa in clou...Gabriele Mittica, CEO @Corley Cloud – “Come creare un’azienda “nativa in clou...
Gabriele Mittica, CEO @Corley Cloud – “Come creare un’azienda “nativa in clou...Associazione Digital Days
 
Alessio Mazzotti, Aaron Brancotti; Writer, Screenwriter, Director, UX, Autore...
Alessio Mazzotti, Aaron Brancotti; Writer, Screenwriter, Director, UX, Autore...Alessio Mazzotti, Aaron Brancotti; Writer, Screenwriter, Director, UX, Autore...
Alessio Mazzotti, Aaron Brancotti; Writer, Screenwriter, Director, UX, Autore...Associazione Digital Days
 
Edoardo Di Pietro – “Virtual Influencer vs Umano: Rubiamo il lavoro all’AI”
Edoardo Di Pietro – “Virtual Influencer vs Umano: Rubiamo il lavoro all’AI”Edoardo Di Pietro – “Virtual Influencer vs Umano: Rubiamo il lavoro all’AI”
Edoardo Di Pietro – “Virtual Influencer vs Umano: Rubiamo il lavoro all’AI”Associazione Digital Days
 
Alessandro Nasi, COO @Djungle Studio – “Cosa delegheresti alla copia di te st...
Alessandro Nasi, COO @Djungle Studio – “Cosa delegheresti alla copia di te st...Alessandro Nasi, COO @Djungle Studio – “Cosa delegheresti alla copia di te st...
Alessandro Nasi, COO @Djungle Studio – “Cosa delegheresti alla copia di te st...Associazione Digital Days
 
Federico Bottino, Lead Venture Builder – “Riflessioni sull’Innovazione: La Cu...
Federico Bottino, Lead Venture Builder – “Riflessioni sull’Innovazione: La Cu...Federico Bottino, Lead Venture Builder – “Riflessioni sull’Innovazione: La Cu...
Federico Bottino, Lead Venture Builder – “Riflessioni sull’Innovazione: La Cu...Associazione Digital Days
 
Programma Biennale Tecnologia 2024 Torino
Programma Biennale Tecnologia 2024 TorinoProgramma Biennale Tecnologia 2024 Torino
Programma Biennale Tecnologia 2024 TorinoQuotidiano Piemontese
 
Mael Chiabrera, Software Developer; Viola Bongini, Digital Experience Designe...
Mael Chiabrera, Software Developer; Viola Bongini, Digital Experience Designe...Mael Chiabrera, Software Developer; Viola Bongini, Digital Experience Designe...
Mael Chiabrera, Software Developer; Viola Bongini, Digital Experience Designe...Associazione Digital Days
 
Luigi Di Carlo, CEO & Founder @Evometrika srl – “Ruolo della computer vision ...
Luigi Di Carlo, CEO & Founder @Evometrika srl – “Ruolo della computer vision ...Luigi Di Carlo, CEO & Founder @Evometrika srl – “Ruolo della computer vision ...
Luigi Di Carlo, CEO & Founder @Evometrika srl – “Ruolo della computer vision ...Associazione Digital Days
 

Kürzlich hochgeladen (9)

Daniele Lunassi, CEO & Head of Design @Eye Studios – “Creare prodotti e servi...
Daniele Lunassi, CEO & Head of Design @Eye Studios – “Creare prodotti e servi...Daniele Lunassi, CEO & Head of Design @Eye Studios – “Creare prodotti e servi...
Daniele Lunassi, CEO & Head of Design @Eye Studios – “Creare prodotti e servi...
 
Gabriele Mittica, CEO @Corley Cloud – “Come creare un’azienda “nativa in clou...
Gabriele Mittica, CEO @Corley Cloud – “Come creare un’azienda “nativa in clou...Gabriele Mittica, CEO @Corley Cloud – “Come creare un’azienda “nativa in clou...
Gabriele Mittica, CEO @Corley Cloud – “Come creare un’azienda “nativa in clou...
 
Alessio Mazzotti, Aaron Brancotti; Writer, Screenwriter, Director, UX, Autore...
Alessio Mazzotti, Aaron Brancotti; Writer, Screenwriter, Director, UX, Autore...Alessio Mazzotti, Aaron Brancotti; Writer, Screenwriter, Director, UX, Autore...
Alessio Mazzotti, Aaron Brancotti; Writer, Screenwriter, Director, UX, Autore...
 
Edoardo Di Pietro – “Virtual Influencer vs Umano: Rubiamo il lavoro all’AI”
Edoardo Di Pietro – “Virtual Influencer vs Umano: Rubiamo il lavoro all’AI”Edoardo Di Pietro – “Virtual Influencer vs Umano: Rubiamo il lavoro all’AI”
Edoardo Di Pietro – “Virtual Influencer vs Umano: Rubiamo il lavoro all’AI”
 
Alessandro Nasi, COO @Djungle Studio – “Cosa delegheresti alla copia di te st...
Alessandro Nasi, COO @Djungle Studio – “Cosa delegheresti alla copia di te st...Alessandro Nasi, COO @Djungle Studio – “Cosa delegheresti alla copia di te st...
Alessandro Nasi, COO @Djungle Studio – “Cosa delegheresti alla copia di te st...
 
Federico Bottino, Lead Venture Builder – “Riflessioni sull’Innovazione: La Cu...
Federico Bottino, Lead Venture Builder – “Riflessioni sull’Innovazione: La Cu...Federico Bottino, Lead Venture Builder – “Riflessioni sull’Innovazione: La Cu...
Federico Bottino, Lead Venture Builder – “Riflessioni sull’Innovazione: La Cu...
 
Programma Biennale Tecnologia 2024 Torino
Programma Biennale Tecnologia 2024 TorinoProgramma Biennale Tecnologia 2024 Torino
Programma Biennale Tecnologia 2024 Torino
 
Mael Chiabrera, Software Developer; Viola Bongini, Digital Experience Designe...
Mael Chiabrera, Software Developer; Viola Bongini, Digital Experience Designe...Mael Chiabrera, Software Developer; Viola Bongini, Digital Experience Designe...
Mael Chiabrera, Software Developer; Viola Bongini, Digital Experience Designe...
 
Luigi Di Carlo, CEO & Founder @Evometrika srl – “Ruolo della computer vision ...
Luigi Di Carlo, CEO & Founder @Evometrika srl – “Ruolo della computer vision ...Luigi Di Carlo, CEO & Founder @Evometrika srl – “Ruolo della computer vision ...
Luigi Di Carlo, CEO & Founder @Evometrika srl – “Ruolo della computer vision ...
 

WPF MVVM Toolkit

  • 1. UNIVERSITA' DEGLI STUDI DI TRIESTE FACOLTA' DI INGEGNERIA CORSO DI LAUREA IN INGEGNERIA INFORMATICA Separazione dei ruoli tra Designer e Developer nello sviluppo di applicazioni Desktop: uso di WPF e del pattern Model-View-ViewModel Laureando: Relatore: Alessandro Andreose' Chiar.mo Prof. Maurizio Fermeglia Anno Accademico 2008 – 2009
  • 2. Sommario Introduzione............................................................................................................ 6 Un po’ di storia .................................................................................................... 7 Capitolo uno ............................................................................................................ 8 Windows Presentation Foundation .................................................................... 8 Perché WPF ..................................................................................................... 9 XAML ............................................................................................................. 10 Layout e controlli .......................................................................................... 11 Logical Tree e Visual Tree ............................................................................. 11 Command ...................................................................................................... 13 Routed Event................................................................................................. 14 Dependency Property ................................................................................... 14 Attached Property (o Attached Behaviour) .................................................. 15 Object Resources e Resource Dictionary ...................................................... 15 Data Binding .................................................................................................. 16 Data Template............................................................................................... 18 Separazione dei ruoli .................................................................................... 19 Model – View – ViewModel .............................................................................. 20 Model ............................................................................................................ 21 ViewModel .................................................................................................... 21 View .............................................................................................................. 22 Separazione dei ruoli .................................................................................... 23 Capitolo due: Il toolkit .......................................................................................... 24 INotifyPropertyChanged ................................................................................... 24 2
  • 3. Delegate Command .......................................................................................... 27 Scrivere il codice del comando nel ViewModel ............................................ 29 Sapere quando un comando può essere eseguito ....................................... 30 Aggiungere degli shortcut ............................................................................. 31 Associare il comando al controllo nel codice XAML e minimizzare la scrittura di codice ripetitivo ........................................................................................ 32 Come si usa ................................................................................................... 36 IMessageBroker ................................................................................................ 36 UI Composition ................................................................................................. 48 IRegion .......................................................................................................... 49 IModule ......................................................................................................... 54 ViewModel .................................................................................................... 57 Associare una region al ViewModel ............................................................. 58 Application Controller ................................................................................... 60 Capitolo tre: L’applicazione .................................................................................. 63 Obiettivo dell'applicazione ............................................................................... 63 Architettura hardware e software dell'ambiente di produzione ..................... 63 Requisiti tecnici non funzionali ......................................................................... 64 Architettura dell'applicazione........................................................................... 64 Application Server / Web Server .................................................................. 66 Client ............................................................................................................. 68 Attori ................................................................................................................. 69 Struttura dell'applicazione ................................................................................ 71 Alcune user story .............................................................................................. 72 3
  • 4. Conclusioni ............................................................................................................ 80 Appendice uno: come scrivere un'applicazione WPF ........................................... 82 XAML Conventions ............................................................................................ 82 Utilizzare x:Name al posto di Name ............................................................ 82 Posizionare il primo attributo di un elemento nella riga sotto il nome dell'elemento ................................................................................................ 82 Preferire StaticResource a DynamicResource .............................................. 83 Naming Convention per gli elementi ............................................................ 84 Unire le risorse a livello di Applicazione ....................................................... 84 Organizzazione delle risorse ............................................................................. 84 Strutturare le risorse ..................................................................................... 84 Naming Convention ...................................................................................... 86 Appendice due: come disegnare una Window in WPF ........................................ 87 Grid.................................................................................................................... 88 Bibliografia ............................................................................................................ 92 Web ................................................................................................................... 93 4
  • 5. 5
  • 6. Introduzione L’obiettivo primario di questa tesi è lo sviluppo di un toolkit per scrivere applicazioni desktop in WPF applicando il pattern Model-View-ViewModel (MVVM). Questo toolkit permette sia di non dover ogni volta riscrivere quelle parti di codice che naturalmente si ripetono in ogni progetto, sia di avere del codice testato, aggiornato e facile da manutenere. Lo sviluppo di applicazioni senza l’utilizzo di pattern come Model View Controller (MVC)1, Presentation Model, Model-View-ViewModel e derivati soffre di tre gravi problemi:  Non c’è separazione dei ruoli tra Designer e Developer.  Non c’è una gestione dello stato dei controlli.  È molto difficile testare le funzionalità legate alla user interface. Tutti questi pattern cercano di risolvere questi tre problemi, però hanno un costo: si deve scrivere molto più codice che poi deve essere manutenuto. Inoltre, l’utente di oggi si aspetta di utilizzare applicazioni che abbiano un’interfaccia utente accattivante e user-friendly. Ora più che mai c’è bisogno che figure diverse partecipino alla realizzazione dell’intero progetto software. Per quanto riguarda lo sviluppo della interfaccia utente (user interface, UI) sono due i ruoli fondamentali: il developer e il designer. Il primo è in grado di creare tutta la logica, il secondo invece deve curare l'aspetto grafico. Il problema principale di questo approccio è che developer e designer devono lavorare assieme, ma non devono, per quanto possibile, intralciarsi a vicenda. Negli anni sono state sviluppate varie tecniche, sia architetturali, che tecnologiche. Nel mondo web ad esempio la tecnologia dei Cascading Style Sheet 1 http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller 6
  • 7. (CSS) aiuta a suddividere i compiti delle due figure professionali. Dal punto di vista architetturale esistono modelli di riferimento per la scrittura del codice (pattern) come il Model View Controller, che separa il codice di visualizzazione (View) dalle entità di business (Model): per comunicare hanno bisogno di un Controller. Ed è proprio in quest’ultimo che viene inserita la maggior parte della logica. La view ha poco codice (sperabilmente nessuno) e la maggior parte del lavoro lo deve fare il designer. Quando si sviluppano applicazioni con WPF, non è obbligatorio implementare il pattern MVVM e gli altri principi che saranno spiegati in questa tesi. Tutti questi principi, però, definiscono una metodologia di sviluppo che aiuta a separare il ruolo del developer da quello del designer. Il toolkit, insomma, è la formalizzazione di una metodologia che il developer applicherà ripetutamente nelle sue applicazioni desktop con WPF. Un po’ di storia La separazione tra l’interfaccia utente (view) e il modello di business (model) non è una nuova idea dello sviluppo software: ha circa trenta anni2. Recentemente è rinato l’interesse per le architetture view-model, soprattutto a causa della crescita della complessità dei sistemi software moderni e della necessità di visualizzare un prodotto software su diverse interfacce utente (desktop, web, …) senza per questo motivo dover riscrivere l’intera applicazione. Il pattern Model-View-ViewModel è una variazione del pattern Model View Controller, nato verso la fine degli anni settanta come framework sviluppato in Smalltalk, e presentato come pattern da Martin Fowler nel suo libro Patterns of Enterprise Application Architecture3. 2 http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller#History 3 http://martinfowler.com/books.html#eaa 7
  • 8. Il pattern MVVM è stato presentato per la prima volta al pubblico nell’ottobre del 2005 in un post4 sul blog di John Gossman, ed è stato utilizzato per la prima volta in Microsoft per sviluppare Expression Blend, primo software Microsoft a essere sviluppato interamente in Windows Presentation Foundation (WPF) ed orientato alla figura del designer. Capitolo uno Windows Presentation Foundation Niente è più importante della user experience di un'applicazione. Mentre molti professionisti sono più interessati a come lavora un'applicazione, gli utenti che utilizzano il software si preoccupano dell'interfaccia utente. L'interfaccia di un'applicazione compone la stragrande maggioranza della user experience di un software. Per molti utenti l'interfaccia e l'esperienza sono l'applicazione. Se si produce una buona esperienza utente attraverso l'interfaccia, si aumenta la produttività del software stesso. Windows Presentation Foundation (WPF) è una parte del Framework .NET 3.0 uscito nel novembre 2006, aggiornato in seguito con il Framework 3.5 nel novembre 2007 e con il Framework 3.5 SP1 nell’agosto 2008. WPF è la nuova tecnologia Microsoft .NET per sviluppare l’interfaccia utente di un’applicazione desktop ed è l’unica che viene aggiornata con l’aggiunta di nuove feature. Si possono creare interfacce utente (UI) sia scrivendo codice managed, sia utilizzando un linguaggio di markup XAML (eXtensible Application Markup Language), una sintassi Xml per un approccio "dichiarativo", più da designer. È possibile anche utilizzare una tecnica mista che è quella più comunemente utilizzata. Per ogni window o user control di solito si hanno 2 file: un file .xaml 4 http://blogs.msdn.com/johngossman/archive/2005/10/08/478683.aspx 8
  • 9. che contiene il codice XAML e un file .xaml.cs (o .xaml.vb) che contiene il codice associato (nel linguaggio scelto). La prima parte di questo capitolo spiega le caratteristiche principali di WPF e l’ausilio che WPF può dare per separare il lavoro del designer da quello dello sviluppatore. La seconda parte del capitolo, invece, introduce il pattern Model – View – ViewModel e come si collega a WPF. Perché WPF Lo scopo di Windows Presentation Foundation è di facilitare lo sviluppo dell'interfac-cia utente, per migliorare la user experience. Utilizzando WPF sviluppatori e designer possono creare interfacce che incorporino documenti, video, immagini, grafica 2D e 3D, animazioni e molto altro. Piattaforma unificata Prima di WPF la creazione di interfacce utente seguendo i requisiti appena citati, richiedeva l'utilizzo di molte tecnologie differenti. Questo di per sé non è un problema. 9
  • 10. Il problema è che molto spesso uno sviluppatore non conosce tutte queste tecnologie e quindi non le usa. Questo comportamento ha come conseguenza diretta una minore user experience che va tutta a danno dell'utente finale. WPF unifica tutte queste aree in un'unica tecnologia. Gli sviluppatori possono creare applicazioni accattivanti conoscendo a fondo una singola tecnologia. XAML XAML è un linguaggio di markup utilizzato per istanziare oggetti .NET. Nonostante XAML sia una tecnologia che può essere applicata a differenti domini di problemi, la sua principale applicazione è di costruire interfacce utente in WPF: i documenti XAML definiscono la creazione e il posizionamento di pannelli, bottoni e controlli che vivono nelle window di un’applicazione WPF. Lo standard XAML si fonda sull'applicazione di alcune semplici regole di base:  Ogni elemento in un documento XAML mappa un’istanza di una classe .NET. Il nome del tag di ogni elemento è identico al nome della classe.  Come per tutti i documenti XML, è possibile inserire un elemento all’interno di un altro.  È possibile settare le proprietà di ogni classe attraverso gli attributi. Comunque, in certe situazioni un attributo non è sufficientemente potente. In questi casi è possibile inserire dei tag all’interno dell’elemento con una sintassi speciale. L'utilizzo di codice XAML per creare interfacce utente permette a developer e designer di lavorare sullo stesso file utilizzando strumenti diversi. Questo porta ad una maggiore interazione tra le due figure professionali. 10
  • 11. Layout e controlli Per organizzare le varie parti di un'interfaccia, WPF utilizza dei contenitori per il layout. Ogni panel può contenere dei figli, inclusi dei controlli come bottoni e blocchi di testo o altri panel. Tutto ciò, crea una struttura ad albero che ha come padre la window che contiene un panel che a sua volta è il padre di tutti gli altri controlli. Logical Tree e Visual Tree La struttura ad albero principale in WPF è la struttura ad albero dell'elemento. In Windows Presentation Foundation, sono disponibili due modi di elaborazione e concettualizzazione della struttura ad albero dell'elemento: come albero logico e come struttura ad albero visuale. Le distinzioni tra albero logico e struttura ad albero visuale non sono sempre necessariamente importanti. Tuttavia possono talvolta causare problemi ad alcuni sottosistemi WPF e influire sulle scelte fatte nel markup o nel codice. Logical Tree In WPF è possibile aggiungere contenuti agli elementi utilizzando le proprietà. Ad esempio, è possibile aggiungere elementi a un controllo ListBox utilizzando la proprietà Items. In questo modo, gli elementi vengono collocati nell'oggetto ItemCollection del controllo ListBox. Per aggiungere elementi a un 11
  • 12. oggetto DockPanel è necessario utilizzare la relativa proprietà Children. In questo caso, gli elementi vengono aggiunti all'oggetto UIElementCol- lection dell'oggetto DockPanel. Grazie all'albero logico, i modelli di contenuto possono scorrere prontamente i possibili elementi figlio e quindi essere estendibili. Inoltre, l'albero logico fornisce un framework per alcune notifiche, ad esempio quando tutti gli elementi dell'albero logico sono caricati. Ancora, i riferimenti di risorsa5 vengono risolti cercando verso l'alto nell'albero logico gli insiemi di risorse relativi all'elemento di richiesta iniziale e succes- sivamente gli elementi padre. Visual Tree Nella struttura ad albero visuale viene descritta la struttura degli elementi visivi rappresentati dalla classe base Visual. Un'esposizione della struttura ad albero visuale come parte della programmazione di applicazioni WPF convenzionale è necessario, poiché su di esso è implementato il meccanismo degli eventi, 5 Per avere maggiori informazioni sulle risorse, vedere il paragrafo Object Resources e Resource Dictionary, presente in questo capitolo. 12
  • 13. chiamato "routed events"6. Gli "eventi instradati" percorrono la struttura dell'albero visuale e non dell'albero logico. Command In un'applicazione reale, le funzionalità sono divise in attività (task) di alto livello. Questi task possono essere attivati da varie azioni differenti e da molti elementi dell’interfaccia utente, inclusi i menù, i bottoni, le scorciatoie da tastiera e le toolbar. WPF permette di definire questi task, conosciuti come command, ed associare ad essi i controlli, così non c’è bisogno di scrivere codice ripetitivo (code bloat) quando si sottoscrive un evento. Ancora più importante, i comandi controllano lo stato dell’interfaccia utente e automaticamente disabilitano i controlli a essi collegati quando il task non è permesso. L'esecuzione di comandi è un meccanismo di input di WPF che fornisce la gestione di input a un livello semantico maggiore rispetto all'input del dispositivo. Le operazioni Copia, Taglia e Incolla presenti in molte applicazioni sono esempi di comandi. La differenza tra i comandi e un semplice gestore eventi associato a un pulsante o a un timer consiste nel fatto che i comandi separano la semantica e la creazio- 6 Per avere maggiori dettagli sui ruoted event, vedere il paragrafo Routed Event, presente in questo capitolo. 13
  • 14. ne di un'azione dalla relativa logica. In questo modo, più codici sorgente diversi possono richiamare la stessa logica di comando che pertanto può essere personalizzata per obiettivi differenti. Routed Event Ci sono tre tipi di routed event: direct, bubbling e tunneling. I Direct Event sono eventi che possono essere intercettati solo dall'elemento che ha creato l'evento. I Bubbling Event sono eventi che viaggiano verso l'alto attraverso il visual tree e possono essere intercettati da ogni parent dell'elemento sorgente. Infine, i Tunneling Event sono eventi che viaggiano verso il basso attraverso il visual tree e possono essere intercettati da ogni child dell'elemento sorgente. Dependency Property Le Dependency Properties evolvono il concetto standard di proprietà in .NET e sono caratteristiche di WPF. Utilizzano lo storage in maniera più efficiente e supportano alcune feature di alto livello come per esempio la notifica quando cambia il valore e la capacità di propagare i valori di default verso il basso per tutto l’albero di elementi. Le dependency property sono alla base di molte caratteristiche di WPF, come il data binding e gli stili. public string MyProperty { get { return (string)GetValue(MyPropProperty); } set { SetValue(MyPropProperty, value); } } public static readonly DependencyProperty MyPropProperty = DependencyProperty.Register("MyProp", typeof(string), typeof(MyClass), new PropertyMetadata("")); Dal codice si può osservare che una property, anziché memorizzare il valore in un 14
  • 15. campo privato, definisce una variabile di tipo DependencyProperty. Per conven- zione, la DependencyProperty termina con la parola "Property". Il metodo Register() accetta una classe di tipo PropertyMetadata che, fra le altre cose, setta il valore di default della property. Attached Property (o Attached Behaviour) Le attached properties sono delle dependency properties particolari che possono essere agganciate ad un oggetto. La cosa importante è che non devono essere definite nella classe alla quale devono essere aggiunte. Le attached properties sono particolarmente utili per agganciare i command anche agli oggetti WPF e agli eventi che non supportano nativamente i Command. Per esempio la TextBox non accetta nativamente un comando per l’evento KeyPress: tramite un’attached property si può aggiungere. Può essere utile, anche se si utilizza una TextBox come campo di ricerca: è più comodo per l’utente scrivere il testo e premere invio senza dover prima spostare il focus su un bottone “Cerca”. Un altro esempio potrebbe essere il doppio clic su una ListView: è utile per esempio per visualizzare il dettaglio di un oggetto visualizzato in una lista. WPF non lo supporta nativamente, ma tramite le attached property si può aggiungere questa caratteristica. Object Resources e Resource Dictionary WPF introduce un nuovo sistema di risorse che si integrano strettamente con XAML. Questo sistema permette di definire risorse in molti posti all’interno del markup (in un controllo specifico, in una window o per tutta l’applicazione) e riutilizzarle facilmente. Le Object Resource hanno un numero importante di benefici: 15
  • 16. Efficienza. Le risorse permettono di definire un oggetto una volta sola e utilizzarlo in molti posti nel markup.  Manutenibilità. Le risorse permettono di avere pochi dettagli di formattazione legati direttamente al controllo (come per esempio la dimensione del font) e di spostarli in un luogo centrale dove è più facile cambiarli. È l’equivalente XAML di creare costanti nel codice.  Adattabilità. Se certe informazioni sono separate dal resto dell’applicazione e sono salvate in una resource section, è possibile modificarle dinamicamente. Ogni elemento include una proprietà Resources di tipo Dictionary, in cui è possibile mettere le risorse. Questa collection può contenere qualsiasi tipo di oggetto .NET. Ogni oggetto è indicizzato da una stringa. Nonostante ogni oggetto ha una proprietà Resources, è più comune inserire le risorse a livello di Window. Questo perché ogni oggetto figlio condivide le risorse esposte dal padre. Se si vogliono condividere le risorse, è possibile creare un Resource Dictionary. Un resource dictionary è un documento XAML che fa da contenitore per le risorse che si vogliono utilizzare. La cosa importante delle risorse è che possono essere create e recuperate direttamente tramite markup. Le risorse possono essere create direttamente nel file xaml di una view, oppure in un file esterno. Per fare un paragone si possono immaginare i file di risorse come un file CSS, nel quale sono salvati vari stili. Data Binding Il Data Binding è la possibilità di collegare un dato direttamente all’interfaccia utente, senza doversi preoccupare di aggiornare la UI quando cambia il dato e viceversa. In WPF non c’è limite a cosa sia un “dato”: può essere una stringa, un 16
  • 17. numero, un oggetto di business più o meno complesso, un altro controllo utente, eccetera. L’unico accorgimento è che si può fare il collegamento solo delle proprietà e non dei campi pubblici. <TextBlock Text="{Binding Person, Path=FirstName}" /> Il data binding in WPF può essere di vari tipi:  "OneWay" indica che l'interfaccia utente è aggiornata quando l'oggetto cambia.  "OneWayToSource" è aggiornato l'oggetto quando l'interfaccia utente cambia.  "TwoWay" l'aggiornamento è bidirezionale.  "OneTime" è visualizzato il valore quando l’interfaccia utente viene visualizzata per la prima volta e poi non viene più aggiornato. Un oggetto (source) collegato tramite data binding a una proprietà di un control- lo (target) che risiede nell’interfaccia utente per funzionare correttamente deve avere una serie di caratteristiche:  La proprietà dell’oggetto target deve essere una Dependency Property;  Se si tratta di TwoWay o OneWayToSource allora la proprietà da mettere in binding deve avere definito anche il setter;  Se si tratta di OneWay o TwoWay, source deve implementare INotifyPropertyChanged o INotifyCollectionChanged, 17
  • 18. rispettivamente se la proprietà restituisce un singolo valore o una lista di oggetti;  Se si vuole supportare la gestione degli errori, una soluzione possibile è far implementare a source IDataErrorInfo, oppure si crea una classe che eredita da ValidationRule e si associa al binding che interessa. È possibile che si debba fare una conversione tra il valore della proprietà del source e la proprietà del target. Questo può accadere per esempio perché da una parte si ha una stringa, ma dall’altra parte si vuole avere un bool, oppure perché si vuole formattare in un determinato modo una stringa. Per fare questo esistono i ValueConverter, cioè degli oggetti che implementano l’interfaccia IValueConverter. Data Template I Data Template sono dei descrittori: descrivono come deve essere visualizzato un determinato oggetto. Per esempio si può decidere che un oggetto Person sia visualizzato come una Label contenente la stringa Cognome, Nome. A questo punto se in una listbox si inseriscono degli oggetti Person, verranno visualizzate una lista di Label contente la stringa precedentemente creata. Unendo i concetti di Data Template e risorse, è possibile creare una serie di resource dictionary in file separati con i vari Data Template. In questo modo si definisce una volta sola il template per un determinato oggetto. 18
  • 19. Separazione dei ruoli Con il solo utilizzo di tutte le caratteristiche di WPF, designer e developer possono lavorare assieme senza intralciarsi troppo. Il file con il code behind può essere ridotto al minimo grazie all’utilizzo del Data Binding e dei command. Sia il designer che il developer lavorano sul codice XAML: l’unica cosa che deve fare il designer per agevolare il lavoro del developer è quello di associare (o non modificare) gli handler d’evento ai controlli ed eventi corretti. Sarà poi il developer a scrivere il codice all’interno degli handler nel code behind. Inoltre dovrà associare i controlli tramite data binding ai command e agli oggetti corretti. Grazie agli stili, i resource dictionary e ai template il designer può modificare l‘interfaccia utente modificando poco il file xaml. Il developer dal canto suo deve evitare di modificare gli stili associati ai vari controlli nel file xaml. 19
  • 20. Model – View – ViewModel Il pattern Model-View-ViewModel (MVVM) ha come scopo principale quello di separare l’interfaccia utente dall’implementazione. È stato creato pensando principalmente al Data Binding, in modo che sia possibile minimizzare (fino ad annullare) il codice nel code behind della View. Tutto il codice applicativo si trova nel ViewModel, che fa da ponte con il Model. Il designer lavora solo sulla View, il developer su ViewModel. Per collegare View e ViewModel si utilizzano Data Binding e i Command. Lo scopo del pattern MVVM non è quello di non scrivere codice nel code behind della View e nemmeno quello di scrivere migliaia di righe di codice. Ci sono alcune cose che è corretto scrivere nella View, come per esempio la modifica del focus di un controllo. Quest'operazione è possibile farla sia nella View che nel ViewModel: il posto corretto è nella View (ed è molto più semplice). Inoltre se si scrive il codice per gestire il focus nel ViewModel si crea una dipendenza del ViewModel alla View: questo, però è un esempio di cosa NON fare, poiché questo non è un compito del ViewModel, ma della View. 20
  • 21. La cosa importante quando ci si chiede dove inserire una funzionalità è di prendere in considerazione anche il code behind delle View e non escluderlo a priori perché si sta utilizzando il pattern Model-View-ViewModel. Model Il model rappresenta il dominio applicativo, gli oggetti di business dell’applica- zione, i dati. È completamente indipendente dall’interfaccia utente. Per esempio la classe Person di seguito riportata è un esempio di classe appartenente al modello ed ha due proprietà, FirstName e LastName. ViewModel Il ViewModel (VM) è un “Model of a View”. Può essere immaginato come un’astrazione della view, ma nello stesso tempo è anche una specializzazione del model che la view utilizza per il data binding. Il VM è particolarmente utile quando il Model è complesso, o è già esistente e non si può modificare, oppure quando i tipi di dato del model non sono facilmente collegabili alla view. Come regola, ogni User Story ha il suo ViewModel. Mentre non è detto che ci sia una corrispondenza uno a uno tra View e ViewModel. Può capitare di avere molte View con un solo ViewModel o viceversa. Il ViewModel si pone in mezzo tra View e Model e fa da ponte tra i due mondi: quello del dominio applicativo e l’interfaccia utente. Non ha bisogno di conoscere la view: tramite i Data Template si collega il ViewModel alla view. Il ViewModel deve avere delle proprietà che rappresentano gli elementi che 21
  • 22. interessano alla View, oppure esporre direttamente parte del Model. Per esempio deve avere le proprietà FirstName e LastName oppure una proprietà che restituisce un oggetto Person. La prima soluzione è più elegante perché si disaccoppia completamente la View dal Model, ma richiede la scrittura di più codice. La seconda invece obbliga a scrivere gli oggetti di business implementando determinate interfacce, se si vuole che il Data Binding, la visualizzazione degli errori e altre cose funzionino correttamente. Se non si può modificare il Model, la prima soluzione diventa una scelta obbligata. Il ViewModel deve anche avere delle proprietà che restituiscono degli oggetti che implementano l’interfaccia ICommand. Per esempio SaveCommand e CloseCommand. View La view è formata prevalentemente da codice xaml. È formata da elementi visuali, bottoni, finestre e controlli più complessi. Codifica le scorciatoie da tastiera e controlla gli input dei vari device che è responsabilità del controller nel MVC. Spesso è definita dichiarativamente, spesso tramite tool. Ogni View deve conoscere il suo ViewModel associato perché deve agganciare, tramite Data Binding, tutte le proprietà che servono: per esempio FirstName e LastName se la View rappresenta l’anagrafica di un cliente. Inoltre deva 22
  • 23. agganciare, sempre tramite Data Binding, i command ai vari eventi, come per esempio il clic sul bottone “Ok” per salvare le modifiche. Separazione dei ruoli Unendo le potenzialità di WPF al pattern Model – View – ViewModel si ottiene un code behind della Window, o View, che non contiene codice. Questo permette di separare maggiormente il lavoro del designer da quello del developer. Il developer deve creare la logica a partire dal ViewModel. Il designer invece deve fare l’interfaccia utente. L’unico punto di contatto tra i due ruoli si ha nel file XAML: devono coesistere la parte di data binding e command da una parte e la parte di stili e template dall’altra. Se la View è in mano completamente al designer, l’unica cosa che deve sapere per far funzionare correttamente l’interfaccia utente è il contratto esposto dal ViewModel. A questo punto sta al designer agganciare i command che servono, dove servono e recuperare i dati tramite binding. Il developer dal canto suo, può dimenticarsi dell’esistenza della View, il suo lavoro termina con la definizione del contratto e l’implementazione del ViewModel. 23
  • 24. Capitolo due: Il toolkit In questo capitolo si parlerà in dettaglio del toolkit che è stato costruito per agevolare lo sviluppo di applicazioni basate sul pattern Model-View-ViewModel per non dover riscrivere quelle parti di codice che naturalmente si ripetono in ogni progetto. Sarà spiegato quali sono i problemi che si possono riscontrare e come sono stati risolti. Creare un'applicazione WPF senza utilizzare il pattern MVVM ovviamente è possibile, e molto probabilmente l'interfaccia utente è identica a un'applicazione scritta utilizzando il pattern. La vera differenza si nota soprattutto quando si deve manutenere l'applicazione. Se si applica il pattern, ci sarà una divisione netta tra l'interfaccia utente (e le operazioni proprie della UI) e il modello a oggetti. In caso contrario, una modifica fatta a una tabella del database, potrebbe essere molto più difficile da gestire. INotifyPropertyChanged Come già detto in precedenza, se si vuole aggiornare l'interfaccia utente quando cambia il valore di una proprietà messa in binding, si deve implementare l'interfaccia INotifyPropertyChanged. Questo è indipendente dall'uso del pattern MVVM. La differenza risiede in quale classe deve implementare questa interfaccia. 24
  • 25. Se non si utilizza il pattern, e si collegano alla finestra, tramite data binding, direttamente gli oggetti di dominio, allora saranno gli oggetti di dominio stessi a dover implementare l'interfaccia. Se invece si utilizza il pattern MVVM, View e Model non si conoscono, quindi deve essere il ViewModel a implementare INotifyPropertyChanged. Una soluzione per fare implementare questa interfaccia a tutti i ViewModel è fare in modo che tutti loro ereditino da una classe base ViewModelBase. Questa classe avrà tutte le funzionalità comuni, fra cui l'implementazione delle varie interfacce. using System.Componentmodel; using System.Diagnostics; public class ViewModelBase : INotifyPropertyChanged { public ViewModelBase() { } public event PropertyChangedEventHandler PropertyChanged = delegate { }; protected virtual void OnPropertyChanged( string propertyName) { this.VerifyPropertyName(propertyName); var handler = this.PropertyChanged; if (handler != null) 25
  • 26. { handler( this, new PropertyChangedEventArgs(propertyName)); } } [Conditional("DEBUG")] [DebuggerStepThrough] private void VerifyPropertyName (string propName) { if (TypeDescriptor .GetProperties(this)[propName] == null) { var msg = "Invalid property name: " + propName; Debug.Fail(msg); } } } Il metodo VerifyPropertyName serve per bloccare il debug con un errore se il nome della proprietà passato non esiste. Questo può accadere quando si modifica il nome di una proprietà, ma ci si dimentica di cambiare la stringa che si passa a OnPropertyChanged. Di seguito un breve esempio di come si usa questa classe. public class MyViewModel : ViewModelBase { private string myProperty; public string MyProperty { get { return this.myProperty; } set { if (value != this.myProperty) { this.OnPropertyChanged("MyProperty"); 26
  • 27. this.myProperty = value; } } } } Delegate Command Si supponga di dover eseguire quest'operazione: la finestra View1 ha un bottone, al clic bisogna scrivere in una TextBox "Bottone premuto". Se non si applica il pattern MVVM la soluzione più semplice è quella di intercettare l'evento Click del bottone e scrivere il testo nella TextBox. Questo è il flusso di esecuzione: 1. L'utente preme il bottone. 2. Si esegue l'handler associato all'evento Click del bottone. 3. Il codice setta la proprietà Text della TextBox. 27
  • 28. Se invece si implementa il pattern, il primo problema che si incontra è quello di associare il command all'evento Click del bottone. L'altro problema è come fare a scrivere il codice nel ViewModel e non nella View. Ecco il flusso che si vuole ottenere per aggiornare la TextBox al clic del bottone: 1. L'utente preme il bottone. 2. Viene scatenato il comando associato al bottone. 28
  • 29. 3. Si esegue il metodo Execute del comando. 4. Il codice setta la proprietà Text di tipo string. 5. Tramite data binding viene aggiornata l'interfaccia utente. 6. La TextBox contiene il testo "Bottone premuto". Ci sono poi altre due cose che uno sviluppatore vorrebbe avere quando associa un command ad un evento tramite codice xaml: la possibilità di sapere quando si può eseguire quel comando e la possibilità di aggiungere degli shortcut da tastiera. Ci sono, quindi, quattro problemi da risolvere: 1. Scrivere il codice del comando nel ViewModel. 2. Sapere quando un comando può essere eseguito. 3. Aggiungere degli shortcut. 4. Associare il comando al controllo nel codice XAML e minimizzare la scrittura di codice ripetitivo. Scrivere il codice del comando nel ViewModel La soluzione a questo problema è di creare un proprio comando che implementa l'interfaccia System.Windows.Input.ICommand. public interface ICommand { void Execute(object parameter); bool CanExecute(object paramenter); event CanExecuteChanged; } L'unica accortezza è di accettare un delegato nel quale è scritto il codice che si deve eseguire quando si richiama il metodo Execute di ICommand. public class DelegateCommand : ICommand { private readonly Action<object> executeMethod; 29
  • 30. public DelegateCommand(Action<object> executeMethod) { this.executeMethod = executeMethod; } public void ExecuteMethod(object parameter) { if (this.executeMethod == null) return; this.executeMethod(parameter); } // ... } In questo modo, per creare un comando basta passare al costruttore un delegato. Sarà poi compito dell'infrastruttura chiamare il metodo Execute dell'interfac- cia ICommand. Sapere quando un comando può essere eseguito Quest'operazione è già prevista dall'interfaccia ICommand, che ha il metodo CanExecute e l'evento CanExecuteChanged. Bisogna quindi lavorare in maniera analoga al punto precedente, facendo in modo che il costruttore accetti un secondo delegato che verrà poi eseguito dal metodo CanExecute. Inoltre, bisogna far sapere all'infrastruttura di WPF quando deve controllare nuovamente se può eseguire o no il comando. La soluzione più semplice è quella di dire a WPF di controllare ogni volta che succede qualche modifica. Di seguito c'è il nuovo codice: public class DelegateCommand : ICommand { // ... 30
  • 31. private readonly Func<object, bool> canExecuteMethod; public void CanExecuteMethod(object parameter) { if (this.canExecuteMethod == null) return; this.canExecuteMethod(parameter); } public event EventHandler CanExecuteChanged { add { CommandManager.requerySuggested += value; } remove { CommandManager.requerySuggested -= value; } } } Aggiungere degli shortcut Per risolvere questo problema si deve creare una nuova interfaccia IBindableCommand che eredita da ICommand. public interface IBindableCommand : ICommand { InputBindingCollection InputBindings { get; } string DisplayText { get; } } Fatto ciò, si fa implementare a DelegateCommand questa interfaccia e non ICommand. Seguono le aggiunte alla classe DelegateCommand. using System; using System.Windows.Input; public class DelegateCommand : IBindableCommand { // ... 31
  • 32. public void AddGesture(InputGesture gesture) { if (this.inputBindings == null) { this.inputBindings = new InputBindingCollection(); } this.inputBindings.Add( new InputBinding(this, gesture); } public InputBindingCollection Inputbindings { get { return this.inputBindings; } } public string DisplayText { get; private set; } } Associare il comando al controllo nel codice XAML e minimizzare la scrittura di codice ripetitivo Qui le cose da fare sono due: creare una Markup Extension per il codice XAML e associare agli shortcut ai controlli. Una markup extension è un'estensione alla sintassi del codice XAML. È possibile implementare una markup extension per fornire valori per le proprie- tà nell'utilizzo di un attributo, per le proprietà nell'utilizzo di un elemento pro- prietà o in entrambi i casi. Quando è utilizzata per fornire un valore di attributo, la sintassi che distingue un'estensione di markup in un processore XAML è la presenza delle parentesi graffe di apertura e chiusura { e }. Il tipo di estensione di markup è quindi identificato tramite il token di stringa immediatamente successivo alla parentesi graffa di apertura. 32
  • 33. Quando è utilizzata nella sintassi degli elementi proprietà, un'estensione di markup corrisponde visivamente a qualsiasi altro elemento utilizzato per fornire un valore di elemento proprietà, ovvero una dichiarazione di elementi XAML che fa riferimento alla classe dell'estensione di markup come elemento, racchiuso tra parentesi angolari (<>). Spesso si utilizzano le markup extension per trasformare molte righe di codice XAML in un'espressione molto più concisa. Per fare in modo di poter associare direttamente nello XAML un IBindableCommand, si crea una classe BindCommand che eredita da System.Windows.Markup.MarkupExtension. Nel metodo HandleLoaded si associa lo shortcut. using System; using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Markup; using ComponentModel; [MarkupExtensionReturnType(typeof(IBindableCommand))] public class BindCommand : MarkupExtension { public BindCommand() { } public BindCommand(string commandName) { this.Name = commandName; } public string Name { get; set; } public override object ProvideValue( IServiceProvider serviceProvider) { var pvt = serviceProvider.GetService( typeof(IProvideValueTarget)) as IProvideValueTarget; 33
  • 34. if (pvt != null) { var fe = pvt.TargetObject as FrameworkElement; if (fe != null) { fe.Loaded += this.HandleLoaded; } } return null; } private void HandleLoaded(object sender, RoutedEventArgs e) { var fe = sender as FrameworkElement; if (fe == null || DesignerProperties.GetIsInDesignMode(fe)) { return; } if (fe.DataContext == null) { return; } // Use reflection to retrieve provided Command. var property = fe.DataContext.GetType().GetProperty(this.Name); // Get root elements. var rootElement = this.GetRootElement(fe); var command = (IBindableCommand) property.GetValue(fe.DataContext, null); var source = sender as ICommandSource; if (source != null) { var button = fe as Button; var menuItem = fe as MenuItem; // Is it a button? if (button != null) { button.Command = command; if (!String.IsNullOrEmpty(command.DisplayText)) { button.Content = new AccessText { Text = command.DisplayText }; } } 34
  • 35. else if (menuItem != null) { // MenuItem. menuItem.Command = command; if (!String.IsNullOrEmpty(command.DisplayText)) { menuItem.Header = new AccessText { Text = command.DisplayText }; } if (command.InputBindings != null) { var keyGesture = command.InputBindings[0].Gesture as KeyGesture; if (keyGesture != null) { menuItem.InputGestureText = keyGesture.DisplayString; } } } // Add commandbindings to root element. if (command.InputBindings != null) { foreach (InputBinding ib in command.InputBindings) { rootElement.InputBindings.Add(ib); } } } // De-register loaded event. fe.Loaded -= this.HandleLoaded; } private FrameworkElement GetRootElement( FrameworkElement fe) { if (fe.Parent == null) { return fe; } return this.GetRootElement( fe.Parent as FrameworkElement); } } 35
  • 36. Come si usa Per utilizzare queste classi, nel ViewModel si crea un BindableCommand. public class MyViewModel { public ICommand MyCommand { get; private set; } public MyViewModel() { this.MyCommand = new DelegateCommand( o => { // TODO: Add Code here... }); } } Nel codice XAML si associa a un controllo il comando. <UserControl x:Class="MyView" xmlns="..." xmlns:x="..." xmlns:cmd= "clr- namespace:Toolkit.Commands;assembly=Toolkit" > <Button Command="{cmd:BindCommand Name=MyCommand}" /> </UserControl> IMessageBroker Si supponga di dover eseguire quest'operazione: una finestra, View1 ha un bottone. Al clic del bottone si deve visualizzare un'altra finestra, View2. Se non si è applica il pattern MVVM, la soluzione più semplice è creare e visualiz- zare View2 direttamente dall'handler associato all'evento Click del bottone. 36
  • 37. Se però si implementa il pattern MVVM, per eseguire un'operazione bisogna passare dal ViewModel. Una prima soluzione che può venire in mente è: 37
  • 38. 1. Associare un comando al clic del bottone e nel metodo Execute, che si trova nel ViewModel e non nella View. 2. Creare ViewModel2 (VM associato a View1). 3. Nel metodo Show di ViewModel2, creare View2 e visualizzare la finestra. Questo scenario però ha un problema:  I due moduli non sono indipendenti: ViewModel1 conosce View- Model2. Per risolvere questo problema si utilizza il broker. Esso è utilizzato per far comu- nicare due moduli che non si conoscono. La soluzione è creare un terzo attore, il broker appunto, che si mette in mezzo ed è conosciuto da entrambi. In questo modo il modulo A se deve inviare qualcosa al modulo B, invia un messaggio al broker che lo consegna al modulo B. Più in dettaglio un modulo sottoscrive un messaggio e quando qualcuno invia quel particolare messaggio, il broker lo recapita a tutti quelli che lo hanno sottoscritto. Il sistema di comunicazione del broker, sottoscrizione tramite delegato e invio del messaggio, è molto simile agli eventi: sottoscrizione tramite handler e lancio dell'evento. L'utilizzo del broker, quindi, è molto simile ad utilizzare gli eventi, però è stato ottenuto il disaccoppiamento tra i moduli. Il compito del "passacarte" è svolto da una classe che implementa l'interfaccia IMessageBroker. 38
  • 39. L'interfaccia ha due metodi per sottoscrivere un messaggio: il sottoscrittore chiede al message broker di essere notificato, invocando il delegato passato come parametro, quando un messaggio di tipo T è inviato; ha cinque metodi per cancellare la sottoscrizione e un metodo per inviare un messaggio. 39
  • 40. Gli unici metodi che hanno bisogno di una spiegazione sono i cinque metodi Unsubscribe. void Unsubscribe(object subscriber); Cancella tutte le sottoscrizioni di un sottoscrittore. void Unsubscribe<T>(object subscriber) where T : IMessage; Cancella tutte le sottoscrizioni di un sottoscrittore per un determinato messaggio T. void Unsubscribe(object subscriber, object sender); Cancella tutte le sottoscrizioni di un sottoscrittore per un determinato sender. void Unsubscribe<T>(object subscriber, object sender) where T : IMessage; Cancella tutte le sottoscrizioni di un sottoscrittore per un determinato sender e solo per un certo tipo di messaggio T. void Unsubscribe<T>( object subscriber, System.Action<T> callback) where T : IMessage; Cancella tutte le sottoscrizioni di un sottoscrittore per una determinata callback. L'interfaccia IMessage è così definita. 40
  • 41. L'utilizzo del message broker per comunicare tra più viewmodel ha anche il vantaggio che il broker stesso non deve sapere a priori, quali saranno i messaggi da inviare. Questo permette di creare messaggi ad hoc per ogni esigenza, conosciuti solo da sender e sottoscrittore. Di seguito è mostrato un esempio di utilizzo del broker per sottoscrivere un messaggio. class LoadGuardianCostListViewModel : IModule { public void Initialize( object sender, IUnityContainer container) { var broker = container.Resolve<IMessageBroker>(); broker.Subscribe<GetAllItemsMessage<GuardianCost>>( sender, message => { var vm = new GuardianCostListViewModel( container, message.Context); vm.Show(); }); } } La classe LoadGuardianListViewModel ha un unico metodo che crea una sottoscrizione a un messaggio che quando viene inviato causa la visualizzazione della view GuardianCostListViewModel. Per inviare invece un messaggio ecco un esempio. this.Broker.Dispatch( new GetAllItemsMessage<GuardianCost>( this, ServerContextRegistry.GetNew(container))) 41
  • 42. ServerContextRegistry.GetNew restituisce una nuova unit of work da utilizzare per lavorare con il database. Container è un container di inversion of control (IoC), nello specifico Unity di Microsoft. Dependency Inversion Principle (DIP) e Inversion of Control (IoC) Secondo il Dependency Inversion Principle7 ogni modulo di alto livello non dovrebbe dipendere da un modo di livello più basso. Entrambi dovrebbero dipendere da un'astrazione. L'astrazione deve essere indipendente dai dettagli. I dettagli dovrebbero dipendere dall'astrazione. In seguito sono mostrati due esempi: il primo non rispetta questo principio, il secondo sì. La grossa differenza sta nel fatto che nel secondo esempio gli oggetti di basso livello sono iniettati nel costruttore e non creati dall'oggetto di alto livello. public class FinanceInfoService { public string GenerateAsHtml(string symbol) { SomeStockFinder finder = new SomeStockFinder(); StockInfo[] stocks = finder.FindQuotesInfo(symbol); HtmlTableRenderer renderer = new HtmlTableRenderer (); return renderer.RenderQuoteInfo(stocks); } //... } L'obiettivo è ottenere una struttura molto simile all'immagine seguente. 7 Il DIP è stato formalizzato da Robert Martin. È possibile leggere altri dettagli sul sito web http://www.objectmentor.com/resources/articles/dip.pdf 42
  • 43. In questo modo, GenerateAsHtml diventa indipendente da SomeStock- Finder e HtmlTableRenderer. public class FinanceInfoService { IFinder finder; IRenderer renderer; public FinanceInfoService( IFinder finder, IRenderer renderer) { this.finder = finder; this.renderer = renderer; } public string GenerateAsHtml(string symbol) { StockInfo[] stocks = finder.FindQuotesInfo(symbol); return renderer.RenderQuoteInfo(stocks); } //... } L'Inversion of Control è l'applicazione del DIP. Spesso IoC è anche detta Dependency Injection (DI). A volte in letteratura si trova che la DI è il principio, 43
  • 44. mentre IoC è l'applicazione del principio. In ogni caso IoC è basato sul pattern DIP. Oggi IoC/DI è spesso associato con dei framework particolari che hanno una serie di caratteristiche. Esistono vari framework di IoC, ma tutti hanno delle caratteristiche comuni: sono costruiti attorno ad un container che, in base ad alcune informazioni di configurazione, risolve le dipendenze. Il chiamante istanzia il container e gli passa l'interfaccia desiderata come parametro. Come risposta, il framework di IoC/DI restituisce un oggetto concreto che implementa quell'interfaccia. In questa tesi, come framework di IoC è stato utilizzato Microsoft Unity Application Block. Struttura del broker Il broker ha un campo privato: un dictionary chiamato subscriptions. private IDictionary<Type, IList<Action<T>>> subscriptions; Questo dictionary usa come chiave il tipo del messaggio che ha una sottoscrizio- ne e come valore una lista di delegati, che devono essere invocati quando è in- viato un messaggio del tipo presente nella chiave. In WPF non è possibile modificare un oggetto dell'interfaccia utente da un thread diverso da quello che ha creato l'oggetto stesso, pena una CrossThreadException. In WPF esiste quindi un oggetto Dispatcher, che evita questo problema. Il suo metodo Invoke, accetta un delegato, che viene eseguito sempre nel thread visuale dell'applicazione. 44
  • 45. Per questo motivo, il broker ha anche un altro campo privato, un dispatcher, che serve al metodo Dispatch per evitare le CrossThreadException. private readonly Dispatcher dispatcher; Sottoscrivere un messaggio Per sottoscrivere un messaggio, si deve chiamare un overload del metodo Subscribe. 45
  • 46. Questo metodo ha almeno un parametro di tipo Action<T> che è l'azione da eseguire quando viene inviato il messaggio e un parametro generico di tipo T che indica a quale messaggio ci si deve sottoscrivere. Quando qualcuno chiama Subscribe, il broker, scopre se il dictionary subscriptions contiene già il messaggio T, e in caso contrario lo aggiunge. Poi aggiunge il delegato alla lista di delegati di quel messaggio. public void Subscribe<T>(Action<T> callback) where T : IMessage { if (this.subscriptions.ContainsKey(typeof(T))) { var subscribers = this.subscriptions[typeof(T)]; subscribers.Add(callback); } else { this.subscriptions.Add( typeof(T), new List<Action<T>>() { callback }); } } Inviare un messaggio Per inviare un messaggio si chiama il metodo Dispatch<T>(). Quando viene chiamato questo metodo, il broker cerca nel dictionary subscriptions se qualcuno ha sottoscritto il messaggio T. In caso affermativo chiama tutti i delegati associati. 46
  • 47. public void Dispatch<T>() where T : IMessage { if (this.subscriptions.ContainsKey(typeof(T))) { var subscribers = this.subscriptions[typeof(T)]; subscribers .ToList() .ForEach(callback => { try { this.dispatcher.Invoke(callback); } catch (Exception e) { if (e.InnerException != null) throw e.InnerException; else throw; } }); } } 47
  • 48. UI Composition Un'applicazione desktop spesso è formata da molte finestre distinte. Una cosa importante nello sviluppo di quel genere di applicazioni è cercare di tenere le varie finestre fra loro indipendenti. Di seguito ci sono alcune definizioni. Modulo Un modulo è una parte dell'applicazione, generalmente visuale. I moduli fra loro devono essere indipendenti e nel caso di moduli visuali sono visualizzati nella shell all'interno di una region. Region La region contiene i moduli visuali. Shell Finestra principale dell'applicazione. Esiste solo una shell e contiene, in varie region, gli altri moduli. La shell non deve conoscere i moduli, conosce solo le region che li conterranno. Se i moduli sono indipendenti fra loro, ci sono una serie di vantaggi:  Possono essere sviluppati indipendentemente l'uno dall'altro.  È possibile mostrare certi moduli a certi utenti ed altri ad altri senza difficoltà: basta non caricare il modulo che non si vuole visualizzare.  È possibile caricare i moduli che interessano solo a runtime, magari utilizzando un file di configurazione. Per implementare il pattern MVVM non c'è nessuna necessità di scrivere applicazioni composite. Creando applicazioni di questo tipo, però, si riesce a modificare o aggiungere una parte d'interfaccia utente senza interferire con tutto il resto dell'applicazione. E questo è un grosso vantaggio. 48
  • 49. Il pattern MVVM è un buon punto di partenza per creare applicazioni composite per due motivi:  Il broker: poiché i moduli non si possono conoscere, sarà lui l'unico in grado di farli comunicare.  L'indipendenza intrinseca dei ViewModel (Moduli se si parla in termini di UI Composition) che è collegata alla regola "Una User Story, un ViewModel": se si aggiungono, o si modificano alcune User Story (cioè alcuni requisiti del software), basta modificare solo il VM direttamente interessato (e la View naturalmente) lasciando inalterato tutto il resto. Per avere un'applicazione composita mancano ancora alcuni dettagli, che saranno spiegati nei prossimi paragrafi. IRegion Il primo problema che si riscontra nello sviluppo di applicazioni composite è quello di fare in modo che la Shell non abbia alcuna conoscenza dei moduli che andrà a visualizzare. 49
  • 50. Per fare ciò, la Shell avrà una o più region. All'interno di ogni region è possibile visualizzare uno o più moduli. Naturalmente anche un modulo a sua volta può contenere una region, nella quale ci sono altri moduli, e così via. Si supponga di dover eseguire quest'operazione: nella finestra principale (Shell), quando si preme un bottone, si deve inserire nella shell stessa una view (View1). Se non applichiamo la UI Composition, la soluzione più semplice è creare View1 e associarlo alla region nell'handler associato evento Click del bottone. Questo è il flusso di esecuzione: 1. L'utente preme il bottone. 2. Si esegue l'handler associato all'evento Click. 50
  • 51. 3. Il codice, che si trova nella Shell, crea un'istanza di View1 e la inserisce nella region. Quest'approccio ha due problemi:  La Shell crea il modulo View1.  Il codice per inserire un modulo nella region si trova sempre nella Shell. Quello che si vuole ottenere è qualcosa di simile all'immagine sottostante. 51
  • 52. Quello che cambia rispetto alla situazione precedente è tutta racchiusa nel punto due:  Non si crea più il modulo nella Shell, ma si recupera un'istanza dal container di IoC.  Il codice per inserire un modulo non si trova più nella Shell, ma nel metodo ShowViewModel della classe region. Se si implementa anche il pattern MVVM, l'unica differenza è che il codice in 2 si troverebbe nel ViewModel e non nella View. Implementazione Una region implementa l'interfaccia IRegion. public interface IRegion { IEnumerable<IWorkspaceViewModel> ViewModels { get; } void ShowViewModel(IWorkspaceViewModel item); bool? ShowDialogViewModel(IWorkspaceViewModel item); } L'interfaccia ha una proprietà che restituisce la lista di moduli che contiene, e due metodi: ShowViewModel e ShowDialogViewModel. Il secondo apre una finestra modale. Come si può notare entrambi i metodi accettano come unico parametro un oggetto che implementa l'interfaccia IWorkspaceViewModel. Questa interfaccia è la base di tutti i moduli visuali che devono essere visualizzati in una region. public interface IWorkspaceViewModel : IViewModel { event EventHandler Closed; ICommand CloseCommand { get; } 52
  • 53. object Content { get; } void Show(); bool? ShowDialog(); } Utilizzando questa struttura, la shell ha almeno una region con all'interno almeno un modulo da visualizzare. Manca ancora qualcosa però: la region ha una lista di ViewModel, mentre bisogna visualizzare le rispettive View. Questo si risolve utilizzando una caratteristica di WPF. In Windows Presentation Foundation può essere visualizzato qualunque oggetto. Se è un oggetto visuale, è visualizzato, altrimenti è chiamato ToString() dell'oggetto per visualizzare una stringa. C'è una terza possibilità: per un oggetto si può definire quale sarà la sua visualizzazione utilizzando i Data Template. <DataTemplate DataType="{x:Type vm:PhoneViewModel}"> <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" > <vw:PhoneView /> </ScrollViewer> </DataTemplate> Questo template nello specifico, visualizza uno ScrollViewer e all'interno la view associata a PhoneViewModel. Il toolkit contiene tre classi diverse che implementano l'interfaccia IRegion:  ContentContainer: può contenere un solo viewmodel.  ListContainer: può contenere più viewmodel.  DialogContainer: apre una finestra modale e può contenere un solo viewmodel. 53
  • 54. IModule Quando si scrivono applicazioni composite formate da tanti moduli, bisogna trovare un modo semplice per inizializzare i moduli stessi. Portando all'estremo deve essere possibile indicare in un file di configurazione quali moduli inizializzare. Quest'ultima cosa non è presente nel toolkit. Quello che è presente, invece, è la possibilità di scoprire a runtime quali sono i moduli e come inizializzarli indicando a compile time solo gli assembly che contengono i moduli stessi. Inizializzazione di un modulo Tornando all'esempio della figura precedente, manca qualcosa: anziché creare un oggetto View, si utilizza il container di IoC per risolvere la dipendenza. Bisogna però ancora capire come si fa a inizializzare View1. In altre parole, manca chi deve eseguire RegisterInstance(new View1()); public interface IModule { void Initialize(object sender, IUnityContainer container); } L'interfaccia ha solo un metodo che inizializza il modulo. Una piccola nota. Il metodo Initialize non restituisce nulla, quindi se si deve recuperare il 54
  • 55. modulo in un secondo momento, nel metodo suddetto si deve inserire il modulo nel container di IoC. In questo modo poi nell'applicazione sarà possibile recuperare il modulo inizializzato. Di seguito c'è un esempio di inizializzazione di un modulo visuale. internal sealed class LoadPersonViewModel : IModule { public void Initialize( object sender, IUnityContainer container) { var broker = container.Resolve<IMessageBroker>(); broker.Subscribe<EditingMessage<Person>>( sender, message => { var myContainer = container; var pvm = new PersonViewModel( message.Context, myContainer, message.Item, true); pvm.Show(); }); } } Questa classe è un modulo non visuale che recupera l'istanza del message broker, e sottoscrive un messaggio. Il delegato che deve essere eseguito quando il messaggio è inviato altro non fa che creare un ViewModel e visualizzarlo. Questo ViewModel è un modulo visuale che sarà visualizzato all'interno di una region. Discovery dei moduli Naturalmente, dopo aver scritto il codice di inizializzazione dei moduli si deve eseguire il codice stesso. Bisogna prima però scoprire quali moduli sono stati creati per l'applicazione. 55
  • 56. Per fare questo, bisogna dire al toolkit quali assembly contengono le classi che implementano l'interfaccia IModule. public static void RegisterModules( IUnityContainer container, Assembly assembly) { if (container == null) throw new ArgumentNullException("container"); if (assembly == null) throw new ArgumentNullException("assembly"); assembly .GetTypes() .Where(t => t.IsInterfaceImplemented<IModule>()) .ForEach(t => container.RegisterType( typeof(IModule), t, t.FullName, new ContainerControlledLifetimeManager())); } Questo metodo controlla tutti i tipi di un assembly alla ricerca di classi che implementano l'interfaccia IModule. Quando ne trova una, la inserisce nel container di IoC come singleton. ForEach e IsInterfaceImplemented sono due extension method. Gli Extension Method consentono di "aggiungere" metodi ai tipi esistenti senza creare un nuovo tipo derivato, ricompilare o modificare in altro modo il tipo originale. Gli extension method sono uno speciale tipo di metodo statico, ma sono chiamati come se fossero metodi d'istanza sul tipo esteso. L'ultima cosa da fare è quella di recuperare i moduli dal container di IoC e inizializzarli. foreach (var item in this.Container.ResolveAll<IModule>()) 56
  • 57. { item.Initialize(this, this.Container); } ViewModel Tutti i ViewModel implementano l'interfaccia IViewModel. Questa interfaccia ha quattro proprietà. IsInDesignMode serve per indicare se il ViewModel è stato creato a runtime oppure se è stato creato all'interno di un designer come Visual Studio o Expression Blend. Quest'operazione è utile soprattutto in Expression Blend 3 per poter associare il ViewModel direttamente alla View: così facendo il designer elenca tutte le property del ViewModel che possono essere messe in Data Binding con la View. Inoltre nel codice è possibile inizializzare il ViewModel con alcuni valori di test solo quando il ViewModel è utilizzato all'interno di un designer. public interface IViewModel : IDisposable, INotifyPropertyChanged { string DisplayName { get; } IMessageBroker Broker { get; } IUnityContainer Container { get; } bool IsInDesignMode { get; } } Ci sono tre interfacce che ereditano da IViewModel:  ICommandViewModel: si utilizza quando un singolo comando deve avere una view per essere inserito in una view container.  IWorkspaceViewModel: tutti i ViewModel che devono essere inseriti in una region devono implementare questa interfaccia che altro non fa che aggiungere alcune funzionalità di visualizzazione e chiusura della view. 57
  • 58. IShellViewModel: il ViewModel associato alla Shell deve implementare questa interfaccia. Associare una region al ViewModel Quando si crea una classe che implementa IWorkspaceViewModel è indispensabile anche indicare in quale region deve essere inserito. Questa informazione però interessa solamente a runtime, quindi si può utilizzare un attributo per salvare questa informazione e un po' di reflection per recuperarla a runtime. L'attributo ha due proprietà: una che indica il tipo di region, l'altra, opzionale, che rappresenta il nome di quella region all'interno del container di IoC. A runtime si devono recuperare le informazioni presenti nell'attributo per inserire il ViewModel nella region corretta. 58
  • 59. [Region(typeof(ListContainer), Name = "MyRegion")] class MyViewModel : IWorkspaceViewModel { private IRegion region; public MyViewModel() { RegionAttribute regionAttribute; if (this.GetType() .TryGetCustomAttribute(out regionAttribute)) { if (string.IsNullOrEmpty(regionAttribute.Name)) { this.region = (IRegion)container.Resolve( regionAttribute.Region); } else { this.region = (IRegion)container.Resolve( regionAttribute.Region, regionAttribute.Name); } } } public void Show() { this.region.ShowViewModel(this); } // ... } Il codice recupera le informazioni sulla region a runtime e risolve la dipendenza utilizzando il framework di IoC. E questa region sarà poi utilizzata dal metodo Show. TryGetCustomAttribute è un extension method che altro non fa che valorizzare regionAttribute se il tipo è decorato con l'attributo RegionAttribute. L'ultima cosa da fare è quella di creare un'istanza della region nella quale inserire il ViewModel e registrare la stessa nel framework di IoC. 59
  • 60. this.Container.RegisterType<ListContainer>( "MyRegion", new ContainerControlledLifetimeManager()); Questo codice va inserito nella fase di inizializzazione dell'applicazione, cioè nell'Application Controller del quale parleremo a breve. Application Controller L'ultima cosa che rimane da fare è l'inizializzazione dell'applicazione. Le operazioni da fare sono le seguenti:  Creare un container di IoC e registrare tutto quello che serve.  Creare un'unica istanza del MessageBroker.  Creare un'unica istanza della Shell.  Cercare e inizializzare tutti i moduli. Per eseguire queste operazioni per prima cosa si crea una classe base astratta ApplicationControllerBase: questa classe ha tutte le funzionalità indipendenti dalla particolare applicazione. public abstract class ApplicationControllerBase { private readonly IUnityContainer container; private IShell shell; private IMessageBroker broker; protected ApplicationControllerBase( IUnityContainer container) { this.container = container; } public IShell Shell { get { return this.shell; } } 60
  • 61. protected IUnityContainer Container { get { return this.container; } } public void Run() { // Registra il MessageBroker. this.container .RegisterType<IMessageBroker, MessageBroker>( new ContainerControlledLifetimeManager(), new InjectionConstructor( Dispatcher.CurrentDispatcher)); // Trova tutti i moduli presenti negli assembly // e li registra nel container di IoC. foreach (var assembly in this.GetViewModelAssemblies()) Unity.Discovery.RegisterModules( this.Container, assembly); this.broker = this.Container.Resolve<IMessageBroker>(); this.shell = this.CreateShell(); // Recupera tutti i moduli e li inizializza. foreach (var item in this.Container.ResolveAll<IModule>()) item.Initialize(this, this.Container); } protected abstract IShell CreateShell(); protected abstract IEnumerable<Assembly> GetViewModelAssemblies(); } Questa classe ha due metodi astratti che devono essere implementati dalla classe che eredita da essa, ApplicationController, che personalizza il comportamento di default creando la shell e indicando quali sono gli assembly nei quali si devono cercare i moduli. class ApplicationController : ApplicationControllerBase { 61
  • 62. public ApplicationController( IUnityContainer container) : base(container) { this.Container.RegisterType<ListContainer>( new ContainerControlledLifetimeManager()); } protected override IShell CreateShell() { this.Container.RegisterType<IShell, Shell>( new ContainerControlledLifetimeManager()); var shell = this.Container.Resolve<IShell>(); shell.DataContext = new ShellViewModel(this.Container); return shell; } protected override IEnumerable<Assembly> GetViewModelAssemblies() { return new[] { Assembly.GetExecutingAssembly() }; } } Il costruttore registra nel container di IoC l'unica region dell'applicazione. In questa region saranno visualizzati tutti i moduli visuali. Il metodo CreateShell crea un'istanza della shell e setta anche il ViewModel. Il metodo GetViewmodelAssemblies in questo caso restituisce semplice- mente l'assembly corrente. Infine, all'avvio dell'applicazione, si crea un'istanza della classe Application- Controller al quale si passa un'istanza del container di IoC e si chiama il metodo Run(). var controller = new ApplicationController(new UnityContainer()); controller.Run(); 62
  • 63. Capitolo tre: L’applicazione Questo capitolo parlerà di un'applicazione sviluppata utilizzando il toolkit esposto nel capitolo due per realizzare un'interfaccia utente modulare in Windows Presentation Foundation, basata sul pattern Model-View-ViewModel. Obiettivo dell'applicazione Lo scopo primario dell'applicazione è di gestire le palestre di Trieste. Il committente deve gestire le palestre, assegnare i turni alle varie società che vogliono utilizzarle, inviare dei custodi ad aprire e chiudere gli edifici, pagare i custodi stessi per il loro lavoro e farsi pagare dalle società per l'utilizzo delle palestre. Architettura hardware e software dell'ambiente di produzione L'ambiente di produzione è un dominio Active Directory formato da tre macchine server con Windows Server 2008: la prima è un database server con installato SQL Server 2008, la seconda è un web server con IIS 7.0. Il terzo server è il domain controller della rete. 63
  • 64. Inoltre ci sono una serie di macchine client con Windows Vista. Tutti i computer, client e server, hanno installato il .NET Framework 3.5 Service pack 1. Requisiti tecnici non funzionali Il committente vuole un'applicazione desktop progettata in ambiente Microsoft. In questo momento i client sono pochi, ma in un prossimo futuro è molto probabile che aumentino. Quest'applicazione deve essere visibile e utilizzabile solo all'interno della rete interna. L'interfaccia utente dell'applicazione deve essere personalizzabile in funzione dell'utente che utilizzerà l'applicazione, per esempio user o admin. Inoltre, deve essere estendibile con nuove interfacce e moduli in futuro. Architettura dell'applicazione Visti l'architettura hardware e i requisiti tecnici non funzionali, l'applicazione deve essere sviluppata utilizzando una serie di tecnologie. 1. Deve avere un database SQL Server 2008. 2. Deve essere sviluppata in .NET. 3. Deve avere, se serve, un Web Server IIS 7.0 Il non sapere a priori quanti siano i client e il fatto che possano aumentare molto, porta come conseguenza diretta che il server non deve avere alcuna conoscenza dei client e soprattutto non deve fare nessuna assunzione su di essi. Infine, l'interfaccia utente deve essere modulare: questo per migliorare l'estendibilità e la personalizzazione. Detto questo, l'applicazione è sviluppata secondo un'architettura three-tier, ovvero con un database su una macchina, una parte di application server su un secondo server e la parte client da installare su tutti i computer client della rete. 64
  • 65. La comunicazione tra l'application server e i client avviene tramite servizi REST su http. Representational State Transfer – REST REST è l’acronimo di Representational Transfer State, ed è un paradigma per la realizzazione di applicazioni Web che permette la manipolazione delle risorse per mezzo dei metodi GET, POST, PUT e DELETE del protocollo HTTP. Basando le proprie fondamenta sul protocollo HTTP, il paradigma REST restringe il proprio campo d’interesse alle applicazioni che utilizzano questo protocollo per la comunicazione con altri sistemi. 65
  • 66. Il termite REST è stato coniato nel 2000 da Roy Fielding, uno degli autori del protocollo HTTP, per descrivere un sistema che permette di descrivere ed identificare le risorse web8. REST prevede che la scalabilità del Web e la crescita siano diretti risultati di pochi principi chiave di progettazione:  Lo stato dell'applicazione e le funzionalità sono divisi in Risorse WEB.  Ogni risorsa è unica e indirizzabile usando sintassi universale per uso dei link ipertestuali.  Tutte le risorse sono condivise come interfaccia uniforme per il trasferimento di stato tra client e risorse, questo consiste in: o un insieme vincolato di operazioni ben definite; o un insieme vincolato di contenuti, opzionalmente supportato da codice on demand.  Un protocollo che è: o Client-Server; o Stateless; o Cachable; o A livelli. Application Server / Web Server La parte di application server è formata dall'accesso ai dati utilizzando un O/RM, nello specifico Entity Framework di Microsoft. La parte invece di creazione dei servizi REST è creata utilizzando ADO.NET Data Services v1.0, sempre di Microsoft. 8 http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm 66
  • 67. Object / Relational Mapping – O/RM L'Object-Relational Mapping (ORM) è una tecnica di programmazione per convertire dati fra RDBMS e linguaggi di programmazione orientati agli oggetti. In buona sostanza, associa a ogni operazione ed elemento usato nella gestione del database degli oggetti con adeguate proprietà e metodi, astraendo l'utilizzo del database dal DBMS specifico. I principali vantaggi nell'uso di questo sistema sono i seguenti.  Il superamento (più o meno completo) dell'incompatibilità di fondo tra il progetto orientato agli oggetti ed il modello relazionale sul quale è basata la maggior parte degli attuali DBMS utilizzati; con una metafora legata al mondo dell'elettrotecnica, si parla in questo caso di disadattamento dell'impedenza (Impedence Mismatch) tra paradigma relazionale e ad- oggetti (object/relational impedance mismatch).  Un'elevata portabilità rispetto alla tecnologia DMBS utilizzata: cambiando DBMS non devono essere riscritte le routine che implementano lo strato 67
  • 68. di persistenza; generalmente basta cambiare poche righe nella configurazione del prodotto per l'ORM utilizzato.  Facilità d'uso, poiché non si è tenuti a utilizzare direttamente un linguaggio di query come l'SQL (che comunque rimane disponibile per eseguire compiti complessi o che richiedono un'elevata efficienza). I prodotti per l'ORM in questo momento più diffusi, offrono spesso nativamente funzionalità che altrimenti andrebbero realizzate manualmente dal programmatore:  caricamento automatico del grafo degli oggetti secondo i legami di asso- ciazione definiti a livello di linguaggio;  gestione della concorrenza nell'accesso ai dati durante conversazioni;  meccanismi di caching dei dati (con conseguente aumento delle presta- zioni dell'applicazione e riduzione del carico sul sistema RDBMS). Client Il client è sviluppato in Windows Presentation Foundation, utilizzando il toolkit presentato nel capitolo due. Questo tier è formato da tre livelli:  Accesso ai dati e model.  ViewModel.  View. La parte di accesso ai dati e Model è creata utilizzando la parte client di ADO.NET Data Services; i ViewModel e le View si basano sul toolkit. Cosa fondamentale, poiché si parla di MVVM, la View non conosce il Model. 68
  • 69. Attori In questa parte sono riassunti tutti gli attori, siano essi persone fisiche, società o luoghi o oggetti. Committente Il committente è la società che gestisce le palestre situate sul territorio della provincia di Trieste, siano esse comunali o provinciali. Società La società è chi utilizza la palestra. Una società può utilizzare più palestre diverse in orari differenti. Ogni società deve pagare il committente per l’utilizzo della palestra. Il costo dipende dalla categoria della palestra e 69
  • 70. se la società è affiliata al CONI e/o socia del committente. A ogni società sono associati dei turni. Custode Un custode apre e chiude una palestra. Alla fine di ogni mese deve consegnare al committente un documento, nel quale si attesta quante ore ha fatto. Federazione Una federazione sportiva è paragonabile a una società. L'unica differenza è che una federazione fa parte del CONI. Istituto Comprensivo Rappresenta l’ente che ha in gestione le varie scuole del Comune (e le relative palestre). Ogni istituto può avere uno o più palestre. Solo le palestre comunali hanno un istituto comprensivo. Palestra / Scuola Una palestra può essere collegata a un istituto comprensivo e può avere uno o più custodi. Una scuola (o palestra) è o del comune o della provincia. Ogni palestra ha degli orari di disponibilità. 70
  • 71. Sala CONI Una Sala CONI è paragonabile a una palestra. Ci sono in totale tre sale, tutte di proprietà del CONI. È previsto un custode per la pulizia e la custodia. Turno Un turno in una palestra è un orario della settimana nel quale una determinata società può svolgere attività in palestra. Un turno si deve riferire a un anno sportivo. Struttura dell'applicazione Poiché questa tesi è incentrata sull'interfaccia utente, in quest'ultima parte del capitolo si parlerà solamente del client. Il client è un'applicazione modulare. La shell dell'applicazione è formata da due region:  Una ribbon region, nella quale sono contenuti tutti i comandi.  Una tab region che contiene, all'interno di un TabControl, tutte le view che devono essere visualizzate. L'immagine sottostante, per esempio, visualizza cinque view. Ognuna di esse è uno UserControl che viene iniettato nella shell e naturalmente ogni view non ha nessuna conoscenza né della shell, né delle altre view. 71
  • 72. Alcune user story In quest'ultima parte del capitolo saranno elencate tre user story che producono dei ViewModel molto diversi fra loro. Una User Story9 è un requisito di un sistema software formato da una o due frasi scritte il linguaggio corrente o nel linguaggio di business dell'utente. Le user story sono utilizzate nelle metodologie di sviluppo agili10. Ogni user story è scritta generalmente in un piccolo pezzo di carta, per assicurarsi che non cresca troppo. Le user story dovrebbero essere scritte dai clienti di un progetto software, e sono lo strumento principale per influenzare lo sviluppo del software. 9 http://www.agilemodeling.com/artifacts/userStory.htm 10 http://agilemanifesto.org/ e http://www.agilemodeling.com/ 72
  • 73. Le user story sotto elencate rappresentano la maggior parte dei ViewModel possibili. La prima user story produrrà un ViewModel che è collegato a un solo oggetto del modello per eseguire operazioni d'inserimento e modifica dei dati. Anche il secondo è collegato a un solo oggetto del modello, ma esegue delle operazioni di ricerca, e non visualizza tutti i dati presenti nell'oggetto di dominio. Infine, la terza user story è collegata a molti oggetti del modello e visualizza solamente i dati che interessano. US1 – Inserimento e modifica di un custode Un utente deve poter inserire un nuovo custode. Deve poter anche modificare i dati inseriti. 73
  • 74. L'immagine precedente mostra la View quando si deve inserire un nuovo custode, quella sottostante, invece, visualizza la stessa View quando si deve modificare un custode in precedenza inserito. La prima cosa da notare è che la View è identica: indipendentemente dall'operazione da eseguire, non cambia. L'unica differenza degna di nota è il titolo del Tab che passa da "Nuovo" a "Carla Riboni". Capire in quale stato ci si trova è compito del ViewModel, che fra le altre cose modifica anche il titolo della View. Questo è il primo tipo di ViewModel che capita spesso di trovare. Ecco le sue caratteristiche: 74
  • 75. Comanda più operazioni (inserimento e aggiornamento) su una sola View.  È associato a un solo oggetto del modello.  Espone tutti i campi dell'oggetto. L'utilizzo principale di questo ViewModel è di eseguire operazioni d'inserimento e aggiornamento di un singolo oggetto del dominio. 75
  • 76. Infatti, il toolkit contiene una classe base, ItemViewModelBase, che incapsula tutte le caratteristiche fondamentali per eseguire queste operazioni. US2 – Lista dei custodi Un utente deve poter cercare un custode per cognome. Deve inoltre poter modificare o eliminare il custode appena trovato. Come si può vedere dall'immagine, la View è molto semplice: ha un'area di ricerca e una zona nella quale sono visualizzati i risultati. Per eseguire la ricerca è possibile cliccare sul bottone cerca o premere il tasto Invio dopo aver scritto qualcosa nel box di ricerca. Facendo doppio clic su un custode, si apre una nuova View per modificare i dati. Infine c'è un bottone per eliminare un custode. 76
  • 77. Questo è il secondo tipo di ViewModel molto frequente. Ecco le sue caratteristiche:  Permette la ricerca su un unico oggetto del dominio.  Visualizza i risultati in una lista.  Permette operazioni di aggiornamento e cancellazione.  Espone solo alcuni campi dell'oggetto. 77
  • 78. Poiché anche questa serie di operazioni sono molto frequenti, esiste una classe nel toolkit che raggruppa tutte queste funzioni di base: ListViewModelBase. US3 – Creazione Turno Si deve poter assegnare un turno a una società presso una palestra e il costo che la società deve accollarsi per usufruire dello spazio. Contestualmente si deve anche associare il custode e quanto deve essere dovuto allo stesso per il lavoro svolto. Questa View permette di scegliere ora, giorno e tipo di turno. A essi associa una palestra, una società e un custode. È possibile fare una ricerca per trovare quello che serve. In oltre, in automatico il sistema propone un costo per la palestra e per il custode; costi che possono essere modificati se necessario. 78
  • 79. Questo è l'ultimo tipo di ViewModel. Sono ViewModel generici, che hanno solo una cosa in comune fra loro: recuperano i dati da molti oggetti diversi del modello e poi visualizzano solo quello che è necessario. Per questi ViewModel non esiste una classe base e spesso sono molto complessi da realizzare perché fanno molte cose diverse. In questo caso specifico, queste sono le caratteristiche del ViewModel:  Legge i dati da sei oggetti diversi.  Esegue tre tipi di ricerche manuali.  Esegue due ricerche automatiche.  Permette la modifica delle ricerche automatiche.  Inserisce il turno nel database. 79
  • 80. Conclusioni In questa tesi si è parlato in dettaglio del pattern Model-View-ViewModel, delle sue caratteristiche, vantaggi e svantaggi ed è stata anche proposta un'imple- mentazione. Il pattern è molto giovane, non esistono ancora delle linee guida: per adesso ci sono solamente una serie di problemi e la comunità degli svilup- patori sta proponendo soluzioni diverse. Anche Windows Presentation Foundation è giovane come tecnologia, e, nonostante Microsoft sia spingendo molto, non è ancora entrata a far parte del bagaglio di tutti gli sviluppatori di applicazioni desktop in ambiente Windows. Tutto ciò ha una serie di cause, prima fra tutte, la curva di apprendimento di WPF molto ripida. Inoltre, mancano ancora una serie di controlli che sono presenti in Windows Forms e rendono ancora questa tecnologia appetibile. Infine solo a metà luglio 2009 è uscito Expression Blend 3, il primo designer che finalmente riesce ad esprimere tutte le potenzialità di WPF. In rete si trovano tutta una serie di articoli e toolkit che implementano il pattern MVVM. Tra i più famosi ci sono il MVVM Light Toolkit11 di Laurent Bugnion, Cinch12 di Sasha Barber e Goldlight13 di Peter O'Hanlon. Questi tre toolkit hanno implementazioni anche molto diverse fra loro, ma tutte risolvono i problemi che sono stati affrontati in questa tesi. Se invece, oltre ad affrontare i problemi associati al pattern MVVM, si vuole anche creare un'applicazione modulare sfruttando la UI composition, il toolkit di riferimento e Prism14 sviluppato dal team di Pattern and Practices di Microsoft. 11 Per maggiori informazioni è possibile consultare il sito http://www.galasoft.ch/mvvm/getstarted/ 12 Per maggiori informazioni è possibile consultare il sito http://cinch.codeplex.com/ 13 Per maggiori informazioni è possibile consultare il sito http://goldlight.codeplex.com/ 14 Per maggiori informazioni è possibile consultare il sito http://compositewpf.codeplex.com/ 80
  • 81. La giovane età di WPF e del pattern MVVM possono far pensare che non sia ancora il caso di sviluppare un'applicazione enterprise utilizzando queste tecniche e tecnologie. Io ritengo invece, che nonostante tutti i problemi che la loro giovinezza può portare, ha assolutamente senso sviluppare una nuova applicazione in WPF in sinergia con il pattern MVVM. Questo perché la versatilità e l'espandibilità, sia nel senso delle caratteristiche, che del layout dell'applicazione portano un vantaggio intrinseco che supera notevolmente qualsiasi problema. 81
  • 82. Appendice uno: come scrivere un'applicazione WPF Quest'appendice contiene alcuni suggerimenti per scrivere applicazioni WPF. XAML Conventions XAML è XML e come tutti i formati XML ha una formattazione standard che è bene utilizzare. Ci sono, però, un paio di cose da prendere in considerazione. Utilizzare x:Name al posto di Name Nonostante entrambi gli attributi facciano la stessa cosa, Name può essere utilizzato solo per alcuni elementi, mentre x:Name funziona per tutti gli elementi (eccetto gli elementi in un resource dictionary). Quando si guarda una lunga lista di attributi di un elemento, gli attributi che cominciano con il prefisso "x:" tendono ad essere più facili da vedere. Per rendere più facile trovare il nome di un elemento è sempre meglio utilizzare x:Name. Posizionare il primo attributo di un elemento nella riga sotto il nome dell'elemento Questa regola esiste perché la maggior parte degli editor XML indenta ogni riga allo stesso livello del primo attributo. Per esempio: <StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="3" Background="{TemplateBinding Background}"> <Button Content="Hello" Background="{StaticResource Brush_BtnBackground}" Width="300" /> <ImageButton Source="{StaticResource Image_Copy}" Width="16" Height="16" /> 82