implementazione del design pattern mvc per la

Transcript

implementazione del design pattern mvc per la
 UNIVERSITA’ POLITECNICA DELLE MARCHE FACOLTA’ DI INGEGNERIA Corso di laurea magistrale in Ingegneria Informatica TESI DI LAUREA IMPLEMENTAZIONE DEL DESIGN PATTERN MVC PER LA REALIZZAZIONE DI UN’APPLICAZIONE RIA SVILUPPATA CON LA PIATTAFORMA MICROSOFT SILVERLIGHT 2 RC1 Candidato: Relatore: Michelangelo de Muzio Prof. Aldo Franco Dragoni
Anno Accademico 2007/2008 Sommario INTRODUZIONE ............................................................................................................5 1. IL CONTESTO APPLICATIVO ....................................................................................7 1.1 INTRODUZIONE ................................................................................................7 1.2 L’ARCHITETTURA SOFTWARE ...........................................................................8 1.3 ARCHITETTURA FISICA E PIATTAFORMA TECNOLOGICA ............................... 20 1.4 IL MOTORE DI WORKFLOW ........................................................................... 23 1.5 LA PROFILATURA ........................................................................................... 30 1.6 LA REPORTISTICA .......................................................................................... 34 2. RICH INTERNET APPLICATIONS ........................................................................... 37 2.1 LE MOTIVAZIONI ........................................................................................... 37 2.2 BENEFICI DELLE APPLICAZIONI RIA ............................................................... 38 2.3 DIFETTI DELLE RICH INTERNET APPLICATIONS .............................................. 40 2.4 SILVERLIGHT .................................................................................................. 42 2.4.1 SILVERLIGHT VS FLASH ............................................................................... 45 2.4.2 SILVERLIGHT VS AJAX ................................................................................. 49 2.4.3 SILVERLIGHT VS JAVAFX ............................................................................. 50 2.4.4 L’ARCHITETTURA ........................................................................................ 52 2.4.5 IL CLR ......................................................................................................... 56 2.4.6 L’ISOLATED STORAGE ................................................................................ 59 2.5 WINDOWS PRESENTATION FOUNDATION .................................................... 62 2.5.1 XAML .......................................................................................................... 62 2.6 LE TECNOLOGIE RIA ...................................................................................... 63 3. I PATTERN ............................................................................................................ 67 3.1 LE MOTIVAZIONI ........................................................................................... 67 3.2 I PATTERN E LA PROGRAMMAZIONE OBJECT ORIENTED .............................. 72 3.3 TIPOLOGIE DI PATTERN ................................................................................. 76 3.4 I DESIGN PATTERN ........................................................................................ 77 3.4.1 I DESIGN PATTERN CREAZIONALI ............................................................... 81 3.4.2 I DESIGN PATTERN STRUTTURALI .............................................................. 97 3.4.3 I DESIGN PATTERN COMPORTAMENTALI ................................................ 112 3.5 I PATTERN DI PROCESSO ............................................................................. 127 3.5.1 TASK PROCESS PATTERN .......................................................................... 129 3.5.2 STAGE PROCESS PATTERN ....................................................................... 132 3.5.3 PHASE PROCESS PATTERN ....................................................................... 137 3.5.4 BENEFICI .................................................................................................. 141 4. IL PATTERN MVC ............................................................................................... 142 4.1 CARATTERISTICHE DEL PATTERN ................................................................ 142 4.1.1 LE VARIANTI ............................................................................................. 145 4.1.2 BENEFICI E SVANTAGGI ............................................................................ 148 4.2 MVC E LE WEB APPLICATION ...................................................................... 150 5. MVC PER APPLICAZIONI SILVERLIGHT ............................................................... 156 5.1 LE MOTIVAZIONI ......................................................................................... 156 5.2 IMPLEMENTAZIONE DEL PATTERN ............................................................. 156 5.3 I COMPONENTI ........................................................................................... 161 5.3.1 IL MODEL LOCATOR ................................................................................. 161 5.3.2 GLI EVENTI DI BUSINESS .......................................................................... 163 5.3.3 I COMMANDS .......................................................................................... 166 5.3.4 I DELEGATES ............................................................................................. 170 5.3.5 IL FRONTCONTROLLER ............................................................................. 171 6. WEB APPLICATION PER FORM DINAMICHE ...................................................... 175 6.1 STRUTTURA DELL’APPLICAZIONE ................................................................ 177 6.2 L’APPLICATION CLASS ................................................................................. 180 6.3 L’APPLICATION CORE .................................................................................. 194 6.3.1 SEZIONE ASCX .......................................................................................... 206 6.3.2 PARAMETRI DEFAULT GERARCHIE ........................................................... 212 6.3.3 PERSONALIZZAZIONI ASCX ....................................................................... 218 6.3.4 SEZIONE DETTAGLI ................................................................................... 233 6.3.5 PARAMETRI PERSONALIZZAZIONE ASCX .................................................. 239 6.3.6 GESTIONE PARAMETRI ............................................................................. 246 6.3.7 TEST STRUTTURA ..................................................................................... 249 6.4 DYNAMIC FORM PREVIEW .......................................................................... 254 6.5 MIGRAZIONE ALLA RTW DI SILVERLIGHT .................................................... 288 CONCLUSIONI .......................................................................................................... 290 BIBLIOGRAFIA .......................................................................................................... 292 RINGRAZIAMENTI .................................................................................................... 295 INTRODUZIONE Con questa tesi si intende presentare le motivazioni e le fasi che hanno condotto alla individuazione ed implementazione di un pattern in grado di fornire le linee guida per la realizzazione di applicazioni Silverlight. Quest’ultima è una piattaforma utilizzata per la realizzazione di applicazioni web definite ‘rich interactive’, ossia applicazioni che consentono di spostare lato client parte della processazione delle applicazioni e offrono agli utenti la possibilità di eseguire delle operazioni molto interattive. Quello implementato è un pattern già ampiamente diffuso nell’ambito informatico, il Model‐View‐Controller. Tale pattern è stato implementato come una libreria che contiene tutte le classi necessarie per il suo utilizzo all’interno delle applicazioni. Altro intento della tesi è quello di mostrare un caso d’uso dell’implementazione realizzata. Vengono così illustrati il processo di sviluppo e le metodologie impiegate nella realizzazione di una parte di un’applicazione software utilizzata in ambito aziendale. L’applicazione web in questione ha lo scopo di creare e gestire delle form dinamiche, ossia delle form il cui contenuto può essere modificato in un qualunque momento a seconda delle esigenze degli utenti che le adoperano. La modifica dell’aspetto di una form può riguardare semplici operazioni di drag and drop o di ridimensionamento dei suoi componenti, l’aggiunta di un nuovo campo in una determinata fase d’esecuzione, oppure una variazione dei valori di certi parametri. L’applicativo comunica con un sistema molto più grande che gestisce la fornitura di servizi ai clienti da parte di una azienda. Le operazioni che l’applicazione deve permettere di gestire la fanno rientrare all’interno della categoria di applicazioni RIA; per questo motivo è stata utilizzata per il suo sviluppo la piattaforma Silverlight. Poiché la decisione di realizzare una particolare implementazione del pattern MVC è nata dalla necessità di codificare l’applicazione di gestione delle form dinamiche seguendo delle linee guida ben precise, ritengo opportuno presentare già all’interno del primo capitolo di questa tesi quello che è il contesto all’interno del quale si colloca l’applicazione. Viene dunque descritta l’architettura fisica e software del sistema con cui l’applicativo deve comunicare, e vengono poi illustrate la piattaforma tecnologica e le modalità con cui sono svolte alcune delle sue principali funzionalità. Nel secondo capitolo vengono spiegate le problematiche che hanno portato a riscrivere l’applicazione, realizzata precedentemente in ASP.NET, come un’applicazione RIA e a servirsi di uno strumento come Silverlight. A tal proposito viene fornita una panoramica sulla piattaforma Silverlight utilizzata nella realizzazione dell’applicativo per la gestione di form dinamiche, descrivendone l’architettura e i principali componenti, il suo ambito di utilizzo, confrontandola con altre piattaforme in commercio come Adobe Flash Player o JavaFX e con altre tecnologie come Ajax. Vengono inoltre descritte le caratteristiche e i difetti delle applicazioni RIA realizzabili con il plugin, volgendo brevemente lo sguardo al sistema Windows Presentation Foudation da cui esso nasce. All’inizio del terzo capitolo vengono illustrate le motivazioni che spingono a fare uso di pattern, e viene fornita una descrizione riguardante le origini e la loro evoluzione in generale, cercando di illustrarne la struttura e gli ambiti di utilizzo. Successivamente, vengono presentate varie tipologie di pattern, soffermandosi con maggiore attenzione sulla tipologia dei design pattern che, con molta probabilità, è quella più diffusa. A tal riguardo vengono analizzati i principali pattern creazionali, strutturali e comportamentali. Viene poi discusso il ruolo dei pattern all’interno della programmazione orientata agli oggetti e viene presentata una particolare tipologia di pattern fondamentale in ambito aziendale, ovvero i pattern di processo. Vengono dunque descritte le tre principali tipologie di pattern di processo: task, stage, e phase process pattern. Nel quarto capitolo viene illustrato e descritto accuratamente il pattern MVC, di cui verrà realizzata una particolare implementazione, descrivendone le caratteristiche salienti, le varianti, i benefici e gli svantaggi, anche per quel che riguarda il suo utilizzo all’interno delle applicazioni web. Nel quinto capitolo, poi, vengono analizzati i motivi che hanno portato alla scelta del pattern MVC per la realizzazione di una particolare implementazione che fosse adeguata per lo sviluppo di applicazioni, come quella in questione, realizzate con la piattaforma Silverlight. Importante è poi la descrizione che segue dell’implementazione del pattern MVC come libreria di classi, in cui viene definita la sua ossatura principale e vengono descritti in maniera dettagliata tutti i suoi principali componenti. Nell’ultimo capitolo viene, infine, presentato l’applicativo web di gestione delle form dinamiche, descrivendo quelle che sono le sue parti principali e soffermandosi particolarmente sulla realizzazione della Preview, ovvero un controllo custom che consente di visualizzare ed eseguire sulla form dinamica scelta le operazioni interattive citate in precedenza. Alla fine del capitolo vengono riportate le modifiche necessarie a migrare l’applicazione creata sulla release definitiva del plugin. 1. IL CONTESTO APPLICATIVO 1.1 INTRODUZIONE Si vuole realizzare un applicativo che venga utilizzato da un’azienda che sviluppa software gestionali per offrire ai clienti la possibilità di personalizzare una Web application da loro commissionata attraverso l’uso di form dinamiche. Con questo termine si intendono quelle form il cui aspetto può essere in ciascuno stadio di esecuzione dell’applicazione diverso da quello che avrebbe in un altro stadio di esecuzione. Molto spesso i clienti che commissionano delle applicazioni hanno la necessità di modificare l’aspetto e il contenuto della loro applicazione perché magari è stata introdotta una funzionalità nuova che si vuole implementare, o è necessario introdurre in una form un nuovo campo che serva a contenere ad esempio anche l’indirizzo e‐mail di una persona oltre ai suoi dati anagrafici, oppure si desidera che in un determinato momento un certo campo non sia visibile. Tutto ciò deve essere fatto senza dover ricontattare l’azienda che ha fornito il prodotto per effettuare le modifiche desiderate poiché, oltre ad essere dispendioso economicamente, ciò sarebbe dispendioso anche in termini di tempo; nel caso peggiore di fallimento dell’azienda fornitrice, l’applicativo non può essere più modificato. Viene così offerta al cliente la possibilità di scegliere l’aspetto di una determinata form in un qualsiasi stadio d’esecuzione; l’utente deve semplicemente aggiungere o rimuovere elementi da un Panel grafico attraverso semplici operazioni di drag‐and‐drop, settare le proprietà di un determinato componente all’interno di finestre popup, e ridimensionare gli elementi grafici presenti sul Panel attraverso il semplice utilizzo del mouse. L’applicazione da realizzare deve comunicare con un sistema molto più grande e complesso residente su un server che si occupa della gestione delle procedure di fornitura di servizi ai clienti. Di tale sistema viene ora descritta l’architettura software, la piattaforma tecnologica utilizzata, e alcuni tra i principali componenti. 1.2 L’ARCHITETTURA SOFTWARE Viene ora presentata quella che è l’architettura software del sistema con cui l’applicazione Silverlight realizzata deve comunicare, tenendo presente, fin da subito, che si tratta di un sistema distribuito altamente scalabile e flessibile che può essere facilmente calato in scenari di dispiegamento diversi ed appropriati. L’architettura tiene conto inoltre fin da subito, per quanto possibile, degli aspetti legati alle prestazioni dell’intero sistema sia in termini di tempi di esecuzione che di risorse utilizzate. L’architettura è basata su una tipica struttura a livelli e a moduli che prevede quindi una stratificazione software sia verticale che orizzontale. La separazione dei livelli e dei moduli è fondamentale per garantire il più possibile l’autonomia e la modularità delle componenti sia in fase di sviluppo che in fase di dispiegamento e manutenzione del sistema. Lo sforzo di mantenere una struttura stratificata e separata, anche a livello logico‐funzionale, facilita l’attività progettuale volta ad ottenere un modello coerente e pone le basi essenziali per una buona gestione dell’attività di sviluppo. La stratificazione software è una regola generale che deve rispettare l’intero sistema, visto nella sua globalità, e che deve essere applicata anche all’interno dei singoli moduli funzionali che lo compongono. Esistono inoltre degli aspetti in un sistema complesso a carattere distribuito, dove ciascuna componente può essere dispiegata indipendentemente, di cui si deve tener conto a qualsiasi livello, e fra questi vi è senz’altro quello legato alla sicurezza e quindi alle politiche di autenticazione, autorizzazione e profilazione. Questo è necessario al fine di garantire gli obiettivi che prevedono la scomposizione del sistema e la sua integrazione anche con componenti e moduli esterni terzi. Altri due aspetti altrettanto importanti in questo senso sono quello legato alle comunicazioni tra le componenti, visto l’approccio di base orientato ai servizi, e quello legato alle funzioni di monitoraggio che ci permettono di tenere sotto controllo l’andamento del sistema in esercizio sotto vari aspetti, non ultimo quello che permette di garantire dei livelli di servizio prestabiliti (SLA). I livelli logici principali secondo i quali è strutturato il sistema, e ciascun modulo che lo compone, da un punto di vista software architetturale, sono tipicamente tre: presentazione, applicativo e dati, come rappresentato nella seguente figura: Figura 1 – Livelli principali del sistema Il livello dati è in realtà un EIS (Enterprise Integration Services) layer in quanto si occupa anche dell’integrazione di servizi esterni. I Calling Services rappresentano sistemi, servizi o componenti esterni al dominio applicativo del sistema che intendono usufruire delle logiche di business da esso esposte; tra questi vi è lo schedulatore di task. I Services rappresentano sistemi, servizi o componenti esterni utilizzati dal sistema con i quali esso interagisce secondo logiche di B2B ed EAI. LIVELLO PRESENTAZIONE Il livello presentazione contiene i componenti che sono necessari per permettere all’utente di interagire con l’applicazione. In particolare questo livello ha il compito di visualizzare i dati, permettere all’utente di inserire dati in maniera assistita e di catturare eventi innescati dall’utente in risposta dei quali deve essere attivata un’azione da parte del sistema stesso. Il sistema in questione utilizza di base l’interfaccia web per l’interazione con l’utente e quindi i componenti che si occupano di questo livello sono dispiegati in parte sul client all’interno del browser internet utilizzando le tecnologie DHTML, Javascript e Ajax ed in parte lato server tramite pagine web implementate con la tecnologia ASP.NET. In alcuni casi particolari il sistema utilizza forme di interfaccia differenti ad esempio per permettere l’uso di dispositivi mobili e/o client leggeri oppure utilizza una interfaccia document‐based che sfrutta le funzionalità di applicazioni client di office‐
automation di larga diffusione quali MS Word, MS Excel e Adobe Acrobat. CLIENT COMPONENTS Il sistema prevede l’utilizzo di componenti dispiegati sul Client, all’interno del browser internet, al fine di soddisfare il prerequisito che richiede la massima interattività dell’interfaccia utente minimizzando il caricamento delle pagine dal server. In particolare si fa uso di: • componenti HTC, che incapsulano aspetto e comportamento fornendo in modo standardizzato funzionalità di rappresentazione e validazione dei dati; • oggetti e librerie di funzioni Javascript comuni utili allo sviluppo della logica di presentazione; • tecnologia Ajax, che permette comunicazioni asincrone con i rispettivi componenti server per la trasmissione dei dati in formato xml tramite il protocollo http/https. Figura 2 – Livello presentazione UI COMPONENTS Le pagine ASP.NET hanno una struttura standardizzata e sono costruite a partire da templates prefissati in base alla tipologia (pagine standard, master/detail, popup, pagine di ricerca ecc). Tra le funzionalità comuni vi è senz’altro il controllo dell’autorizzazione dell’utente all’utilizzo della funzione che essa implementa, il recupero dei dati dell’utente e di contesto mantenuti in sessione, l’inizializzazione dei controlli consumatori dei dati che compongono l’interfaccia in base al profilo dell’utente ed il primo reperimento dei dati da visualizzare. Esiste una vasta gamma di componenti di interfaccia lato server (Web User Sever Controls) che fanno parte del framework per la suite di prodotti a cui fa riferimento il sistema. Particolare attenzione è data alla funzione di profilazione verticale (la profilazione orizzontale sui dati è implementata direttamente al livello dati tramite il VPD di Oracle) che, implementata da un apposito componente, permette di controllare l’aspetto ed il comportamento di ciascun controllo presente sulla pagina, sia esso una semplice label, una textbox, una combo ecc., in base al profilo dell’utente, alla funzionalità che si intende erogare e al dato a cui il controllo è collegato. Per ciascun controllo è infatti possibile ad esempio determinarne la visibilità, o se questo debba essere di sola lettura, la lingua ecc. UI PROCESS E WORKFLOW COMPONENTS Oltre ai componenti di interfaccia, l’architettura del livello di presentazione prevede, lato server, l’utilizzo di componenti più evoluti che sono dedicati alla gestione di processi complessi di interazione con l’utente. Ad esempio questo tipo di componenti è utile per regolare interazioni “lunghe” che non si esauriscono all’interno di una singola pagina e che richiedono la gestione di uno stato e di un flusso di lavoro articolato. Vi sono inoltre dei componenti che sono dedicati all’interazione con il motore di workflow e che servono a pilotare l’interfaccia utente in base alle condizioni di stato dei processi in essere. I componenti del livello di presentazione al fine di espletare i loro compiti interagiscono con il back‐end tramite le funzionalità esposte dal livello di business in base ad una logica service‐
oriented utilizzando l’interfaccia tipica dei Web Services. Non è consentito, al fine di mantenere intatta la logica che sta alla base della stratificazione software a livelli, l’utilizzo diretto dei componenti di livello inferiore, ad esempio di accesso ai dati. SICUREZZA L’accesso alle pagine web è protetto tramite form‐authentication basata tipicamente su username e password, utilizzando eventualmente un servizio di SSO (Single Sign On) esterno. Tra gli aspetti di sicurezza e privacy dei dati fanno parte le funzionalità di profilatura di cui si è parlato sopra. COMUNICAZIONI Le comunicazioni tra client e server avvengono esclusivamente tramite il protocollo di trasporto http/https, mentre le comunicazioni tra i componenti server del livello di presentazione ed il livello di business sottostante avvengono utilizzando il protocollo di comunicazione SOAP‐Document‐Http secondo il paradigma standard dei Web Services. PRESTAZIONI In questo livello, al fine di massimizzare le performances è necessario seguire almeno le seguenti regole: • rilevante utilizzo delle tecniche di caching lato server, soprattutto per i dati poco volatili (è importante in ogni caso tenere presente il limite di memoria utilizzabile da una Web Application); • uso delle tecniche di paginazione lato server per la visualizzazione di insiemi di dati consistenti; in generale è auspicabile che sul client arrivino solamente i dati strettamente necessari. Le suddette tecniche di paginazione interagiscono direttamente con il livello dati; • uso di comunicazioni asincrone tra client e server e per le chiamate a Web Services lato server ed in generale per tutte quelle attività che non coinvolgono direttamente la CPU (lettura e scrittura di files, operazioni che coinvolgono l’utilizzo della rete, ecc); A livello sistemistico inoltre, in fase di dispiegamento, occorre considerare l’opportunità di utilizzare la funzionalità di compressione http offerta da IIS 6 e porre particolare attenzione alla configurazione dei parametri del framework .NET ed alla sua interazione con IIS 6. LOGGING E MONITORAGGIO Dato che il livello di presentazione interagisce direttamente con l’utente è importante strumentalizzare i componenti che ne fanno parte per tenere sotto controllo i tempi di risposta almeno per le funzionalità più rilevanti. I dati raccolti, integrati con quelli di natura sistemistica (framework .NET, IIS 6, ecc), sono utili per dimensionare ed eventualmente correggere le modalità di dispiegamento e la configurazione della piattaforma fisica. LIVELLO APPLICATIVO Il livello applicativo, detto anche livello intermedio, costituisce la parte core del sistema. In esso sono implementate tutte le funzionalità che caratterizzano il sistema dal punto di vista logico‐funzionale. Queste funzionalità vengono utilizzate dal livello di presentazione e da servizi esterni tramite apposite interfacce. La progettazione delle funzionalità e dei componenti che costituiscono questo livello è tale da essere totalmente indipendente dal tipo di consumatore che le utilizza. Questo livello deve quindi essere un modulo completamente autonomo in grado di fornire servizi a livelli di presentazione diversi e con interfacce utente diversificate (web, desktop, mobile, document, ecc.). Allo stesso modo i componenti sono implementati in modo da essere indipendenti dal livello dati sottostante di cui richiede i servizi. Per soddisfare al meglio questi requisiti, per questo livello è richiesta una ulteriore stratificazione software che prevede l’utilizzo dei moduli di Business Entities, Business Components, Business Workflow e di un sottolivello Service Interfaces, come evidenziato dalla seguente figura. Figura 3 – Livello applicativo BUSINESS ENTITIES Questo modulo implementa la modellazione dei dati e delle entità coinvolte nella logica applicativa del sistema. La modellazione dei dati è fatta da un punto di vista logico e funzionale ed astrae, ad esempio tramite tecniche di aggregazione, le modalità con cui i dati vengono realmente rappresentati a livello fisico. L’approccio con cui viene realizzata la modellazione è in questo caso puramente Object Oriented (OOP); è infatti compito dei componenti di business e del sottolivello di interfaccia esporre le funzionalità secondo una logica Service Oriented (SOA). Le entità, implementate tramite classi .NET, sono a volte oggetti complessi, e le istanze rappresentano gli oggetti e i dati che vengono passati da un livello e/o da un modulo all’altro e quindi anche a livello di presentazione. Per questi motivi è importante che le classi abbiano una natura serializzabile, in modo che le loro istanze possano essere utilizzate con interfacce tipo quella dei Web Services, e che siano disegnate in modo tale che i metodi, che ne specificano il comportamento, non richiedano l’accesso ai dati del livello sottostante. Di fatto quindi le business entities sono poco più che delle definizioni di tipo e gli eventuali metodi devono avere una implementazione autoconsistente, ovvero che non esce al di fuori della classe stessa (come accedere al livello dati ecc.) dato che possono essere usate a qualsiasi livello dell’architettura. E’ preferibile, ma non necessario che le business entities siano implementate tramite classi custom, in alternativa si può utilizzare ad esempio un semplice xml o un dataset tipizzato o meno. BUSINESS COMPONENTS Questi componenti sono dedicati ad implementare le regole di business che realizzano la logica applicativa del sistema. Essi utilizzano, per espletare i propri compiti, gli oggetti che sono stati definiti nelle business entities. Questi componenti sono disegnati secondo una logica funzionale e quindi seguono un approccio Service Oriented. I componenti di business sono progettati in modo da esporre le proprie funzionalità in modo totalmente indipendente dalla struttura dei dati, sono trasparenti rispetto i servizi che utilizzano per portare a termine i propri compiti e, soprattutto, devono essere transazionalmente consistenti. Ciascuna funzionalità è implementata in modo da costituire una transazione atomica che al suo termine lasci sempre i dati in una condizione consistente. Se ciò non è possibile, ad esempio nel caso sia necessario utilizzare un servizio esterno o un componente del quale non è possibile controllare la transazione, occorre prevedere opportune azioni di compensazione nell’eventualità che si verifichi un fallimento dell’operazione. Nel caso occorra implementare una funzionalità complessa che richieda ed utilizzi altre funzionalità già implementate, è necessario prevedere dei meccanismi che permettano di passare alle sottoattività il contesto transazionale comune. BUSINESS WORKFLOW Esistono, a causa della natura complessa delle funzionalità implementate all’interno del sistema, situazioni particolarmente articolate che hanno le seguenti caratteristiche: ƒ
ƒ
ƒ
ƒ
ƒ
Richiedono comunicazioni di tipo asincrono; Coinvolgono processi di lunga durata (Long Running Transaction); Coinvolgono attori, sistemi e servizi esterni anche al dominio applicativo; Necessitano della correlazione tra i messaggi scambiati; Richiedono l’aggregazione di diverse funzionalità per portare a termine il processo. In questi casi il sistema utilizza componenti e servizi specializzati che rispondono alle caratteristiche di Workflow, EAI e B2B. SERVICE INTERFACES Questo livello implementa le interfacce necessarie ad esporre le funzionalità realizzate tramite i componenti di business e di workflow. I servizi o componenti consumatori sono rappresentati dai servizi esterni o dai componenti del livello di presentazione del sistema. Il livello delle interfacce non contiene logica applicativa né gestione delle transazioni, ma svolge semplicemente le operazioni necessarie alla particolare tecnologia che sta alla base dell’interfaccia stessa. Il sistema di norma espone i propri servizi tramite l’interfaccia standard dei Web Services e le comunicazioni avvengono con scambio di messaggi SOAP, così come previsto dai requisiti architetturali inizialmente stabiliti. Questo tipo di interfaccia prevede lo scambio dei dati in formato xml, quindi è compito della sua implementazione svolgere le funzioni di serializzazione, deserializzazione e trasformazione in genere delle entità di business. Dato che questo tipo di interfaccia è accessibile a servizi o sistemi esterni al dominio applicativo è necessario implementare al suo interno anche le apposite politiche di autenticazione ed autorizzazione. Lo strato software delle interfacce può essere omesso nel caso si utilizzino i componenti di business direttamente all’interno del livello di presentazione (in‐process), oppure possono essere usate interfacce differenti; la tecnologia .NET Remoting ad esempio, avendo la possibilità di utilizzare come trasporto TCP anziché HTTP e scambiando dati in formato binario anziché in xml, è senz’altro più efficiente, ma sicuramente non standard e quindi non utilizzabile per le comunicazioni con sistemi esterni. SICUREZZA Le problematiche di sicurezza in questo livello vanno affrontate nell’ambito delle interfacce. A tale scopo è possibile securizzare il canale di trasporto http, ad esempio attraverso i protocolli HTTPS (Secure HyperText Transfer Protocol) e SSL (Secure Socket Layer), ma, visto l’utilizzo di SOAP e Web Services è senz’altro preferibile securizzare direttamente i messaggi scambiati tramite lo standard ws‐
security. Tramite questo paradigma è infatti possibile effettuare l’autenticazione del chiamante, essere certi che il messaggio non è stato alterato durante il trasporto ed eventualmente, nel caso di scambio di messaggi che contengono dati particolarmente sensibili, è possibile procedere alla crittografia dei messaggi stessi. Questo ulteriore livello di stratificazione, implementato tramite la tecnologia MS Web Services Enhancement (WSE) è trasparente e quindi può essere utilizzato all’occorrenza senza impattare sulla implementazione delle stesse interfacce. E’ importante sottolineare che l’approccio basato su ws‐security, agendo a livello di messaggio anziché sul canale di trasporto, è indipendente dal canale e garantisce quindi la sicurezza dei messaggi in modo persistente anche al di fuori del canale utilizzato per il trasporto. Tuttavia i due sistemi non sono mutuamente esclusi e possono quindi essere usati congiuntamente. COMUNICAZIONI Per quanto riguarda l’aspetto delle comunicazioni, l’utilizzo di http/https come trasporto, permette di dispiegare il livello di presentazione ed il livello intermedio anche in sottoreti o zone demilitarizzate separate da firewall anche in contesti di reti geografiche. PRESTAZIONI Per quel che riguarda le prestazioni al fine di massimizzare le performance, a tale livello è necessario seguire almeno le stesse regole che erano state menzionate in precedenza per il livello di presentazione. LOGGING E MONITORAGGIO A questo livello sono implementate le procedure che si occupano del log di utilizzo dei moduli applicativi, nonché del tracciamento degli errori. E’ opportuno dotare queste routines di strumenti tali da poter analizzare i dati raccolti con gli strumenti forniti dalla piattaforma. LIVELLO DATI Nel livello dati viene implementato il modello fisico dei dati e le modalità di accesso. Il principale datasource del sistema è costituito dall’RDBMS; in esso è implementato il modello dei dati tramite l’uso di tabelle, viste, relazioni, vincoli di integrità ecc; non è escluso tuttavia l’utilizzo di datasources differenti (file system, files xml o ASCII di interscambio ecc.). La logica usata per accedere ai dati di ciascun datasource è incapsulata in data access logic components che forniscono metodi al livello applicativo per interrogare ed aggiornare i dati. I dati che questi componenti scambiano con il livello applicativo hanno lo stesso formato di quelli definiti nelle business entities del livello applicativo stesso. I data access logic components sono generalmente implementati senza stato e servono a separare la logica applicativa dalla logica di accesso ai dati ed hanno le seguenti caratteristiche: • forniscono tipicamente i metodi per creare, leggere, aggiornare e cancellare (CRUD) ciascuna business entity dell’applicazione nel database; • utilizzano un ulteriore sottolivello (data access helper) che serve a centralizzare la gestione delle connessioni e delle transazioni, nonché quanto di strettamente e specificatamente legato alle caratteristiche del datasource utilizzato (cioè Oracle); • l’implementazione di questi componenti nel sistema è caratterizzata dall’invocazione di stored procedures che migliorano le prestazioni e la manutenibilità del sistema; • si occupano di eseguire trasformazioni sui dati scambiati in input ed output; • nascondono le modalità di invocazione delle stored procedures e le peculiarità dei parametri da queste richiesti; • astraggono la struttura tabellare e normalizzata del database e forniscono quindi funzioni per manipolare dati che sono il risultato di un procedimento di aggregazione di più tabelle (business entities); • gestiscono e nascondono al livello applicativo le tecniche di gestione della paginazione dei dati sfruttando le potenzialità del database; • gestiscono le politiche di autenticazione e di sicurezza in genere per le comunicazioni con il DB; • traslano le eccezioni che provengono dall’RDBMS in eccezioni .NET che possono essere gestite ed interpretate dall’applicazione; Figura 4 – Livello dati Il sistema inoltre, tramite i componenti di accesso ai dati, delega all’RDBMS il compito di svolgere elaborazioni particolarmente intensive e che coinvolgono grandi quantità di dati. Vengono così di fatto sfruttate le potenzialità del linguaggio PL/SQL di Oracle per trasferire parti della logica applicativa, che hanno caratteristiche particolari, il più vicino possibile ai dati stessi al fine di migliorare le prestazioni e la manutenibilità dell’intero sistema. In modo astratto si può dire che alcuni metodi (data bound methods) dei Business Component sono implementati in stored procedure di Oracle. Sempre a questo livello, il sistema implementa le logiche di profilatura orizzontale dei dati tramite alcune funzionalità offerte dall’RDBMS. INTEGRAZIONE CON SERVIZI ESTERNI Altri componenti, denominati services agents, svolgono una funzione logicamente analoga, ma con caratteristiche diverse dai componenti di accesso ai dati, e servono a gestire le semantiche di comunicazione con servizi esterni all’applicativo. Questi componenti vengono ad esempio utilizzati dai workflow business components del livello applicativo per colloquiare con sistemi workflow, EAI e B2B esterni ed hanno le seguenti caratteristiche: ƒ Incapsulano l’accesso al servizio; ƒ Gestiscono lo stato e la semantica dei messaggi scambiati, soprattutto nel caso di transazioni lunghe; ƒ Isolano il processo applicativo, in termini di formato e schema dei dati scambiati, e operano le opportune trasformazioni necessarie; ƒ Gestiscono le politiche di accesso ed autorizzazione all’utilizzo dei servizi remoti; A livello dati il sistema implementa le logiche di profilatura orizzontale dei dati tramite alcune funzionalità offerte dall’RDBMS. 1.3 ARCHITETTURA TECNOLOGICA FISICA E PIATTAFORMA Vengono di seguito presi in esame alcuni scenari di dispiegamento per l’architettura logica esposta precedentemente con riferimento alla piattaforma tecnologica utilizzata. In particolare vengono analizzate le possibilità di mappare i livelli logici in livelli fisici al fine di massimizzare la scalabilità del sistema all’aumentare del bacino di utenza, mantenendo la consistenza dei dati. Il sistema in questione ha la capacità di scalare sia orizzontalmente che verticalmente. La scalabilità verticale è realizzata mappando i livelli logici su livelli fisici differenti, mentre la scalabilità orizzontale è ottenuta ridondando alcuni livelli logici in altrettanti livelli fisici. Scalando in modo orizzontale si ottiene anche una ridondanza di risorse che garantisce una certa tolleranza ai guasti ed alle attività di manutenzione. La scalabilità orizzontale può essere applicata sia al livello presentazione che al livello applicativo replicando le componenti software su macchine distinte e sfruttando i servizi di Network Load Balancing (NLB) della piattaforma tecnologica. Nel primo caso si hanno più macchine che svolgono il ruolo di web server, su ciascuna delle quali è replicata l’applicazione ASP.NET di front‐end; nel secondo caso più macchine sono deputate all’esecuzione delle componenti software di back‐end. In una applicazione come questa, dove l’interfaccia utente è web‐based, le componenti client non necessitano di alcuna fase di dispiegamento, mentre gli altri componenti (UI components e UI process components) vengono generalmente dispiegati sui web server di front‐end. Per quel che riguarda i componenti applicativi (con interfaccia a Web Services), essi possono essere dispiegati sugli stessi server di front‐end oppure su application server dedicati. Volgendo lo sguardo al livello dati, c’è da dire che la parte del livello dati che contiene i datasources viene generalmente dispiegata su uno o più database server in configurazione cluster (per aumentare la disponibilità e l’affidabilità del sistema), mentre il sottolivello di accesso ai dati (DAL) segue il dispiegamento del livello applicativo essendo separato solo logicamente da questo. La figura seguente mostra uno scenario in cui il livello di presentazione, ad escluzione dei componenti client, il livello applicativo e le componenti di accesso ai dati sono collassati in un unico livello fisico. In questa situazione il livello fisico in oggetto è costituito da un Web Server di front‐end o da una batteria di Web Server nel caso si ricorra alla scalabilità orrizzontale tramite NLB. Figura 5 – Architettura fisica a tre livelli La figura seguente mostra uno scenario in cui il livello applicativo è remotizzato rispetto al livello presentazione essendo dispiegato su un livello fisico dedicato, costituito da un Application Server di back‐end. Nel caso si utilizzi una batteria di Application Server in questo scenario viene evidenziata la scalabilità verticale ed orizzontale del sistema. In questo tipo di scenario, in cui viene utilizzata la scalabilità verticale, è maggiormente rilevante ai fini delle prestazioni, fare ampio uso delle tecniche di caching per minimizzare il traffico di rete dei dati da un livello all’altro. Figura 6 – Architettura fisica a quattro livelli In particolari situazioni la scalabilità orizzontale del livello applicativo può essere ottenuta anche dedicando alcuni server all’esecuzione di specifici servizi. Per quanto riguarda le comunicazioni tra un livello fisico e l’altro, essendo questi potenzialmente separati da firewall, occorre tener presente che è sufficiente permettere traffico http tra i Clients, i Web Server e gli Application Server, mentre è necessario permettere il traffico sulla porta TCP di accesso all’RDBMS per le comunicazione tra l’Application Server ed il Database cluster. L’architettura è perfettamente adattabile anche in casi in cui il livello di presentazione sia realizzato con interfacce utente differenti da quella web (es. document‐based, desktop o mobile); in tali situazioni il livello presentazione è dispiegato interamente sui clients. PIATTAFORMA TECNOLOGICA La piattaforma tecnologica di riferimento per i livelli presentazione, applicativo ed i componenti di accesso ai dati è Microsoft, mentre il livello dati è implementato con l’RDBMS di Oracle. I componenti di accesso ai dati, del livello applicativo e del livello di presentazione sono implementati tramite class library .net e quindi si basano sul framework .NET 2.0. Le pagine web di front‐end ed i Web Services del livello applicativo sono implementati tramite la tecnologia ASP.NET 2.0 e quindi utilizzano la piattaforma basata su MS Windows Server 2003, Internet Information Server 6.0 ed il framework .NET 2.0. Per gli aspetti di sicurezza relativi ai Web Services si fa riferimento, dove utilizzata, all’implementazione MS di ws‐security inclusa in WSE 3.0. Il sistema di reportistica è basato sui componenti .NET di Crystal Reports ed il formato di output utilizzato di norma è Acrobat PDF, mentre per la modulistica personalizzabile è stato realizzato un modulo che sfrutta le potenzialità del formato xml di MS Word 2003. Nella figura sottostante è schematizzato lo stack tecnologico di riferimento. Figura 7 – La piattaforma tecnologica 1.4 IL MOTORE DI WORKFLOW Un workflow è una rappresentazione logica costituita da passaggi volta a descrivere un processo reale. E' un concetto semplice che aiuta a schematizzare un'esigenza o un problema ed è l'approccio con il quale chi si avvicina al mondo della programmazione raffigura quello che sarà il codice da scrivere, indipendentemente dal linguaggio. Nel mondo moderno un workflow è spesso utile nella gestione documentale, caratterizzata dalla presenza di più attori quali autori, editori e moderatori che stendono, rifiutano o approvano un documento o un contenuto qualsiasi da pubblicare. Spesso le applicazioni richiedono una personalizzazione a seconda del cliente e delle logiche di business che possono variare frequentemente ed essere più o meno complesse. Anche nelle applicazioni web, il semplice navigare tra pagine legate tra loro, come può essere un portale di e‐commerce può essere rappresentato da un workflow costituito da vari passaggi quali la consultazione, la scelta del prodotto, l'immissione dei dati, il pagamento e la spedizione. Queste logiche sono molte volte simili tra loro e l'uso di componenti può non bastare per astrarre in modo completo ed abbracciare tutte queste esigenze. Ne consegue il ripetersi della stesura di codice spesso simile, alla quale diversi sviluppatori devono mettere mano, modificare e dedurre la logica di chi ha scritto in precedenza il codice. Il risultato è un applicativo poco manutenibile nel tempo, che richiede più risorse per lo sviluppo e poco personalizzabile per il cliente. ARCHITETTURA DI WINDOWS WORKFLOW FOUNDATION Data l’ampiezza degli scenari che richiedono l’utilizzo di un workflow e l'obiettivo di fornire una singola tecnologia in grado di supportarli tutti, il WWF è in realtà un framework composto da numerose interfacce ed estensioni collegate ad un nucleo base. Non è quindi un prodotto finito, ma si completa unendolo allo sviluppo specifico per l’applicazione. Figura 8 – Architettura dell’engine Windows Workflow Foundation Alla base dell’architettura c’è il “processo host”. Windows Workflow Foundation non viene eseguito in un proprio processo. WWF è invece un motore in‐process eseguito all’interno del processo host. Il processo host ha il compito di fornire un insieme di servizi a WWF. Una grande varietà di processi host sono disponibili nella piattaforma Windows tra cui applicazioni console, form windows, applicazioni web, servizi di windows e web services. In realtà qualsiasi processo eseguibile può ospitare il motore di runtime di WWF. Il livello superiore dell’architettura è rappresentato dal livello “runtime”. Il runtime engine si occupa sia dell’esecuzione del workflow che della gestione del suo ciclo di vita. L’ultimo livello dell’architettura è il “Workflow Model” ed è quello utilizzato dagli sviluppatori per interagire con il motore di workflow. Questo livello comprende i vari modelli di workflow, API e le activity. LIVELLO HOST Il “livello host” fornisce le interfacce tra WWF e un particolare host tramite i seguenti servizi: Comunicazione, Persistenza, Tracciamento, Timer, Threading e Transazione. Astraendosi dall’implementazione di questi servizi, WWF può sfruttare le caratteristiche di una specifica applicazione host. Di seguito viene descritto lo scopo di questi servizi: • Persistenza: alcuni workflow durano pochi secondi, ma altri possono richiedere l'interazione umana e durare anche fino ad un mese o essere infiniti. Perciò l'engine non può limitarsi al processo che lo ospita mantenendo tutto in memoria, ma deve persistere le informazioni riguardanti lo stato di un workflow in ogni step che lo costituisce e la coda che deve eseguire. • Threading: fornisce il controllo di esecuzione dei workflow, ottenendo così la facoltà di controllare con quanti thread può essere eseguito o semplificando l'esecuzione in modalità sincrona su un singolo thread. • Tracciamento: in molti scenari è importante la consultazione dello stato di un workflow e di cosa ha fatto in passato. Questo servizio ha lo scopo di memorizzare e restituire queste informazioni. • Transazione: ogni passaggio del workflow può dipendere da altri e formare un'operazione atomica che deve avere successo o fallire nel suo insieme. Al motore è quindi aggiunto un servizio di transazionalità per evitare problemi d'inconsistenza. • Timer: i workflow spesso necessitano di un evento per proseguire. Il timer fornisce un clock utilizzato per gestire queste attese. • Comunicazione: i workflow rilanciano e ricevono eventi o messaggi dalla propria applicazione host. Questi eventi risvegliano i workflow e li evolvono allo step successivo del processo. Windows Workflow Foundation fornisce un’implementazione di base di questi servizi. LIVELLO RUNTIME Il “livello Runtime” è il cuore di Window Workflow Foundation in quanto contiene i servizi critici richiesti per la gestione del workflow: • Esecuzione: pianifica le attività e gestisce gli eventi, le eccezioni, il tracciamento e le transazioni. • Tracciamento: genera gli eventi di tracking che vengono serializzati attraverso le interfacce di tracking. • Gestione stati: gestisce gli stati che possono essere resi persistenti attraverso l’interfaccia di persistenza. • Scheduler: pianifica l’esecuzione delle attività. • Rules: fornisce le policy per l’esecuzione delle funzionalità e la valutazione delle condizioni del CodeDOM (rappresentazione in memoria di codice sorgente indipendente dal linguaggio). LIVELLO MODEL Il “livello Model” supporta diversi modelli di workflow, activities e le principali API di programmazione. In Windows Workflow Foundation sono presenti due tipologie di flusso: •
Sequential Workflow: è un modello basato su attività sequenziali tra loro ed è rappresentabile con un percorso predeterminato. E' caratterizzato da un'attività iniziale e una finale e si completa con l'ultima attività. Figura 9 – Esempio di Workflow sequenziale •
State Machine Workflow: questo modello non dispone di un percorso determinato ed è basato sull'impostazione di ‘stati’. Questi possono ricevere eventi ed in base ad essi possono a loro volta scatenare altri eventi. Ogni stato contiene al suo interno delle attività da eseguire e può essere processato più volte o ritornare allo stato iniziale prima di raggiungere quello finale. Figura 10 – Esempio di Workflow a stati Le attività costituiscono il cuore del workflow e sono ciò che gli sviluppatori devono creare per fornire funzionalità da poter riutilizzare nei workflow da disegnare. Per lo sviluppo di workflow, Windows Workflow Foundation offre un designer integrato e templates per Visual Studio 2008 per poter disegnare e sviluppare attività specifiche per le varie applicazioni. L'ambiente offerto è un'interfaccia che genera codice nella classe sottostante così come per le attuali Winforms, con relativo codebehind per aggiungere codice personalizzato di iterazione delle activity e intercettazione degli eventi. Figura 11 – Disegno di un workflow Il risultato della compilazione è la creazione di una classe che eredita da SequentialWorkflowActivity o da StateMachineWorkflowActivity e popola le attività che la costituiscono valorizzando le relative proprietà. 1.5 LA PROFILATURA L’autorizzazione dell’utente permette di definire cosa l’utente può fare ed a quali dati può accedere. L’identificazione di cosa l’utente può fare viene definita tramite la profilatura verticale, mentre l’insieme dei dati ai quali l’utente può eccedere viene definita tramite la profilatura orizzontale. L’associazione dell’utente ai propri profili orizzontale e verticale avverrà attraverso l’appartenenza a ‘ruoli’ o ‘gruppi’ utente. In particolare si vogliono rispettare i seguenti requisiti: ƒ l’utente può appartenere a più ruoli; ƒ deve poter essere possibile personalizzare i diritti di un ruolo su uno specifico utente (togliendo dei diritti); ƒ in caso di modifica al ruolo le modifiche si devono ripercuotere su tutti i profili degli utenti appartenenti al ruolo. La profilatura orizzontale avviene direttamente sui dati ed è implementata tramite la funzionalità VPD di Oracle. La profilatura verticale avviene al livello presentazione sui componenti di interfaccia utente consumatori dei dati. PROFILATURA ORIZZONTALE L’implementazione è basata su Oracle VPD (Virtual Private Database) e, per garantire dei tempi di risposta accettabili del sistema, è necessario ristringere il più possibile l’insieme delle entità sulle quali applicare la profilazione. Gli elementi profilabili orizzontalmente vengono distinti in semplici e complessi. Gli elementi semplici verranno profilati associando il profilo orizzontale (livello) con l’elemento della tabella. Ovvero per ciascun elemento della tabella si definirà l’insieme dei profili orizzontali che avranno accesso ad esso. In fase di analisi requisiti dovranno essere individuate le entità per le quali è strettamente necessaria l’adozione della profilatura. Per evitare problemi di eccessivo carico elaborativo (tempi di risposta alti) è necessario avere un numero limitato di entità profilate. Gli oggetti complessi in fase di creazione verranno “battezzati” con un marker che è ricavato in funzione dell’attributo di profilazione orizzontale. In caso di modifica e/o nuovi inserimenti di elementi nell’oggetto verranno invece applicati i filtri sulle liste valori. Per l’accesso e la consultazione degli oggetti complessi e semplici è necessario poter definire delle gerarchie di profilatura (ad es. zona, gres). La profilatura orizzontale sui dati sarà sempre una profilatura piatta, ovvero ad un livello. Verrà però creata una tabella associativa chiamata profilo orizzontale o profilo di consultazione al fine di permettere una logica di consultazione di tipo gerarchico. Tale tabella sarà unica per tutti gli oggetti (semplici e complessi) che verranno profilati. In fase di consultazione gli elementi complessi e quelli semplici saranno filtrati in base alla suddetta tabella associativa. Figura 12 – Profilatura orizzontale PROFILATURA VERTICALE Il profilo verticale (o ruolo) definisce cosa l’utente può fare: ƒ albero di navigazione: menu e form alle quali sia possibile accedere; ƒ campi della form: visualizzazione e modificabilità dei campi della form; ƒ funzionalità/azioni: pulsanti ai quali sia possibile accedere. La profilatura dei campi è realizzata anche sui dati dei report. Lo strato di profilatura verticale consente di impostare a run‐time il valore di attributi dei componenti di front‐end in base a regole calcolate sulla base dei valori assunti da parametri di contesto e del profilo utente. Le Web forms sono il risultato dell’assemblaggio di componenti (Controlli html, User Control, Web Server Control) di tipologia finita; a ciascuna tipologia è associabile una classe che ne descrive gli attributi che si intende profilare (es. per un oggetto di tipo Button gli attributi gestiti sono la Caption del pulsante, il Tooltip, la Visibilità e l’Abilitazione). A ciascuna tipologia di componente possono essere associate più classi (il valore degli attributi profilabili è diverso, o regolato da regole differenti, per i diversi componenti di front‐end), mentre più componenti su diverse Web Forms di front‐end possono essere legati ad una stessa classe (hanno il medesimo comportamento indipendentemente dalla Web Forms che li contiene). Ciascun componente in una Web Forms, che si intende profilare, sarà marcato con un attributo custom (‘Profilato’) che riporterà il nome univoco della classe che lo descrive. Per quel che riguarda i valori possibili per un singolo attributo (es. l’attributo di Visibilità di un Pulsante) dichiarati sulla classe di appartenenza, essi saranno espliciti (es. true o false) o calcolati (nome di una funzione/regola). Ciascuna regola è di fatto un puntatore a funzione la cui interfaccia è fissa e prevede come parametri la classe di appartenenza e il profilo dell’utente; tutte le regole necessarie vengono codificate in una libreria a corredo. Una stessa regola può essere condivisa tra più classi anche di diversa tipologia (es. una regola che risolve le caption in base alla lingua presente sul profilo utente è valida sia per le componenti di tipo etichetta, ma anche per Caption di un pulsante o il Tooltip di una TextBox). Tutte le classi identificate nella procedura sono raccolte in un descrittore delle classi che è un file XML. Tale file viene caricato in cache per motivi di performance (acceduto frequentemente), la durata in cache è regolata da una dipendenza dal file system (ogni volta che il descrittore viene modificato sul file system, la cache considera scaduta la sua immagine e la ricarica consentendo così aggiornamenti del descrittore delle classi a caldo). Per quel che riguarda il funzionamento vero e proprio di tale meccanismo di profilatura, c’è da dire che in fase di progettazione vengono definite le classi e associate ai controlli presenti nelle Web Forms (viene indicato il valore dell’attributo ‘Profilato’ che corrisponderà al nome univoco della classe di appartenenza e i valori degli attributi profilabili all’interno di ogni singola classe). In fase di sviluppo poi è sufficiente trascinare in form un Web Server Control appositamente realizzato, il quale all’atto della richiesta della Web Form, tramite una procedura ricorsiva, cerca tutti i controlli soggetti a profilatura (marcati con l’attributo ‘Profilato’), ricerca nel Descrittore delle Classi la classe di appartenenza del controllo, eventualmente invoca la regola da applicare per risolvere un attributo passando le informazioni di contesto, ed infine effettua il rendering del controllo sulla base dei valori degli attributi. La modifica del comportamento di componenti di front‐end distribuiti su più Web Forms, ma associati alla stessa classe, richiede la modifica degli attributi della sola Classe di appartenenza, evitando così di effettuare modifiche sul codice di front‐end (disaccoppiamento degli strati). La modifica di un singolo oggetto si ripercuote in più punti dell’applicazione senza dover rieffettuare il deploy della stessa. Alcuni casi di utilizzo sono la Gestione del multilinguismo, la Personalizzazione delle etichette in funzione dell’installazione, l’attivazione o meno di alcune funzionalità in base al Ruolo di appartenenza dell’utente, la Centralizzazione delle routine per la validazione dei dati lato client, ecc. La definizione delle classi e l’associazione delle stesse ai vari componenti di front‐end è un compito svolto in fase di progettazione dell’applicativo. Per agevolare e semplificare questa fase si ricorre ad un’applicazione Win32 che può essere utilizzata in collaborazione tra più utenti e da remoto, che consente di individuare classi già presenti con avanzati strumenti di ricerca, o di crearne delle nuove rispettando i vincoli sintattici, così come modificarle. Essa consente inoltre di generare e rigenerare il descrittore delle classi a partire da uno store che è gestito in DB, consente di importare da un descrittore delle classi in XML dati in DB, consente la gestione della Profilatura Base, e consente di organizzare le classi secondo una logica di raggruppamento per una facile individuazione tra classi e componenti di front‐end che vi appartengono. Il descrittore delle classi funge da Data Dictionary in quanto in esso sono definiti in modo centralizzato i comportamenti di tutti i componenti consumatori di dati contenuti all’interno di tutte le pagine web di interfaccia utente. L’attuale struttura delle classi può inoltre essere facilmente estesa per accogliere ad esempio il riferimento all’htc che controlla il comportamento e l’aspetto di un determinato controllo associato alla classe che un certo dato rappresenta oppure per accogliere un link che identifichi una funzione, o meglio una pagina correlata al dato stesso. 1.6 LA REPORTISTICA All’interno dell’applicazione, diversi sono i sottosistemi che si occupano della reportistica e sono tutti collocati a livello di presentazione; il motore di reportistica è basato sui componenti .NET di Crystal Report. MODULO DI REPORTISTICA Il modulo si occupa di tutto quanto concerne i servizi di reportistica della Suite. Per Servizi di reportistica si intende: ƒ generazione dei report ƒ funzioni di esportazione ƒ funzioni di delivery Le principali caratteristiche sono: ƒ
ƒ
ƒ
ƒ
ƒ
ƒ
Generazione di report in formato Crystal, PDF, Word, Excel Delivery dei report a video Delivery dei report direttamente ad una periferica di stampa (lato server) Delivery dei report (nei formati sopra indicati) via e‐mail Delivery dei report verso spooler/scheduler (stampa batch) Esportazione dati in formato MS Excel Il modulo utilizza come formato di rappresentazione dati l’XML e come engine di rendering l’engine di Crystal Report contenuto nell’ambiente di sviluppo utilizzato per la realizzazione. Il modulo, pur non essendo un componente unico (infatti è costituito da class library, htc, una pagina aspx di interfaccia e un handler http di comunicazione), deve essere considerato come una black‐box con una propria API nota, questo al fine di centralizzare il codice per semplificarne la manutenzione e l’utilizzo, e per poterlo riutilizzare anche in altri contesti. Il modulo si integra con il componente di profilatura del framework, quindi consente di erogare reports con etichette dinamiche (in base alla lingua o all’installazione) i cui valori possono dipendere dal contesto di esecuzione. Anche la visibilità o meno di eventuali campi profilati sul report dipenderà dal contesto di esecuzione. L’interfaccia del modulo è rappresentata da un html component che espone una serie di attributi/proprietà e che è in grado di generare tre eventi nella pagina web dalla quale dovrà essere realizzata l’integrazione con l’intero modulo. Tramite le proprietà, è possibile caratterizzare il comportamento del modulo in base alle esigenze previste (es. è possibile rendere attiva la funzionalità di export verso Excel o meno, è possibile attivare lo streaming a video o meno, è possibile indicare un repository per i template di trasformazione Excel, è possibile indicare l’indirizzo del server SMTP per l’invio via e‐mail e le credenziali di autenticazione allo stesso, ecc…). Tramite gli eventi è invece possibile codificare la comunicazione tra la pagina dalla quale si intende lanciare il report e il modulo di reportistica. E’ possibile, dunque, il passaggio dei parametri necessari per erogare il report (nome del servizio che eroga i dati, nome del report, lista dei parametri, ecc…), verificare se l’utente è abilitato o meno all’operazione di stampa, e scalare fino al richiedente eventuali messaggi di feed‐back. L’API del modulo consente quindi di integrare le funzionalità di reportistica da una qualsiasi pagina semplicemente programmando i tre eventi e settando opportunamente le proprietà dell’htc, senza l’onere per lo sviluppatore di dover conoscere tutto l’intero modulo, e con il vantaggio di realizzare un unico pattern. DATA PROVIDER Il compito di questo strato è quello di individuare il servizio web che eroga i dati da inserire sul report, quindi di inoltrare la richiesta. In particolare intercetta, sul messaggio xml gestito dall’evento CollectDatiStampa() dell’htc di interfaccia, il nome del servizio e il webMethod da invocare, quindi tramite il file di configurazione del progetto di front‐end, risolve l’indirizzo effettivo del servizio web e costruisce in modo dinamico la chiamata in modalità sincrona, passando opportunamente tutti i parametri previsti e intercettati anch’essi dal messaggio xml. L’invocazione dinamica si avvale del meccanismo di reflection per derivare un oggetto a partire dal nome della classe madre. Tale strato restituisce un dataset contenente la/le tabelle con i dati richiesti. A questo livello qualsiasi servizio già in essere può essere chiamato purchè la struttura dati fornita sia coerente alla mappatura su un oggetto report. REPORT PROVIDER Il compito di tale modulo è quello di istanziare un oggetto di tipo report del tipo richiesto e di applicare la profilatura prevista. Analogamente allo strato Data Provider mediante il meccanismo di reflection viene derivato un oggetto di tipo report a partire dal nome della classe madre intercettata sul messaggio xml gestito dall’evento CollectDatiStampa() dell’htc di interfaccia. Mediante una procedura ricorsiva (un report è a tutti gli effetti un contenitore di oggetti etichette, campi, formule, sub report … e ciascun oggetto può essere a sua volta un contenitore di altri oggetti), lo strato naviga tutti gli oggetti contenuti sul container report e per ciascuno di essi provvede ad associare la sorgente dati restituita dal Data Provider, e ad applicare le regole di profilatura previste (per la profilatura utilizza informazioni di contesto tipo profilo‐utente e le informazioni contenute sul descrittore delle classi). Il modulo applica la profilatura a 2 tipi di oggetti Crystal: le etichette e i campi field object. Per quel che riguarda le etichette, il nome di una eventuale classe associata alla label in oggetto è praticamente la proprietà Text dell’oggetto TextObject, mentre per un campo dati (oggetto Field) la proprietà che contiene il nome della classe eventualmente associata (si controlla la visibilità o meno del campo) è la proprietà CssClass dell’oggetto Field. Tale modulo restituisce un oggetto report con sorgente dati associata e profilato. ESPORTAZIONE E DELIVERY Questo strato si occupa di esportare l’oggetto report restituito dal Report Provider in uno dei formati previsti: PDF, Crystal, Excel, Word. A seconda del canale di trasporto costruisce stream binari in memoria da inviare in risposta alle get http invocate dal browser (canale video), file fisici sul file system (canale e‐mail), stream di stampa verso periferiche (canale Stampante) o file sotto forma di messaggi MSMQ (canale batch). Per le esportazioni vengono utilizzate librerie messe a disposizione dal Crystal engine. INTERFACCIA AL MONDO OFFICE Questo strato si occupa di trasformare un dataset (con una sola tabella) restituito dal Data Provider in uno stream binario in formato Excel da inviare al browser. La libreria provvede a costruire un foglio Excel secondo un template di forma già presente (il template di forma indica come le colonne della tabella del dataset devono essere spalmate sui vari sheet di uno stesso file Excel). In particolare tutti i dati che devono essere inseriti nei vari sheet del foglio Excel vengono sottoposti ad una operazione di casting di tipo in modo tale che sul foglio Excel prodotto sia possibile fare delle elaborazioni con gli strumenti di Excel; l’operazione di casting è quindi necessaria poichè i tipi di dati di Oracle non corrispondono ai tipi dei dati di Excel. Questo strato contiene anche una parte di front‐end che in pratica è un editor di template di forma in ambiente web. L’utente può costruire ed eventualmente salvare un proprio template di forma a partire dalla lista delle colonne della tabella del dataset restituito dal Data Provider. 2. RICH INTERNET APPLICATIONS 2.1 LE MOTIVAZIONI Le tradizionali applicazioni web implementano un’architettura client/server, nella quale un client ‘sottile’ (web browser) interagisce con un server ricco di funzionalità. Tipicamente, tutta la processazione è fatta sul server e il client è usato solo per visualizzare il contenuto Html statico. Il più grande svantaggio delle applicazioni thin‐client è che tutta l’interazione con l’applicazione deve passare attraverso il server. Ciò significa che i dati devono essere inviati al server, il server deve rispondere, e poi la pagina deve essere ricaricata sul client con la risposta del server. L’applicativo per l’implementazione di form dinamiche era già stato sviluppato in precedenza utilizzando il Framework ASP.NET, che è un sottoinsieme del ben più vasto Framework .NET, all’interno dell’ambiente di sviluppo Microsoft Visual Studio 2005. Tuttavia, poiché si voleva spostare parte della processazione dal server al client, rendere l’applicativo più veloce dal punto di vista del tempo di esecuzione, più snello per quel che riguarda l’occupazione di memoria, e capace di un maggior grado di interattività, è stato necessario ricercare uno strumento che consentisse di fare ciò. La soluzione è nel realizzare l’applicazione di gestione delle form dinamiche come un’applicazione rich internet (RIA), che consente funzionalità molto interattive. Le Rich Internet Applications, o RIA, sono applicazioni web che hanno le caratteristiche e la funzionalità delle tradizionali applicazioni desktop, senza però necessitare dell’installazione sul disco fisso. Tali applicazioni trasferiscono al web client la processazione necessaria per le interfacce utente, ma mantengono il grosso della processazione dei dati (come il mantenimento dello stato del programma, i dati, ecc.) sul server dell’applicazione. Spostando la maggior parte dell’elaborazione sulla tecnologia lato‐client che può eseguire istruzioni sul computer del client, le applicazioni RIA possono evitare lentezza e loop sincroni per molte interazioni utente. 2.2 BENEFICI DELLE APPLICAZIONI RIA Uno dei principali benefici delle applicazioni RIA è che esse possono offrire dei comportamenti dell’interfaccia utente che non sono possibili con i soli controlli HTML delle applicazioni web basate su browser standard. Con una piattaforma RIA, le applicazioni web non sono più limitate da ciò che il browser può fare. Piuttosto, esse possono implementare qualsiasi interazione utente che la nuova piattaforma RIA supporta, come azioni drag‐and‐drop, armoniose animazioni, e calcoli lato client. Nonostante alcune di queste interazioni siano possibili anche senza la piattaforma RIA (ad esempio usando Ajax), l’approccio RIA è tipicamente molto più reattivo e utilizzabile tra le varie piattaforme. I benefici delle RIA, comunque, vanno oltre il loro aspetto. Usando un motore client, possono produrre anche altri benefici di performance: •
Bilanciamento client‐server Le RIA spostano il bilanciamento delle risorse di computazione delle applicazioni web dal server al client. Questo consente di liberare risorse sul web server, consentendo allo stesso hardware del server di gestire un maggior numero di sessioni utente concorrenti. Dall’altro lato, questo comunque richiede che gli utenti abbiano computer che siano abbastanza potenti per eseguire del codice complesso lato client, questo tuttavia generalmente non è un problema al giorno d’oggi. •
Comunicazione asincrona Il motore client RIA può interagire con il server in maniera asincrona, ciò permette all’utente di non dover bloccarsi per aspettare i risultati dal server, ma può continuare tranquillamente il proprio lavoro. Questa caratteristica consente di spostare i dati tra il PC dell’utente e il server senza mettere l’utente in attesa che il trasferimento finisca, in maniera simile a ciò che oggi fa Ajax. •
Efficienza di Rete Il traffico della rete può essere significativamente ridotto in una applicazione RIA poiché un motore client specifico dell’applicazione può essere più intelligente di uno standard web browser quando decide quali dati è necessario scambiare con i server. Trasferire meno dati per ogni interazione può velocizzare le richieste e le risposte individuali, riducendo così il carico della rete. L’uso di tecniche di prefetching asincrono, comunque, può azzerare o anche rovesciare questo potenziale beneficio. Poiché il codice non può anticipare esattamente cosa l’utente farà in seguito, precalcolare dati aggiuntivi è una pratica comune, ma molti di essi potrebbero non essere necessari agli utenti. 2.3 DIFETTI DELLE RICH INTERNET APPLICATIONS Benché tali applicazioni offrano vantaggi convincenti per quel che riguarda gli attuali approcci allo sviluppo web, ci sono alcuni svantaggi che affliggono tale tecnologia, non da ultimo la presenza di requisiti del plugin stesso. Tra i più importanti svantaggi di tali applicazioni ci sono: •
Sandbox Poiché le RIA girano dentro una sandbox, esse hanno restrizioni di accesso alle risorse del sistema. Se gli utenti modificano i loro sistemi o riducono i permessi che modificano la capacità di tali applicazioni di accedere alle risorse del sistema, esse possono operare non più correttamente. •
Script disabilitati Le applicazioni RIA solitamente richiedono che il JavaScript o altri linguaggi di scripting siano in esecuzione sul client. Se l’utente ha disabilitato l’active scripting sul suo browser, esse potrebbero non funzionare come dovrebbero. •
Tempo di download degli script Sebbene non sempre è stato installato, l’addizionale motore client delle applicazioni RIA deve essere inviato dal server al client. Nonostante la maggior parte di esso è solitamente messo in cache automaticamente, è necessario che sia trasferito almeno una volta. A seconda della dimensione e del tipo di consegna, il tempo di download del motore client potrebbe essere spiacevolmente lungo, specialmente per gli utenti con connessioni Internet più lente. E’ possibile ridurre l’impatto di ciò comprimendo gli script e organizzando la consegna su pagine multiple di un’applicazione. Tuttavia per motori client che richiedono che venga installato un plugin, questa opzione non è più valida. •
Perdita di visibilità nei motori di ricerca I motori di ricerca possono non riuscire ad indicizzare il contenuto testuale delle applicazioni RIA. Questo può essere un grande problema per le applicazioni web che dipendono dalla visibilità nei motori di ricerca per il loro successo. •
Dipendenza da una connessione Internet Benché la sostituzione di un’applicazione desktop con una rete ideale che viaggi da ufficio ad ufficio potrebbe consentire agli utenti di essere occasionalmente connessi, oggi una tipica applicazione RIA richiede la connessione alla rete. 2.4 SILVERLIGHT Si desidera che l’applicazione rich interactive di gestione delle form dinamiche venga eseguita allo stesso modo su piattaforme e browser diversi. Tra i vari strumenti disponibili sul mercato è stato scelto il nuovo plugin Silverlight 2 sviluppato dalla Microsoft, giunto da poco alla sua versione definitiva. La scelta dello strumento non è ricaduta sul ben più datato plugin Flash Player di Adobe, anche esso utilizzato per lo sviluppo di applicazioni rich interactive e in un certo senso il rivale numero uno di Silverlight, in quanto esso a differenza del plugin della Microsoft non supporta il multi‐threading, non possiede una parte del framework .NET che consente di scrivere codice utilizzando uno qualunque dei linguaggi da esso supportati (ad esempio C# e VB) e di interagire con il resto dell’applicazione realizzata in ASP.NET, e anche perché l’azienda che deve fornire tale applicazione ha già un contratto di licenza con la Microsoft. Vengono analizzate di seguito quelle che sono le caratteristiche salienti della piattaforma Silverlight. [1]Silverlight è un nuovo plugin cross‐browser e cross‐platform della Microsoft per lo sviluppo di applicazioni RIA (Rich Internet Application) per gli utenti del web. Tra le capacità di Silverlight c’è l’invio immediato di contenuti audio e video di alta qualità a tutti i browser importanti compreso Firefox, Safari e Internet Explorer mentre i sistemi operativi supportati sono Windows, Mac e Linux (Moonlight). Silverlight non è un semplice plugin, ma una possibile nuova strada nello sviluppo di applicazioni web. [2]Per capire cosa è realmente Silverlight bisogna capire la sua architettura e ciò che permette di realizzare. Uno dei più grandi limiti nello sviluppo di applicazioni web è dovuto al linguaggio (X)HTML che non si è evoluto negli anni, ma al contrario di altri linguaggi di programmazione, è rimasto quasi del tutto invariato non allineandosi alle esigenze di mercato. Da qui è nata l'idea di Microsoft di introdurre una nuova tecnologia legata al mondo web che permettesse di abbattere i limiti legati all'(X)HTML e legarla ad una tecnologia già consolidata come il .NET Framework e tutto ciò che esso abbraccia. Originariamente Silverlight si chiamava WPF/E , dove "E" stava per Everywhere, mentre WPF, è l'acronimo di Windows Presentation Foundation, la tecnologia Microsoft rilasciata per lo sviluppo di applicazioni con interfacce utente di ultima generazione, sia per il Desktop che per il Web. Allora perché usare Silverlight? La risposta è semplice: WPF richiede l'uso di .Net 3.0 sulla macchina sulla quale gira l'applicazione questo perché ne sfrutta a pieno le funzionalità. Silverlight si ispira a questa tecnologia utilizzando un sottoinsieme delle API usate da WPF ed in particolare al linguaggio dichiarativo noto come XAML. Mentre con WPF si possono realizzare applicazioni che sfruttano a pieno le funzionalità offerte dal sistema operativo client, che ci permettono ad esempio di realizzare applicazioni con funzionalità 3D e di sfruttare la GPU di una moderna scheda grafica, Silverlight permette di realizzare applicazioni Web per diverse piattaforme, browser ed in futuro anche per dispositivi di natura diversa, come cellulari e smartphone. Silverlight 2 è la seconda versione della Microsoft della piattaforma Silverlight. Alcune differenze rispetto alla precedente release riguardano i controlli, ci sono nuovi comandi come il Tab control, Text Wrapping, Scrollbards per i TextBox e funzionalità di Autosize, Reorder, Sort etc. per il DataGrid. Migliora inoltre il Cross Domain, il web client è stato aggiornato per permettere gli uploads e comunicazioni in duplex. Tuttavia, il principale cambiamento alle versioni precedenti (es. Silverlight 1.0) è l’introduzione di una versione compatta del .NET Framework e del Common Language Runtime. La versione del runtime di Silverlight 2 è di circa 4,6 MB. Aggiungendo il .NET a Silverlight, Microsoft ha reso più facile agli sviluppatori .NET l’uso delle conoscenze già acquisite in precedenza, la collaborazione con i designers, e la rapida creazione di ricche applicazioni interattive. E benché Silverlight 2 fornisce il framework .NET al cliente, esso può essere integrato facilmente con molte tecnologie web già esistenti, e piattaforme web di backend. Ciò significa che Silverlight si integra perfettamente con le infrastrutture e applicazioni già esistenti, dall’ IIS (Internet Information Services) e .NET passando per Apache e PHP fino al semplice JavaScript e XHTML sul client. Silverlight non è un tool creato per un uso esclusivo su siti web ASP.NET, la cui cosa potrebbe ostacolare la diffusione di una nuova tecnologia. Uno dei benefici chiave di Silverlight 2 è che può eseguire qualsiasi linguaggio .NET, incluso C# e VB.NET. A differenza del CLR incluso nel .NET framework tradizionale, più istanze del core Silverlight CLR possono essere ospitate in un singolo processo. Grazie a ciò, il markup del layout contenuto nel file XAML (.xaml file) può essere esteso, attraverso l’uso di codice code‐behind, con tutta la programmazione logica scritta in uno qualsiasi dei linguaggi .NET. Il plugin contiene una versione ‘light’ del .NET Framework, che si caratterizza per la presenza di controlli estendibili, Web Services XML, componenti di networking, supporto per il data‐binding e API di LINQ. Questa libreria di classi è un sottoinsieme della libreria di base del .NET Framework, questo permette al plugin di essere un download veloce e piccolo. Per sicurezza, tutto il codice Silverlight viene eseguito all’interno di una sandbox che previene l’invocazione di API della piattaforma, proteggendo i computer degli utenti da codice maligno. Silverlight 2 aggiunge anche il supporto per la gestione dei diritti digitali (DRM) nei file multimediali, un fatto che renderà felici alcune persone e ne rattristerà altre. In aggiunta alle classi del .NET Framework, Silverlight 2 fornisce anche un sottoinsieme del modello di programmazione UI di WPF (Windows Presentation Foundation), includendo il supporto per forme, documenti, media e animazioni WPF. La figura sottostante mostra graficamente le caratteristiche della piattaforma Silverlight. Figura 13 ‐ La piattaforma Silverlight 2.4.1
SILVERLIGHT VS FLASH [3]
Adobe Flash è una tecnologia per aggiungere animazioni e interattività alle applicazioni web. Flash fornisce un ampio supporto cross‐browser e cross‐platform per creare applicazioni RIA. Anche Silverlight fornisce caratteristiche simili. Esso comunque estende tali caratteristiche fornendo un framework di programmazione first‐class che incorpora una parte del .NET Framework. Ciò significa che è possibile scrivere codice che sarà eseguito lato client usando uno dei linguaggi .NET. Nella tabella sottostante è possibile vedere alcune somiglianze e differenze tra queste due potenti tecnologie. Flash Silverlight L’animazione Timeline in Flash è Silverlight usa un’animazione basata basata sul frame. Esso usa una sui vettori. matrice di trasformazione. Flash usa una libreria esterna per Silverlight usa XAML.
memorizzare le forme. Flash supporta più formati e codec Silverlight usa un codec standard video, ciò nonostante l’output è industriale. proprietario. Il contenuto Flash può essere creato Per creare contenuto Silverlight è usando Adobe Flash. possibile usare tool esistenti come Visual Studio ed Expression Blend. Come modello di programmazione, ActionScript ha una curva di apprendimento enorme e non è stata trovata una comune abilità di programmazione negli sviluppatori. Silverlight è stato sviluppato dal basso verso l’alto usando linguaggi che a molti sono già familiari come quelli .NET. Il contenuto Silverlight può essere creato usando XAML e un linguaggio .NET. In tal modo è possibile utilizzare le conoscenze già possedute. Flash prevede animazioni grafiche Silverlight fa la stessa cosa. 2D. Il costo del server Media in Flash è Il costo è inferiore a quello di Flash. maggiore. Le capacità video di full screen non Tale capacità scalabile è disponibile sono previste. sull’HD . Il contenuto Flash è di natura Il contenuto Silverlight è di natura binaria. testuale. Flash non ha il supporto per la Silverlight fornisce tutto il supporto programmazione multi‐thread. necessario per il multi‐threading. [4]
Analizziamo con un maggior dettaglio tali caratteristiche. • Animazioni Il formato Flash non ha una nozione di animazione all’infuori di matrici di trasformazione. E’ possibile applicare una matrice ad un elemento sulla base di un frame per poterlo muovere intorno. Se vogliamo muovere qualcosa lungo lo schermo in 3 secondi è necessario calcolare quanti frame occuperà in 3 secondi , poi si calcolano le matrici richieste da ogni frame. C’è da tenere a mente che il player non vuole mantenere una qualunque frequenza di frame almeno che non si inseriscano tracce audio vuote, così che 3 secondi possono risultare essere 2 o 6 o 5, a seconda dell’umore della macchina. Silverligt supporta il modello di animazione WPF, che non solo è basato sul tempo invece che sui frame, ma consente di definire le condizioni di avvio e di terminazione e capirà come riceverle al posto del programmatore. Non è necessario avere a che fare con le matrici e calcolare le posizioni sui vari frame. Silverlight lavora da solo. • Forme Flash memorizza le sue forme utilizzando record di forme binari. Per scrivere le definizioni di forme, è necessario possedere una SDK esterna in formato Flash o costruirne una propria. Ciò non è molto difficile, ma richiede alcune conoscenze specifiche e la capacità di manipolare oggetti a livello di bit, poiché i record di forme non si allineano sui limiti del byte. Non è necessario dire che ciò non è il genere di cosa che la maggior parte delle persone può scrivere e debuggare in poco tempo. Silverlight usa l’XAML, che è basato sul testo e può essere prodotto usando un semplice oggetto XML. Non è necessario acquistare librerie speciali o scrivere file, e non è necessario scrivere le proprie librerie. Scrivere qualche testo in un file è una cosa abbastanza semplice ed è il genere di cosa che può essere debuggata e terminata in poco tempo. • Testo Flash memorizza i suoi caratteri di testo usando le stesse definizioni di forme usate per una qualunque forma. Il player non comprende i file TTF, così si va a finire nelle API di Win32 e nelle imprecise definizioni della documentazione di Flash per tirar fuori qualche soluzione. Si potrebbero spendere anni per cercare di avere a che fare con le complessità dei fonts, poiché attualmente la tipografia risulta essere molto complessa. Silverlight consente di inserire l’informazione sul tipo di font direttamente nei progetti e scaricare tale informazione con l’oggetto downloader. Non è necessario fare nulla di speciale né gestire nulla, tutto il lavoro è svolto da Silverlight. • Video/Audio Flash supporta molti formati video. Gli ultimi codec sono davvero di ottima qualità e l’occupazione di banda è buona. C’è un solo problema tuttavia se si vuole creare un tool che produca in output del contenuto Flash, in quanto i formati che esso supporta non sono realmente usati da chiunque altro. Per quel che riguarda i formati audio Flash supporta tutti quelli proprietari, ad eccezione di ADPCM, che nessuno usa a causa della sua orribile compressione, e dell’MP3, che è buono ma datato e richiede quote di licenza e librerie di conversione esterne. Silverlight implementa per il video il codec standard industriale VC‐1, offrendo anche il supporto per WMV e WMA. Se non si possiede Windows Movie Maker, la Microsoft ha reso disponibile un SDK encoder per produrre WMA e WMV. Così, non solo sono utilizzati formati che la gente riesce a codificare da sé, ma Microsoft offre anche un supporto per fare ciò. La parte migliore è che Microsoft non punta sui guadagni delle licenze WMA/WMV per sopravvivere, così non solo l’integrazione è più semplice, ma anche più economica. • Scripting E’ possibile usare classi C# all’interno di contenuti esportati. Non c’è un ambiente di sviluppo al di fuori di quello per la creazione di applicazioni desktop reali basato sull’ActionScript. Questo fa sì che tutte le classi e gli oggetti siano scritti due volte. Sono necessarie classi .NET per gestire l’esperienza dell’autore e classi Flash per gestire il run‐time. Se si possiedono componenti server, ancora una volta è necessario passare al .NET e gettar via tutte le classi che il run‐time sta usando. Per esempio, se si sta creando uno strumento che produce in output dei quiz multimediali, con Silverlight e .NET le stesse classi che sono usate per ottenere i risultati sul player potrebbero essere riutilizzate lato server. Con Flash, è necessario scrivere tutta la logica due volte e sincronizzarla con i cambiamenti allo strumento. • Tool E’ possibile creare contenuto Silverlight con gli stessi strumenti che si usano quotidianamente. Visual Studio è da molto il più potente e popolare IDE. Potenzialmente è possibile avere tutto il codice per i componenti server, i componenti di autorizzazione, i componenti di runtime e del player all’interno dello stesso progetto. Non è necessario possedere nessuna abilità extra, ogni sviluppatore può contribuire a creare una parte dell’applicazione. 2.4.2
SILV
VERLIG
GHT VS AJAX
X [5]
Attraveerso un’an
nalisi un po’ superrficiale di queste due d
tecno
ologie si potrebbee pensare cche la teccnologia A
Ajax si con
ntrapponga a quellaa Silverligh
ht; questo
o in realtà non è afffatto vero
o perché esse si co
omplementano a vicenda. v
P
Per mostrare ciò sii prenda in
n considerrazione la figura sotttostante.
Figura 14 – Spettrro tecnologie per appliicazioni mma rappresenta uno spettro degli ap
pprocci e d
delle tecnologie/piaattaformee Il diagram
per lo sviluppo delle applicaazioni dovve da un lato si incrementa lla raggiun
ngibilità, ee nzialità. Lee applicaziioni posso
ono averee dall’altro lato si incrementaano invecee le poten
ne applicazzioni tend
dono a mu
uoversi verso la partte sinistra diversi sccenari; infaatti, alcun
dello speettro alla ricerca di una magggiore ragggiungibilittà, mentree altre si muovono
o verso la p
parte desttra poichéé devono p
possedere
e alcune fu
unzionalittà un po’ p
più spintee che richiedono un
na piattaforma più competittiva, anch
he se ciò significa ridurre la a
ni probab
bilmente ssaranno quelle q
chee raggiungiibilità. Tutttavia, le migliori applicazion
fanno uso
o di opzio
oni multiplle di frontt‐end per vvenire inccontro aglii utenti, utilizzando
o un back‐eend comu
une. Una cosa è siccura, gli utenti u
si aspettano sempre di d più e lee applicazio
oni hanno
o cominciaato a diffeerenziarsi per veniree incontro
o alle loro esigenze. Al temp
po stesso
o, ci son
no numeerose app
plicazioni che non richied
dono più esclusivamente Ajax, ma hanno la necessità n
n come Silverlight,
S
, di utilizzaare plugin
Gears e Flaash per incrementare le loro funzionallità di inteerattività, tipiche dii Google G
applicazio
oni RIA. Q
Questi pluggin portan
no le applicazioni A
Ajax a faree cose interessanti,, come utilizzare teccniche di memorizzzazione de
ei dati in locale, acccedere ai file deglii utenti, integrare ricchi contenuti multimediali e grafica vettoriale, ed effettuare una processazione in background. Proprio come i controlli server di ASP.NET rendono abbastanza facile integrare funzionalità Ajax, essi sono anche posizionati per semplificare lo scenario di Silverlight. Immaginiamo un controllo server associato a dati che visualizza un diagramma Silverlight e genera una immagine statica che è visualizzata dinamicamente sul server senza richiedere allo sviluppatore dell’applicazione di apprendere nuove tecnologie. Un’altra cosa interessante del diagramma sono effettivamente i trend che vanno delineandosi. La piattaforma raggiungibile, che inizia ad essere identificata come Web Aperto, deve diventare più ricca in termini di motore d’esecuzione, stack di presentazione, e potenzialità tipiche di un framework. Dunque attualmente non c’è uno scontro tra Ajax e tecnologie RIA, ma queste due vanno di pari passo, c’è abbastanza spazio per la loro coesistenza. 2.4.3
SILVERLIGHT VS JAVAFX JavaFX è la piattaforma della Sun per lo sviluppo di applicazioni RIA. La parte dell’applicazione che elabora i dati è trasferita a livello client e fornisce una pronta risposta all’interfaccia utente, mentre la gran parte dei dati e dell’applicazione rimane sul server remoto, con notevole alleggerimento per il computer utente. JavaFX è un’estensione dell’esistente Java virtual machine, con l’aggiunta di un linguaggio di scripting descrittivo. [6]Il componente principale di JavaFX si chiama JavaFX Script, linguaggio di programmazione che non è Java e non è nemmeno JavaScript. In JavaFX Script il codice di programmazione deve corrispondere esattamente al layout dell'interfaccia grafica in corso di progettazione. Piuttosto che poggiare su risorse XML per definire come gli oggetti debbano apparire sullo schermo, JavaFX Script impiega un nuovo linguaggio dichiarativo. Nello stesso Java, lo sviluppatore può ricorrere a funzioni che permettano di generare oggetti grafici ed interagire con gli argomenti propri di tali funzioni per trasmettere valori e stringhe di testo. In JavaScript è necessario poggiare su XHTML per stabilire il posizionamento ed il contenuto di ogni oggetto. Con JavaFX, il programmatore dichiara l'esistenza dei vari oggetti grafici che possono disporre di lunghe liste di possibili proprietà. [7]JavaFX Script permette agli sviluppatori di costruire in modo efficiente piacevoli interfacce utente per Java. Esso aiuta a promuovere Java come una piattaforma di sviluppo lato client e cross‐platform. Con JavaFX Script è possibile separare lo sviluppo delle UI dalla loro implementazione, ciò consente di costruire applicazioni RIA in Java. Comunque, i vecchi problemi di Java di basse performance e di inconvenienti di deployment riscontrati lato client sono ancora evidenti. Questo perché il runtime di Java è enorme rispetto a quelli di Microsoft o AIR. Figura 15 – La piattaforma JavaFX Sia Silverlight che JavaFX permettono di effettuare chiamate asincrone e utilizzano linguaggi dichiarativi. Nonostante le due piattaforme siano diverse, ci sono alcune caratteristiche in comune: • Essi supportano definizioni dichiarative delle UI che possono essere separate efficientemente dal codice, consentendo ai designer di costruire UI indipendentemente dai programmatori che scrivono il codice. In questo senso essi adottano le migliori metodologie degli sviluppatori web. • Entrambe supportano ricche animazioni e presentazioni con grafiche basate su vettori. • Entrambe consentono alle applicazioni di essere eseguite allo stesso modo su piattaforme e browser diversi. Tra i suoi vantaggi JavaFX può essere usato facilmente anche da persone che non hanno mai avuto a che fare con la programmazione. Invece, attraverso un test chiamato Bubblemark è possibile mostrare che le applicazioni realizzate con JavaFX sono circa 20 volte più lente di quelle realizzate con la piattaforma Silverlight. JavaFX gira su tutte le piattaforme dove è presente una JVM, e non sono solo Windows, Mac e Linux, come nel caso delle altre piattaforme RIA; inoltre grazie alla realizzazione di JavaFX Mobile esso gira anche su dispositivi mobili. La piattaforma della Sun annulla la differenza tra applicazioni Internet e applicazioni locali: lo stesso programma, eseguito in un browser, può essere trascinato sul desktop del Pc e diventa un'applicazione locale ‐ o viceversa, se necessario. La stessa applicazione, grazie a JavaFx Mobile, funziona anche sui telefonini, consentendo agli sviluppatori di non dover realizzare diverse versioni per le diverse piattaforme. Una differenza importante tra JavaFX è tutte le altre piattaforme RIA è che esso è open source con licenza GPL mentre le altre sono proprietarie per intero o parzialmente. Ci sono differenze anche nella strategia di mercato; infatti la Sun cerca di sfruttare il suo vantaggio competitivo rappresentato dall'enorme numero di dispositivi portatili che supportano il linguaggio Java oltre ai numerosi accordi che già vanta a livello di produzione con i maggiori produttori di dispositivi. Un altro punto di differenziazione della strategia consiste nella scelta della GNU/GPL come licenza di distribuzione della piattaforma. Diversamente dai prodotti di Adobe e Microsoft ci troviamo di fronte alla possibilità di agire davvero sul prodotto per apportare migliorie e personalizzazioni in tutti i componenti del sistema, anche se questi dovranno poi essere condivisi con il resto della comunità. Comunque si può dire che Silverlight e JavaFX utilizzano approcci differenti per raggiungere gli stessi obiettivi. 2.4.4
L’ARCHITETTURA [8]
L’architettura di Silverlight è abbastanza complessa, tuttavia essa può essere suddivisa in due grandi sottoinsiemi, con l’aggiunta di un installer e un componente di update. Il sistema di presentazione (Core Presentation Framework) si occupa di qualsiasi cosa riguardante l’User Interface e l’interazione dell’utente, includendo grafica vettoriale, animazioni, visualizzazione di testi, playback audio/video, gestione dei diritti digitali e data binding. Il .NET Framework di Silverlight è costituito invece da un sottoinsieme del .NET Framework che contiene componenti e librerie, includendo integrazione di dati, controlli di Windows estendibili, networking, librerie di classi di base, garbage collection e il common language runtime. Alcune parti di tale framework sono deployate insieme all’applicazione. Queste librerie Silverlight sono assembly non inclusi nel runtime di Silverlight, ma all’interno della sua SDK. Quando tali librerie sono utilizzate nell’applicazione, esse sono inserite nel package relativo ad essa e sono scaricate sul browser. Esse inoltre includono nuovi controlli UI, XLINQ, Syndication (RSS/Atom), serializzazione XML, and the dynamic language runtime (DLR). Per quel che riguarda infine il controllo di installazione e di update, esso semplifica agli utenti il processo di installazione dell’applicazione per la prima volta e in seguito fornisce modifiche automatiche. La figura sottostante mostra i componenti dell’architettura Silverlight con i relativi componenti e servizi. Figura 16 ‐ Modello del framework Silverlight Questo set di strumenti, tecnologie e servizi incluso nella piattaforma Silverlight riveste una grande importanza, poiché rende più facile agli sviluppatori la creazione di applicazioni molto interattive e collegate in rete. Sebbene è certamente possibile realizzare applicazioni usando le attuali tecnologie web, gli sviluppatori sono ostacolati da numerose difficoltà tecniche, tra cui piattaforme incompatibili, diversi formati di file e protocolli, e browser diversi che visualizzano le pagine e gestiscono gli script in maniera differente. Una applicazione web interattiva che gira perfettamente su un sistema e un browser potrebbe lavorare differentemente su un altro sistema o browser, o potrebbe addirittura non essere eseguita. L’uso attuale di strumenti, protocolli e tecnologie rappresenta uno sforzo spesso enorme e proibitivo per la costruzione di applicazioni che prevedano simultaneamente i seguenti vantaggi: • Abilità di creare la stessa esperienza utente su diversi browser e piattaforme, in maniera tale che l’applicazione è visualizzata ed eseguita ovunque allo stesso modo. • Integrazione all’interno di un’applicazione di dati e servizi da locazioni multiple connesse in rete usando classi e funzionalità del .NET Framework. • Una interfaccia utente ricca di contenuti multimediali, convincente e accessibile. Silverlight rende più facile per gli sviluppatori costruire tali applicazioni, poiché supera molte delle incompatibilità delle attuali tecnologie, e prevede dentro una piattaforma gli strumenti per creare applicazioni ricche interattivamente, cross‐
platform, cross‐browser e integrate. Silverlight fornisce numerose caratteristiche addizionali che aiutano a fare ciò, tra cui: • Isolated storage La gestione dello stato in Silverlight è tipicamente realizzata utilizzando il concetto di Isolated Storage, cioè uno spazio virtuale su file system, legato al profilo dell'utente e alle applicazioni Web. La quota iniziale è di circa 1MB (nella beta 2), ma è possibile, utilizzando un'opportuna API (TryIncreaseQuotaTo), aumentare lo spazio a disposizione. Il suo uso permette ad applicazioni fidate di memorizzare i dati in un modo che è controllato dalla policy di sicurezza del computer; esso impedisce di accedere al file system dell’utente. L’ Isolated Storage è dunque una cache non volatile, che fa sì che ciò che è salvato non sia cancellato quando l’utente svuota la cache del browser. • Programmazione asincrona Un thread che lavora in background esegue alcuni processi lasciando l’applicazione libera di interagire con l’utente. • Gestione dei file Fornisce una sicura dialog box per l’apertura di file, per facilitare il processo di upload sicuro di file. • Interazione con il codice HTML Consente ai programmatori di manipolare gli elementi dell’interfaccia utente nel DOM HTML di una pagina web. Gli sviluppatori web possono anche usare il JavaScript per richiamarli direttamente nel codice managed e accedere a oggetti, proprietà, eventi e metodi. • Serializzazione Fornisce il supporto per la serializzazione dei tipi del CLR in formato JSON e XML. • Packaging Prevede la classe Application e alcuni strumenti per creare il package .xap, che contiene l’applicazione e il punto di entrata per il controllo plugin Silverlight che deve essere eseguito. • Librerie XML Le classi XmlReader e XmlWriter semplificano il lavoro da eseguire con i file XML provenienti dai Web Service. La feature XLinq permette agli sviluppatori di eseguire query su dati XML direttamente dentro i linguaggi di programmazione del .NET Framework. Il plugin si integra nel browser in maniera tale che il contenuto può essere mostrato, così come accadeva con il JavaScript DOM. Inoltre usando del codice JavaScript (o l’ASP.NET AJAX framework ), le applicazioni Silverlight possono essere arricchite per accedere alle API server come ai web services. Il plugin poi fa il parsing del markup ed esegue il codice, anche se nessuna versione del .NET Framework è installata sulla macchina client. E’ possibile così scrivere codice in linguaggi come il C#, tale codice sarà compilato prima del deployment. 2.4.5
IL CLR [9]
Il CLR (Common Language Runtime) è la base del Framework .NET. Esso è responsabile della gestione dell’esecuzione del codice in fase di esecuzione, e fornisce servizi core di compilazione, gestione della memoria, gestione dei thread e verifica della sicurezza del codice. I compilatori hanno come loro target il CLR, che definisce i tipi di base dei dati disponibili per coloro che sviluppano l’applicazione. Poiché prevede un ambiente ‘managed’ per l’esecuzione del codice, il CLR aumenta la produttività nello sviluppo e contribuisce allo sviluppo di robuste applicazioni. Ora verrà dato uno sguardo a due delle principali caratteristiche del common language runtime, ossia la gestione della memoria e il common type system. •
Gestione della memoria Il garbage collector del CLR gestisce l’allocazione e il rilascio della memoria per un’applicazione. Questo consente agli sviluppatori di non dovere scrivere codice per implementare processi di gestione della memoria quando si sviluppano applicazioni managed. La gestione automatica della memoria può eliminare problemi comuni, come dimenticare di liberare un oggetto e provocare una perdita di memoria, o il tentativo da parte di un oggetto che è già stato liberato di accedere alla memoria. Quando si inizializza un nuovo processo, il runtime riserva ad esso una regione contigua dello spazio degli indirizzi, che è chiamata managed heap. Essa mantiene un puntatore all’indirizzo dove il prossimo oggetto nell’heap sarà allocato. Inizialmente tale puntatore è settato all’indirizzo base del managed heap, al cui interno sono allocati tutti i tipi di riferimento. Quando un’applicazione crea il primo tipo di riferimento, la memoria per esso è allocata all’indirizzo di base del managed heap. Quando un’applicazione crea successivamente un oggetto, il garbage collector alloca la memoria per esso nello spazio degli indirizzi immediatamente successivo a quello dell’oggetto precedente. Finchè è disponibile lo spazio degli indirizzi, il garbage collector continua ad allocare spazio per gli oggetti in questo modo. L’allocazione di memoria dal managed heap è più veloce dell’allocazione di memoria unmanaged. Poiché il runtime alloca memoria ad un oggetto aggiungendo un valore al puntatore, questo tipo di allocazione è almeno tanto veloce quanto l’allocazione della memoria dallo stack. In aggiunta, poiché i nuovi oggetti sono allocati in una posizione contigua all’interno del managed heap, un’applicazione riesce ad accedere agli oggetti molto rapidamente. Inoltre, per allocare memoria il motore di ottimizzazione del garbage collector calcola il migliore istante per effettuare una collection basandosi sulle allocazioni precedentemente fatte. Quando il garbage collector fa una collection, esso rilascia la memoria per quegli oggetti che l’applicazione non usa da molto tempo. •
Common type system Poiché è il runtime che, al posto del compilatore del linguaggio, definisce i tipi di base disponibili, si incrementa la produttività dello sviluppatore. I programmatori possono scrivere applicazioni nel linguaggio che preferiscono, utilizzando il runtime, le librerie di classi, e i componenti codificati da altri programmatori in altri linguaggi. Tenendo conto che un compilatore ha come target il .NET Framework e il common language runtime, i componenti sviluppati con quel compilatore possono di solito essere utilizzate da applicazioni scritte in altri linguaggi. Il common type system aiuta a raggiungere l’obiettivo dell’indipendenza di linguaggio; gli sviluppatori possono creare le loro applicazioni nel linguaggio da loro scelto, e possono attingere da librerie e componenti senza badare al linguaggio con cui sono stati codificati. Il common type system supporta due categorie generali di tipi di dati: ƒ Tipi di valore I tipi di valore contengono direttamente i loro dati, e le loro istanze sono allocate sia sullo stack che all’interno di una struttura. I tipi di valore possono essere built‐in (implementati dal runtime) , definiti dall’utente, o possono essere enumerazioni. ƒ Tipi di riferimento I tipi di riferimento memorizzano un riferimento all’indirizzo di memoria che contiene il valore e sono allocati sull’heap. I tipi di riferimento possono essere tipi self‐describing, tipi puntatore, o tipi interfaccia. Il tipo di un tipo di riferimento può essere determinato dal valore dei tipi self‐describing, che comprendono gli array e i tipi di classe. I tipi di classe sono classi definite dagli utenti, tipi di valore contenuti e delegate. Ciascuna variabile che è un tipo di valore possiede la propria copia dei dati; perciò, le operazioni su una variabile non influenzano le altre variabili. Le variabili che sono tipi di riferimento possono, invece, riferirsi allo stesso oggetto; perciò, le operazioni su una variabile possono influenzare lo stesso oggetto referenziato da un’altra variabile. 2.4.6
L’ISOLATED STORAGE [10]
Silverlight permette di memorizzare i dati in maniera sicura e localmente all’interno di una cartella nascosta al di fuori della cache del browser, attraverso una caratteristica chiamata IS (Isolated Storage). Il codice dell’applicazione in Silverlight gira in maniera parzialmente sicura e dunque non deve accedere al file system dell’utente. Comunque un’applicazione Silverlight può creare e accedere ai file attraverso l’uso delle API dell’isolated storage, solo per quei file che sono memorizzati all’interno dell’area contenuta nell’IS. Tali API si basano sulle API FileStream e sono molto simili alla classe File. La dimensione dell’IS nella beta 2 di Silverlight è di circa 1 MB, ma può anche essere aumentata dall’utente attraverso il thread dell’interfaccia utente. Tra i vantaggi dell’Isolated Storage si ha: • Fornisce lato client uno spazio di memorizzazione simile ai cookie. • Piccoli file di dati contenenti settaggi dell’utente, preferenze, temi, user name possono essere facilmente memorizzati. Se lo spazio di memorizzazione è stato incrementato, possono essere memorizzati anche file grandi che richiedono risorse grafiche come le immagini. • Poiché le applicazioni Silverlight sono eseguite in un modello sandbox (sono applicazioni parzialmente sicure), esso impedisce l’accesso al file system. Comunque l’IS prevede un tipo di file system virtuale per memorizzare i file lato client. • Il contenuto dell’IS viene mantenuto anche quando la cache è svuotata; tale contenuto può essere cancellato manualmente dall’utente o dall’applicazione. • Ogni applicazione ha la propria porzione di isolated storage. E’ possibile utilizzare l’isolated storage per altri usi, come la riduzione dell’occupazione di banda. Ad esempio, può essere salvata nell’IS una form incompleta. Quando poi arriva l’utente l’applicazione può recuperare la form dallo storage e consentire all’utente di riempirla con dei dati. La cosa più interessante è che se la form era stata salvata su browser Mozilla, essa può essere aperta successivamente in Internet Explorer e possono essere recuperati i dettagli. Non c’è bisogno di contattare il server, poiché la form è stata salvata nello storage. Ciò dovrebbe ridurre il tempo e gli sforzi necessari all’utente, e consentire un risparmio dell’occupazione di banda. Vediamo ora come leggere e scrivere dei file all’interno dell’Isolated Storage. A tal proposito la classe IsolatedStorageFileStream fornisce i metodi per accedere ai file ed effettuare operazioni su di essi. Nel codice C# sottostante, la classe IsolatedStorageFile è usata per aprire l’area dello storage dell’applicazione Silverlight. Come già detto, ogni applicazione Silverlight ha la propria porzione di memoria sull’IS. Per aprire tale porzione viene chiamato il metodo statico GetUserStoreForApplication() della classe IsolatedStorageFile. Ottenuto lo storage è possibile utilizzare la classe IsolatedStorageFileStream per leggere e scrivere nell’IS. Nell’esempio viene memorizzato il contenuto all’interno di un file chiamato MyOwnFile.txt. Per creare tale file nello storage il codice è il seguente: private void btnCreate_Click(object sender, RoutedEventArgs e)
{
using (IsolatedStorageFile store =
IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream strm =
new IsolatedStorageFileStream("MyOwnFile.txt",
FileMode.Create, store))
{
using (StreamWriter sw = new StreamWriter(strm))
{
sw.Write("You can write some text over here");
}
}
}
}
Fatto ciò se volessimo andare a leggere il contenuto del file memorizzato il codice C# sarebbe il seguente: private void btnRead_Click(object sender, RoutedEventArgs e)
{
using (IsolatedStorageFile store =
IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream fs =
new IsolatedStorageFileStream("MyOwnFile.txt",
FileMode.Open, store))
{
using (StreamReader sr = new StreamReader(fs))
{
String txt = sr.ReadLine();
// Display the contents in a textblock
txtRead.Text = txt;
}
}
}
}
Nel caso in cui l’utente volesse aumentare la dimensione dello storage, espandendola ad esempio fino a 3 MB, occorrerebbe fare in tal modo: private void btnIncrease_Click(object sender, RoutedEventArgs e)
{
IsolatedStorageFile storage = null;
try
{
storage = IsolatedStorageFile.GetUserStoreForApplication();
Int64 availSpace = storage.AvailableFreeSpace;
Int64 incSpace = 3145728; // 1024 * 1024 * 3 = 3 MB
if (availSpace < incSpace)
{
// increase space by 3 MB
bool b = storage.IncreaseQuotaTo(incSpace);
if (b)
{
// User approved increase. Perform your actions
}
else
{
// User has rejected. You have only 1MB of space
}
}
}
catch (IsolatedStorageException ise)
{
}
finally
{
storage.Dispose();
}
}
Quando tale codice verrà eseguito, il sito chiederà all’utente di approvare l’utilizzo di maggiore spazio sulla macchina; verrà inoltre visualizzato lo spazio dello storage attualmente utilizzato e lo spazio che l’applicazione sta chiedendo di incrementare. 2.5 WINDOWS PRESENTATION FOUNDATION Qualche tempo fa è stata progettata alla Microsoft una tecnologia UI di nuova generazione per il sistema operativo Vista. Il sistema doveva essere basato sui vettori e usare l’XML. Il sistema finale fu chiamato Windows Presentation Foundation (WPF). WPF è una parte integrata in tutte le versioni del framework .NET successive alla 3.0 compresa. WPF si compone di un sottosistema di comunicazione WCF (Windows Communication Foundation), un sottosistema di identità digitale WCS (Windows CardSpace) e un sottosistema di workflow WF (Windows Workflow Foundation). Le applicazioni WPF girano sia nei browser sia come applicazioni desktop indipendenti. Entrambi gli scenari richiedono che il framework .NET 3.0 o una versione superiore sia disponibile. Attualmente la maggior parte delle applicazioni WPF sono indipendenti, poichè Vista non ha una grande presa sul mercato, limitando il numero di PC sui quali WPF può essere eseguito, e poiché il .NET 3.0 è un download piuttosto grande. Questa è la ragione chiave per cui Microsoft ha creato Silverlight, una tecnologia simile rivolta al mondo browser. La principale caratteristica di WPF sta nella grafica vettoriale, ma anche la grafica basata sui pixel è supportata molto bene. WPF supporta anche contenuti multimediali sotto forma di dati audio e video. Una delle principali caratteristiche è il supporto per il testo, che include alcune specialità tipografiche come il testo giustificato, kerning e ligatures. Non è certamente una sorpresa che la maggior parte del codice WPF è scritto usando linguaggi .NET come C# e Visual Basic. Il Framework .NET, o per essere precisi il CLR (Common Language Runtime), definisce ogni possibile tipo di elemento in una applicazione WPF e permette una buona esperienza di sviluppo in Visual Studio e una rapida prototipizzazione. 2.5.1
XAML Precedentemente è stato detto che il contenuto WPF è creato usando l’XML, in realtà esiste uno specifico formato per tale scopo: XAML (eXtensible Application Markup Language). XAML è un linguaggio dichiarativo, che serve a rappresentare gli oggetti e ad organizzarli in gerarchie, utilizzato per la realizzazione di interfacce utente per applicazioni WPF. Il runtime di WPF interpreta questo markup, visualizza l’interfaccia utente, e anche integra codice addizionale, scritto in linguaggi come C# o VB, che contiene la logica di business. Microsoft fornisce anche numerosi tool per lo sviluppo di contenuto XAML. E’ possibile per esempio usare Visual Studio, ma per una migliore esperienza visuale, Expression Blend (parte della Microsoft Expression Suite) è l’opzione più indicata. Anche l’SDK del .NET Framework 3.0 contiene un’applicazione chiamata XAMLPad che presenta una visuale suddivisa tra markup XAML e output visuale. Quando si crea del contenuto Silverlight, le conoscenze di base di WPF aiutano gli sviluppatori a comprendere meglio i concetti Silverlight; comunque, né WPF né .NET 3.0/3.5 sono richiesti per la visualizzazione di contenuti Silverlight. Anche Silverlight, come WPF, utilizza l’XAML per creare interfacce utente, tuttavia ne utilizza solo un suo sottoinsieme. Le future versioni di Silverlight aumenteranno la percentuale di elementi e attributi di WPF supportati, ma qualcosa non riuscirà a lavorare in un browser web come in una applicazione desktop. In Silverlight 1.0 l’elemento radice di ogni file XAML è il Canvas che definisce l’area che contiene il contenuto Silverlight. Questo è stato cambiato in Silverlight 2, permettendo così una maggior flessibilità. Quando creiamo un progetto Silverlight la pagina XAML inizialmente appare come segue: 2.6 LE TECNOLOGIE RIA Silverlight è certamente una delle new entry nell’ambiente RIA, ma non è l’unica. Ci sono numerose piattaforme disponibili sul mercato che permettono agli sviluppatori di creare applicazioni RIA, inclusa la perenne favorita, Adobe Flash. Alcune delle principali piattaforme RIA disponibili oggi sono: • Microsoft Silverlight Silverlight fornisce una ricca visualizzazione di oggetti grafici e animazioni attraverso l’integrazione XAML, e include anche il supporto per una interazione multimediale e HTML. Con la versione 2 è incluso il .NET Framework, è consentita la programmazione lato client con linguaggi managed come il C# e sono supportati linguaggi dinamici come IronRuby. Come Adobe Flash Player, il codice Silverlight è eseguito in una sandbox che evita per motivi di sicurezza l’accesso diretto alle API della piattaforma. • Adobe Flash Player e Adobe Flex Un altro modo di costruire applicazioni RIA, e probabilmente il più usato fino ad oggi, è attraverso Adobe Flash Player e Adobe Flex. Queste tecnologie sono cross‐platform e molto potenti per creare ricche interazioni lato client. Adobe Flex prevede la possibilità di utilizzare interfacce utente attraverso la compilazione dell’MXML, un linguaggio di descrizione dell’interfaccia basato sull’XML. Ma probabilmente il più grande vantaggio della piattaforma RIA FLEX è la base di installazione del plugin che fino a poco tempo fa contava almeno il 98% dei computer del mondo. • Framework Ajax Ajax, o Asyncronous JavaScript and XML, è un approccio RIA basato sugli script. JavaScript è il linguaggio di programmazione con il quale sono effettuate le chiamate Ajax, e l’oggetto del browser XMLHttpRequest consente la comunicazione asincrona con il server. I dati recuperati attraverso questa tecnica sono solitamente formattati in formato XML, sebbene questo non è richiesto. Ajax ha aumentato rapidamente la sua popolarità poiché è supportato da tutti i moderni browser senza la necessità di scaricare un plugin addizionale. Come altre applicazioni RIA, tuttavia, esso non è ben assortito per quel che riguarda l’ottimizzazione dei motori di ricerca o per gestire utenti che hanno gli script disabilitati lato client. • Adobe AIR Adobe AIR (Adobe Integrated Runtime), precedentemente chiamato Adobe Apollo, è un runtime cross‐platform che permette a coloro che realizzano applicazioni web di usare le loro abilità di sviluppo con le tecnologie RIA (come Flesh/Flex, JavaScript/Ajax e HTML) per costruire e deployare applicazioni RIA sul desktop. Sebbene non interamente RIA, questa tecnologia non si relaziona molto con la tecnologia delle rich applications. • JavaFX JavaFX è una nuova proposta della Sun Microsystems che complementa la famiglia di tool Java. Tale tecnologia nasce per la crescente domanda nella comunità Java per tool RIA e tecnologie per fornire ricchi contenuti al cliente. Oggi questa tecnologia prevede due release: JavaFX Script e JavaFX Mobile. La prima fornisce agli sviluppatori la possibilità di creare velocemente applicazioni a ricco contenuto per un’ampia varietà di clienti, inclusi dispositivi mobili, desktop e unità elettroniche. In teoria, gli sviluppatori di contenuto ora hanno un semplice modo per creare contenuto per qualunque dispositivo che supporta Java. La seconda release, d’altra parte, è un completo sistema software per dispositivi mobili. • Google Gears Google Gears è un software in versione beta offerto da Google per consentire l’accesso offline ai servizi che normalmente lavorano solo online. Esso installa un motore di database, basato su SQLite, sul client per memorizzare localmente i dati delle applicazioni web. Usando Google Gears, un’applicazione web può periodicamente sincronizzare i dati contenuti nella cache locale con i servizi online ogni volta che è disponibile una connessione di rete. Se una connessione non è disponibile, la sincronizzazione è differita fino a quando non viene stabilita una connessione di rete. Questo consente alle web application di lavorare disconnesse da Internet, rendendole simili alle equivalenti più robuste applicazioni desktop. Sebbene non interamente RIA, questa tecnologia non si relaziona molto con la tecnologia delle rich applications poiché essa affronta il problema chiave della connettività dell’applicazione. 3. I PATTERN 3.1 LE MOTIVAZIONI Prima di iniziare a scrivere codice per realizzare una qualunque applicazione è necessario prendere in considerazione alcune problematiche. Riuscire nell'intento di definire al primo tentativo una struttura ad oggetti che sia corretta e al tempo stesso riusabile e flessibile è un'impresa quasi impossibile, soprattutto nel caso di applicazioni particolarmente complesse. In genere, durante la sua realizzazione, un'applicazione subisce innumerevoli modifiche dettate dalla variabilità delle specifiche progettuali, dalla scarsa conoscenza del dominio applicativo e dall'inesperienza. In più la mancanza di tempo, che nei progetti di sviluppo è un aspetto quasi congenito, porta sovente a scegliere soluzioni molto focalizzate sul modello reale attuale e poco orientate ad adattarsi ai cambiamenti futuri. In uno scenario come quello descritto, diventa importante sia per chi progetta, sia per chi scrive il codice saper individuare delle soluzioni che siano riutilizzabili più volte nell'ambito di uno stesso progetto, piuttosto che in progetti diversi, senza ogni volta dover partire da zero per risolvere uno specifico problema. Per minimizzare il lavoro da svolgere, gli sviluppatori meno esperti solitamente tendono a ricorrere a tecniche non molto efficienti (non ultimo, il famigerato copia e incolla), con il risultato di duplicare parti di codice e di introdurre in modo più o meno voluto accoppiamento e dipendenze tra gli oggetti. Nel realizzare un’applicazione c’è dunque la necessità di definire una struttura logica coerente, di scrivere del codice che sia leggibile e riusabile, e non da ultimo introdurre nuovi elementi all’interno di team di sviluppo aziendali. La soluzione a tali problemi consiste nel far uso di pattern, ovvero di “una soluzione consolidata ad un problema ricorrente in un contesto specifico”. [11] Il paradigma dei Pattern è stato sviluppato alla fine degli anni '70 da Christopher Alexander, professore di Architettura all'Università di Berkeley, per far fronte ai complessi problemi connessi alla progettazione urbanistica ed edilizia e come strumento di progettazione di architettura "a misura d'uomo". Secondo Alexander la scarsa qualità dell'architettura degli anni '60 era dovuta anche alla mancanza di metodi formali di progettazione. In pratica, egli rilevò che i progetti urbanistici e di edifici non tenevano conto delle esperienze concrete che man mano andavano maturando e senza le quali i progetti stessi finivano per essere estranei alle esigenze reali degli utenti. Di qui l'idea di definire dei Pattern che stabiliscono una relazione tra un contesto, un insieme di condizioni (o vincoli) legate a quel contesto ed una soluzione che risolve il problema con quelle condizioni ed in quel contesto. Dalla meta degli anni '90 l'idea di usare Linguaggi di Pattern come supporto ai progettisti ha avuto un nuovo slancio grazie all'enorme successo avuto dalla sua applicazione al campo dell'ingegneria del software e della progettazione object oriented. Recentemente il paradigma dei Pattern è stato applicato anche al campo della Human Computer Interaction (HCI), con estensioni al mondo Web. Un Pattern è un'entità costituita generalmente da cinque elementi: • Nome: un Pattern deve avere un nome significativo. Dare un nome a qualcosa è il primo passo per poterne parlare. • Esempio: una o più immagini, diagrammi, e/o descrizioni che illustrano il prototipo dell’applicazione. • Contesto: è l'insieme delle condizioni al contorno, l'ambiente nel quale si agisce, è l'insieme delle forze in azione delle quali il Pattern deve tenere conto e che vincolano la scelta della soluzione. • Problema: è una situazione che si presenta in maniera ricorrente nel contesto e che crea scompensi tra le forze in azione. • Soluzione: è un algoritmo, una tecnologia, una struttura organizzativa, un metodo noto, un modello di riferimento che risolve in maniera ricorrente quel problema in quel contesto. Il termine Pattern può tradursi con le parole: • Modello, che in italiano indica "l'oggetto o il termine atto a fornire un conveniente schema di punti di riferimento ai fini della riproduzione o dell'imitazione, talvolta dell'emulazione" (Devoto‐Oli) • Schema, che in italiano indica "il modello convenzionale e semplificato di una realtà, di un fenomeno, di un oggetto, di un problema" (Devoto‐Oli). Dalla definizione discende che né il termine Modello né il termine Schema sono adatti alla traduzione di Pattern perché in entrambi i casi manca il riferimento al contesto o al problema: entrambi i termini sono orientati solo alla soluzione. E' stato proposto di tradurre Pattern con la parola Archetipo, "primo esemplare, assoluto ed autonomo": probabilmente è questo il termine che più si avvicina al significato di Pattern. Il fatto poi che un Pattern sia una triade implica che un problema, da solo, non è un Pattern e una soluzione, da sola, non è un Pattern. Non può essere un Pattern il problema perché una situazione diventa un problema solo in relazione al contesto nel quale essa accade, per di più essa deve essere ricorrente nel contesto perché se fosse solo occasionale finirebbe per non essere un problema, ma un accidente, una casualità. Non può essere un Pattern la soluzione da sola perché, sebbene esistano moltissimi algoritmi, tecnologie, metodi noti che possono essere utilizzati come soluzione, l'obiettivo dei Pattern è quello di descrivere come quella determinata soluzione risolve quel determinato problema in quel determinato contesto, non è quello di descrivere la soluzione in sé. Inoltre la soluzione deve essere consolidata cioè accettata da tutti come la soluzione migliore per quel determinato problema in quel determinato contesto. Detto in una frase: un Pattern è una soluzione consolidata a un problema ricorrente in un contesto specifico. Nella definizione di un Pattern sono presenti anche altri elementi: • Condizioni: descrizione delle condizioni (o vincoli) presenti nel contesto • Annotazioni: considerazioni (positive e negative) sulle conseguenze dell'uso del Pattern corrente • Pattern correlati: relazioni tra il Pattern corrente ed altri Pattern utilizzati nel sistema di riferimento • Usi conosciuti: riferimenti puntuali ad applicazioni pratiche del Pattern corrente [12] I pattern hanno idealmente le seguenti proprietà: • Incapsulamento: ogni pattern incapsula una soluzione e un problema ben definiti. I Pattern sono indipendenti, specifici, e formulati precisamente in maniera tale da rendere chiaro quando essi si applicano e se catturano problemi reali, e per assicurare che ogni passo della sintesi sia presente nella costruzione di una entità completa e riconoscibile, dove ogni parte ha un senso ben preciso. • Capacità di generazione: ogni pattern contiene una descrizione locale e a sé stante del processo che descrive come i costrutti sono realizzati. I pattern vengono scritti per poter essere utilizzati da tutti gli sviluppatori, e non solamente dai progettisti. Molti pattern sono come delle ricette, che evitano le naturali procedure tipiche di una costruzione tradizionale priva di metodologia. • Equilibrio: ogni pattern identifica uno spazio delle soluzioni contenente un invariante che minimizza il conflitto tra forze e vincoli. Quando un pattern è usato in un’applicazione, l’equilibrio dà un significato ad ogni step di progettazione individuato dai vincoli. Il fondamento logico per cui una soluzione necessita di questo equilibrio può essere una derivazione formale o teoretica, un’astrazione da dati empirici, osservazioni di pattern in eventi naturali o artefatti tradizionali, una convincente serie di esempi, l’analisi di soluzioni povere o sbagliate, o qualunque raggruppamento di questi motivi. L’equilibrio è la parte strutturale di nozioni familiari nella computazione e può essere difficile trovare una base per approssimarlo. • Astrazione: i pattern rappresentano astrazioni di esperienza empirica e conoscenza di tutti i giorni. Essi sono generali all’interno del contesto dichiarato, sebbene non necessariamente universali. La costruzione di un pattern è un processo sociale iterativo che colleziona, condivide e amplifica l’esperienza e la conoscenza distribuita. Inoltre, i pattern con una base strutturale o similmente con artefatti naturali e tradizionali sfruttano partizionamenti ben adattati del mondo reale. Spesso i pattern possono essere costruiti in maniera meccanica, fondendo o trasformandone altri per poter applicarli in un dominio differente. Alcuni pattern sono talmente vincolati ad altri più universali da nascere dall’introspezione e dall’intuizione prive di formalismo. Le Euristiche basate sulla progettazione in comune, sull’introspezione, sul collegamento a fatti esistenti, e sul consenso sociale incrementano la probabilità di identificare caratteristiche centrali fisse e variabili, e giocano un ruolo fondamentale anche quando l’ambiente è puramente interno e/o artificiale, aiutando ogni parte a generare un contesto per le altre. • Estendibilità: I Pattern possono essere estesi con vari livelli di dettaglio. AI pattern non è possibile attribuire una parte superiore ed una inferiore; essi sono usati nello sviluppo individuando una collezione di pattern che corrispondono le caratteristiche desiderate del progetto, dove ciascuno di questi può a turno richiedere altri sotto‐pattern. La sperimentazione di possibili varianti e l’analisi delle relazioni tra i pattern che formano il pattern intero aggiungono vincoli, miglioramenti e adattamenti a situazioni specifiche. Per esempio, mentre solo un piccolo insieme di pattern potrebbe essere utilizzato nella progettazione di un certo insieme di programmi, ciascun programma potrebbe essere unicamente realizzato con diversi micro‐pattern. Poiché i dettagli delle istanze di pattern sono incapsulati, essi possono variare dentro vincoli dichiarati. Questi dettagli spesso influenzano e vincolano ulteriormente quelli di altri pattern ad essi relazionati. Ma d’altra parte, questa variabilità rimane dentro i confini dei vincoli di più alto livello. • Componibilità: I Pattern sono relazionati tra loro in maniera gerarchica. I pattern più generali sono collocati al vertice della gerarchia e vincolano quelli di livello inferiore. Queste relazioni possono includerne altre, senza limitarne alcuna. La maggior parte dei pattern sono componibili sia dall’alto che dal basso, minimizzando l’interazione con altri pattern, facendo chiarezza su quando due pattern in relazione devono condividerne un altro, e consentendo anche ampie variazioni nei sotto‐pattern. I pattern sono pianificati concettualmente come un linguaggio che esprime questa stratificazione. Poiché i vari pattern e le loro relazioni con altri sono solo vagamente vincolati e scritti interamente in linguaggio naturale, il linguaggio del pattern è analogo al linguaggio formale di un sistema di produzione, ma ha le stesse proprietà come una infinita e non deterministica capacità di generazione. Ci sono due modi di esporre i Pattern: per cataloghi e per linguaggi. Un catalogo di Pattern organizza i Pattern secondo il problema. E' come un dizionario, ossia una volta identificato il problema, si va alla voce ad esso relativa e si cerca il Pattern che sembra adattarsi meglio. I cataloghi di Pattern contengono relativamente pochi Pattern e ognuno è trattato in modo approfondito, soprattutto per quanto riguarda l'applicabilità e le alternative. Un linguaggio di Pattern raccoglie Pattern che cooperano per risolvere i problemi in un certo contesto. I Pattern singoli sono commentati meno approfonditamente che nei cataloghi di Pattern; l'accento è sull'analisi del contesto e sulla collaborazione fra Pattern. Le due modalità di presentazione dei Pattern sono spesso utilizzate in maniera complementare l'una dell'altra. 3.2 I PATTERN E LA PROGRAMMAZIONE OBJECT ORIENTED La forma e le caratteristiche dei pattern, e i metodi e processi che li circondano non sono legati esclusivamente alla progettazione architetturale. I Pattern rappresentano speciali teorie del mondo. Alexander ha notato che questa caratterizzazione dei pattern si adatta bene alle definizioni comuni delle teorie scientifiche. Le euristiche che governano la costruzione dei pattern sono tutte indistinguibili da quelle teorie. I pattern sono meno generali delle descrizioni delle basi semantiche del linguaggio stesso dei pattern. L’accurata interazione tra i contesti, le forze dello spazio del problema e le varie soluzioni implementative rendono questo framework una base ideale per catturare altri concetti di progettazione. Infatti i pattern di Alexander hanno una forte relazione con i costrutti della programmazione OO; essi possono essere visti come delle estensioni delle caratteristiche di definizione delle classi. Nella progettazione OO, le classi hanno due aspetti principali, analoghi a quelli dei pattern: • Una vista dello spazio del problema, che rappresenta l’aspetto esterno: descrizioni di proprietà, metodi, capacità e servizi supportati come sono visti dai clienti del software o dal mondo esterno. • Una vista dello spazio delle soluzioni, rappresentante l’aspetto interno: descrizioni statiche e dinamiche, vincoli e contratti tra i componenti, delegati, collaboratori, ciascuno dei quali è conosciuto solo rispetto ad una vista esterna probabilmente incompleta. Le classi migliori condividono le proprietà di appropriata astrazione, incapsulamento, estendibilità ed equilibrio. Come i pattern, le classi sono normalmente generative, supportano la costruzione di istanze parametrizzate come anche l’istanzazione di alto livello nel caso di classi generiche. Le classi sono intrinsecamente componibili, nonostante queste composizioni non sempre necessitano di essere espresse come classi. Infatti, poiché i pattern possono descrivere concetti e strutture che non sono oggetti di per sé, il termine pattern può essere più appropriato del termine classe almeno a livello della progettazione OO che è chiamata in diversi modi, tra cui ‘astratta’, ‘architetturale’ e/o ‘funzionale’. I pattern possono dunque aumentare l’espressività e il livello di descrizione supportati dai costrutti dell’object‐oriented. Viceversa, i concetti dell’OO possono essere applicati per rinforzare le nozioni di progettazione basata su pattern: • Linguaggi e strumenti. Alexander organizza grammaticalmente i vari pattern (sebbene in maniera implicita) per sfruttare le proprietà generative dei linguaggi formali. Nella computazione, ogni possibile formale, semiformale, e informale insieme di costrutti è stato ordinato come un linguaggio di qualche tipo. Per esempio, un insieme di classi dell’OO possono essere rappresentate grammaticalmente usando regole di riscrittura che denotano la stratificazione composizionale tipica dei pattern. Comunque non è necessario fare l’analisi grammaticale di una collezione di pattern o classi stesse come per un linguaggio. Nella programmazione, di solito è più conveniente esprimere descrizioni con un linguaggio diffuso, per facilitare la manipolazione dei costrutti, la compilazione, ecc. Estensioni della modellazione OO, i linguaggi di progettazione e/o di programmazione possono essere molto utili per rappresentare i pattern. Tale formalizzazione permette anche la costruzione di strumenti di progettazione. Infatti parecchi sistemi CAAD (Computer Aided Architectural Design) hanno rappresentato i pattern di Alexander nel software. Recentemente Galle ha descritto un framework CAAD, che supporta la progettazione basata su pattern, costruito come un sistema esperto parzialmente object‐oriented. Le caratteristiche di tale sistema possono essere astratte come i pattern e usati nella costruzione di strumenti di progettazione simili. Comunque, passerà del tempo prima che gli strumenti di progettazione OO e i libri acquisiscano l’utilità e l’autorevolezza dei Pattern. • Sottoclassi e raffinamento. Per supportare le relazioni composizionali, tutte le notazioni OO includono un secondo genere di regola di strutturazione per descrivere possibili percorsi alternativi nonostante ci sia già all’interno del framework linguistico un insieme di concetti che catturano concetti di progettazione come la composizione/decomposizione e l’astrazione/raffinamento. Perciò, i metodi e i linguaggi OO aggiungono un nuovo insieme di concetti a questo aspetto del framework di Alexander. Mentre la nozione di variabilità all’interno di classificazioni diffuse ricorre spesso negli scritti di Alexander, egli non utilizza esplicitamente l’idea di un raffinamento strutturale attraverso l’uso di sottoclassi. Ciò probabilmente deriva dal fatto che non c’è un buon motivo per formalizzare il concetto nella progettazione architetturale, all’interno della quale si fa un uso limitato della cattura esplicita dei raffinamenti tra un pattern e la sua realizzazione. Invece, il pattern è sostituito (spesso gradualmente) dalla sua realizzazione. Comunque, nel software, queste forme intermedie possono giocare qualunque ruolo nello sviluppo, incluso essere usate come punti di ramificazione per specializzazioni alternative, basi per una progettazione differenziale, descrizioni di protocolli comuni nei framework OO, e strumenti per lo swapping da un componente all’altro. • Ereditarietà e delegati. Le tecniche di progettazione OO, che includono costrutti vari come sottoclassi, delegati e composizione, superano il potenziale ostacolo della progettazione basata su pattern. I pattern di Alexander prevedono una base per il riuso della progettazione senza implicazioni per il riuso dei componenti, limitando così la generazione di routine e l’uso scontato di componenti standardizzati con un costo e delle proprietà già note, e andando in esecuzione all’interno di problemi di controllo della qualità attraverso l’uso di implementazioni uniche. Questo non è generalmente il caso della progettazione OO. Anche quando un componente standard non è come lo si desidera, accade spesso che specializzazioni alternative, strutture di delega, e/o sottoclassi possono condividere molto codice attraverso varie tecniche della progettazione standard OO. Infatti, questo accade così spesso che i programmatori OO sono sorpresi, si lamentano, e sono a volte incapaci di agire quando ciò non accade. • Adattamento e riflessione. I concetti OO possono anche aiutare a comprendere i concetti di unità metodologica, adattamento, apertura, e riflessione presenti nei lavori di Alexander. La mancanza di una decisa distinzione tra progettazione e produzione software già rende le pratiche di sviluppo difficili da classificare lungo il percorso che va dalla maestria all’ingegneria analitica. Questo viene accentuato quando i sistemi software stessi includono tecniche per l’auto‐adattamento e la riprogettazione. Lavorare nell’ OO e nell’ Intelligenza Artificiale ha condotto alla reificazione e a costrutti di ragionamento che, sebbene attraverso significati non completamente comprensibili, permettono la realizzazione di utili sistemi nei quali i confini tra progettista, modello, progetto, e prodotto quasi svaniscono, come è necessario per esempio nella produzione assistita da computer (CAD, CAM, CIM), dove il trend è stato spostato dai sistemi che incrementano solamente la produttività o riducono i difetti nei prodotti di massa. Invece, i sistemi devono puntare su metodi di sviluppo e meccanismi software adattivi per permettere la riconfigurabilità richiesta per ottenere flessibilità e qualità percepibile dall’utente nella produzione di piccoli eseguibili. • Integrazione dei processi. Mentre i modelli di processo OO rimangono in via di sviluppo, la loro potenziale sinergia con i modelli basati su pattern è ovvia. Lo sviluppatore OO medio personifica l’etica dell’architetto‐costruttore al cuore dei processi di sviluppo basati su pattern. Più di ogni altra cosa le esperienze con le versioni OO dei pattern sono state la spinta che ha condotto i ricercatori OO ad esaminare ed esplorare le molte relazioni tra le base semantiche, gli usi, le attività, e i processi dell’OO e dello sviluppo basato su pattern. Molto lavoro c’è ancora da farsi, incluso la riconcettualizzazione delle tecniche di base e degli idiomi OO, i framework e le micro‐architetture OO, così come i metodi, i processi, gli strumenti, le formalizzazioni, i pattern di sviluppo, l’educazione, e i contesti sociali che supportano al meglio il loro sviluppo. 3.3 TIPOLOGIE DI PATTERN [13] Progettare applicazioni basate sul paradigma object‐oriented non è affatto banale, riuscire a renderle anche riusabili ed estendibili a piacimento è ancora più arduo. Ciò che occorre di volta in volta è saper individuare gli oggetti giusti, con un livello di dettaglio e granularità adeguato, ed essere in grado di definire una struttura gerarchica consistente, basata su un insieme di relazioni e collaborazioni coerente ed efficace. I programmatori più esperti fanno uso di soluzioni object‐
oriented che in passato si sono rivelate vincenti ed efficaci. Tali soluzioni o pattern sono orientate a risolvere particolari problematiche di progettazione e tendono ad introdurre nell'ambito di una struttura ad oggetti quella flessibilità che è necessaria per rendere il codice riutilizzabile ed estendibile. [14] Non tutti i pattern sono uguali ed è bene capire come essi possono essere strutturati e organizzati. Dal momento che esistono diverse tipologie di pattern in funzione della loro area di applicazione, in generale essi possono essere raggruppati in macrocategorie specifiche (dette anche cluster), ciascuna delle quali contenente pattern orientati a risolvere problematiche similari. I cluster possono a loro volta essere suddivisi in sottocategorie a granularità più bassa. Oltre che l’appartenenza ad un determinato cluster, per un pattern è possibile considerare come fattore distintivo anche il livello di astrazione che lo contraddistingue. Nell’ambito del cluster dei pattern relativi allo sviluppo di applicazioni software possiamo individuare tre categorie di pattern caratterizzate da un diverso livello di astrazione. • Pattern architetturali: descrivono lo schema organizzativo della struttura che caratterizza un sistema software. In genere questi pattern individuano le parti del sistema a cui sono associate responsabilità omogenee e le relazioni che esistono tra i diversi sottosistemi. Un esempio significativo di pattern architetturale è rappresentato dal layering, che descrive come suddividere un’applicazione in strati logici sovrapposti e tra loro comunicanti. • Pattern di disegno (design pattern): sono i pattern che si riferiscono alle problematiche legate al disegno object‐oriented. • Pattern di implementazione (idiomi): sono pattern di basso livello specifici per una particolare tecnologia (per esempio, il .NET Framework). Essi descrivono le modalità di implementazione da utilizzare per risolvere problematiche di sviluppo sfruttando in modo mirato le caratteristiche peculiari di una particolare piattaforma. Come detto, ciascuno di questi gruppi è caratterizzato da un grado di astrazione differente. I design pattern si collocano tra i pattern architetturali, troppo generici per essere orientati a risolvere problematiche di disegno, e i pattern idiomatici, molto legati alla tecnologia a cui si riferiscono e all’implementazione vera e propria. I design pattern descrivono soluzioni che lasciano sempre e comunque un certo grado di libertà nella loro adozione e implementazione, dal momento che non descrivono mai soluzioni che sono valide per una piattaforma specifica, ma al contrario hanno una validità più generale e trasversale rispetto alla tecnologia. 3.4 I DESIGN PATTERN [15] Uno degli aspetti più delicati nel disegno object‐oriented (OOD) consiste nella scomposizione del sistema in oggetti. Si tratta di una attività complessa dal momento che entrano in gioco fattori non direttamente collegati alle specifiche funzionali quali l'accoppiamento tra oggetti, la loro dipendenza, la coesione funzionale, la granularità, la flessibilità, l'estendibilità e la riusabilità. Questi aspetti devono necessariamente influenzare il processo di scomposizione, talvolta anche in modi tra loro discordanti. Esistono diversi approcci che permettono di scomporre in oggetti un sistema. È possibile partire dai casi d'uso, individuare in essi i sostantivi e i verbi e da questi ricavare le classi e i metodi corrispondenti al fine di ottenere la struttura ad oggetti desiderata. In alternativa, è possibile porre l'attenzione principalmente sulle responsabilità e sulle collaborazioni nell'ambito del sistema in fase di studio e, in funzione di esse, individuare gli oggetti necessari per gestirle opportunamente. O ancora, è possibile partire da un modello del mondo reale e tradurre gli elementi individuati in altrettanti oggetti. Ognuno di questi approcci concorre a definire la struttura statica del sistema che, se da un lato rispecchia la realtà di oggi, dall'altro in genere non si presta direttamente ad evolvere nel tempo e ad adattarsi alla realtà di domani. L'esigenza di individuare una struttura flessibile ed estendibile, in grado di rispondere al meglio ai cambiamenti nel tempo, porta inevitabilmente a cercare soluzioni di disegno che facciano largo uso di astrazioni e oggetti non necessariamente collegati alla realtà in esame per non limitare in modo importante l'evoluzione del sistema. I design pattern aiutano ad individuare queste astrazioni e gli oggetti in grado di rappresentarle. Essi concorrono a limitare le dipendenze, incrementando l'estendibilità e la riusabilità, e rendono di conseguenza il sistema più manutenibile nel tempo, in grado di rispondere ai cambiamenti in modo efficiente ed elegante. I design pattern agevolano il riuso di soluzioni architetturali note, rendendo accessibili ai progettisti e agli sviluppatori tecniche di disegno universalmente riconosciute come valide ed efficaci. In questo senso i design pattern aiutano i progettisti a operare scelte consapevoli tra le varie alternative possibili allo scopo di favorire la riusabilità. [16] Tra i vari design pattern noti in letteratura, i pattern GoF (Gang of Four) formano senza dubbio un cluster fondamentale. Conoscere i nomi e le motivazioni di questi pattern rappresenta senza dubbio un buon punto di partenza per poter successivamente approfondire i dettagli che li riguardano ed eventualmente valutarne l’utilizzo. I 23 pattern che compongono questo cluster sono organizzati in tre categorie distinte e tra loro complementari: • pattern creazionali, che riguardano la creazione di istanze; • pattern strutturali, che si riferiscono alla composizione di classi e oggetti; • pattern comportamentali, che si occupano delle modalità con cui classi e oggetti interagiscono tra loro in relazione alle loro diverse responsabilità. I pattern creazionali sono cinque: • Abstract Factory: fornisce un’interfaccia per creare famiglie di oggetti correlati o dipendenti senza specificare le classi concrete; • Builder: separa la costruzione di un oggetto complesso dalla sua rappresentazione, in modo tale che lo stesso processo di costruzione possa creare rappresentazioni differenti; • Factory Method: definisce un’interfaccia per creare un oggetto, ma lascia alle classi derivate di decidere quale classe istanziare. Questo pattern permette a una classe di delegare la creazione di un’istanza alle sue classi derivate; • Prototype: specifica il tipo degli oggetti da creare usando un’istanza prototipale e crea i nuovi oggetti a partire da questo prototipo; • Singleton: assicura che una classe abbia solamente un’unica istanza e fornisce un entry‐point globale ad essa. I pattern strutturali sono sette: • Adapter: converte l’interfaccia di una classe in un’altra interfaccia compatibile con il client. Questo pattern consente a classi diverse di collaborare tra loro, cosa che non sarebbe possibile diversamente a causa della incompatibilità delle rispettive interfacce; • Bridge: disaccoppia un’astrazione dalla sua implementazione affinché entrambe possano variare in modo indipendente; • Composite: compone una serie di oggetti in una struttura ad albero secondo una gerarchia di tipo part‐whole (parte‐totalità). Questo pattern permette ai client di trattare oggetti singoli o loro raggruppamenti in modo uniforme; • Decorator: aggiunge dinamicamente responsabilità addizionali ad un oggetto. Questo pattern fornisce un meccanismo alternativo e flessibile all’ereditarietà per estendere le funzionalità base; • Facade: fornisce un’interfaccia unificata a un insieme di interfacce in un sottosistema. Questo pattern definisce un’interfaccia ad un livello più alto che rende il sottosistema più facile da usare, dato che ne maschera la complessità interna; • Flyweight: usa la condivisione per gestire in modo efficiente un numero considerevole di oggetti a granularità fine; • Proxy: fornisce un surrogato di un oggetto per controllare l’accesso ad esso. I pattern comportamentali sono undici: • Chain of Responsability: evita di accoppiare il mittente di una richiesta con il suo destinatario dando la possibilità a più di un oggetto di gestire la richiesta. Collega tra loro gli oggetti ricevitori e fa passare la richiesta da un oggetto all’altro fino a destinazione; • Command: incapsula una richiesta in un oggetto, rendendo possibile parametrizzare i client con diverse tipologie di richieste, con richieste bufferizzate (queue), con richieste registrate (log) e con richieste annullabili (undo); • Interpreter: dato un linguaggio, definisce una rappresentazione della sua grammatica e del relativo interprete, che usa la rappresentazione per interpretare le frasi del linguaggio; • Iterator: fornisce un modo per accedere in modo sequenziale agli elementi di una collezione di oggetti senza esporre la sua rappresentazione sottostante; • Mediator: definisce un oggetto che incapsula le modalità di interazione di un insieme di oggetti. Questo pattern favorisce un basso accoppiamento, evitando che gli oggetti facciano riferimento l’uno con l’altro esplicitamente, e permette di variare le modalità di interazione in modo indipendente dagli oggetti stessi; • Momento: senza violare l’incapsulamento, recupera e rende esplicito lo stato interno di un oggetto in modo tale che l’oggetto stesso possa essere riportato allo stato originale in un secondo momento; • Observer: definisce una dipendenza uno‐a‐molti fra oggetti in modo tale che, se un oggetto cambia stato, tutti gli oggetti da esso dipendenti vengono notificati e aggiornati automaticamente; • State: permette ad un oggetto di modificare il suo comportamento quando il suo stato interno cambia; • Strategy: definisce una famiglia di algoritmi, li incapsula e li rende intercambiabili fra loro. Questo pattern permette di variare gli algoritmi in modo indipendente dal contesto di utilizzo; • Template Method: definisce lo scheletro di un algoritmo in un metodo di una classe base, delegando alcuni passi alle classi derivate. Questo pattern permette di ridefinire nelle classi derivate alcuni passi dell’algoritmo senza cambiare la struttura dell’algoritmo stesso; • Visitor: rappresenta un’operazione da svolgersi sugli elementi di una struttura ad oggetti. Questo pattern consente di definire nuove operazioni senza cambiare le classi degli elementi su cui opera. I design pattern rappresentano un modo elegante e flessibile per fare disegno object‐oriented. Essi possono essere usati sia in fase di progettazione di un sistema, sia per rivedere e migliorare un sistema già esistente applicando il refactoring al codice. Sia in un caso che nell’altro, i design pattern vanno comunque applicati con attenzione e cognizione di causa. I design pattern non rappresentano la panacea di tutti i mali nella progettazione del software, né tanto meno vanno usati a priori in modo estensivo con l’inevitabile conseguenza di produrre over‐engineering (sovradimensionamento). L’approccio corretto deve sempre porre al centro la ricerca da parte del progettista delle soluzioni di disegno più semplici, sfruttando i pattern in modo mirato per ottenere questo obiettivo. Pertanto i design pattern vanno usati solamente quando effettivamente servono. In caso contrario il rischio è quello di produrre inutilmente una struttura ad oggetti troppo complessa e articolata, di difficile comprensione e tale da richiedere grossi sforzi per essere gestita nel tempo. L’esperienza e il buon senso in molti casi permettono di fare la scelta giusta. 3.4.1
I DESIGN PATTERN CREAZIONALI [17] I pattern creazionali forniscono un’astrazione del processo di creazione delle istanze delle classi, favorendo l’indipendenza del sistema dalle modalità di creazione e dai tipi concreti effettivamente generati. Ci sono due aspetti che caratterizzano i pattern appartenenti a questa categoria: ƒ la capacità di mascherare al client la conoscenza degli oggetti concreti creati, sfruttando tipi astratti per definire le interfacce di riferimento; ƒ la capacità di nascondere le modalità di creazione all’utilizzatore dell’istanza. Queste due caratteristiche conferiscono una notevole flessibilità al processo di creazione, dal momento che ciò che viene creato in generale risulta essere disaccoppiato dal contesto di utilizzo. Infatti solo l’oggetto creatore conosce il tipo effettivo dell’istanza e ciò che viene esternamente reso pubblico è unicamente l’interfaccia di riferimento. Di seguito verranno mostrati una serie di esempi relativi ai pattern principali di tale categoria. ABSTRACT FACTORY L’Abstract Factory (detto anche Kit) è un pattern creazionale che ha lo scopo di fornire un’interfaccia per la creazione di famiglie di oggetti tra loro correlati o dipendenti limitando l’accoppiamento derivante dall’uso diretto delle classi concrete. L’applicazione di questo pattern si rivela assai utile quando si vuole rendere un sistema indipendente dalle modalità di creazione, composizione e rappresentazione degli oggetti costituenti, rendendo note unicamente le interfacce e non le implementazioni concrete. Questo consente di rendere tra loro interscambiabili le diverse implementazioni che soddisfano una determinata interfaccia, senza che il contesto d’uso dell’istanza debba essere modificato al variare dell’implementazione scelta. I partecipanti di questo pattern sono (tra parentesi sono indicati gli oggetti equivalenti nell’esempio proposto successivamente): ƒ AbstractFactory (IShapeFactory) Definisce l’interfaccia di riferimento per gli oggetti che creano le istanze. ƒ ConcreteFactory (MyShapeFactory) Implementa in modo concreto l’interfaccia definita da AbstractFactory e crea effettivamente una tipologia specifica di oggetti appartenenti ad una famiglia. ƒ AbstractProduct (Circle e Rectangle) Definisce l’interfaccia di riferimento per una famiglia di oggetti da creare tramite il factory corrispondente. ƒ ConcreteProduct (MyCircle e MyRectangle) Implementa in modo concreto l’oggetto appartenente alla famiglia per cui vale l’interfaccia AbstractProduct e che viene creato dall’oggetto factory corrispondente. ƒ Client (Program) Utilizza unicamente le classi astratte del factory e dell’oggetto da creare, senza conoscerne gli aspetti implementativi. L’annullamento dell’accoppiamento tra il client e gli oggetti concreti è ottenuto tramite l’inversione delle dipendenze, uno dei principi base dell’Object Oriented Design (OOD). L’esempio proposto per questo pattern include innanzitutto un’interfaccia IShape che dichiara un metodo Print che deve essere presente in tutte le istanze che la implementano. In particolare queste istanze sono rappresentate dagli oggetti di tipo MyCircle e MyRectangle, che implementano l’interfaccia IShape in modo indiretto tramite le classi base Circle e Rectangle rispettivamente. L’oggetto MyShapeFactory implementa l’interfaccia IShapeFactory e nei metodi CreateCircle e CreateRectangle crea tramite il costruttore di default le istanze di tipo MyCircle e MyRectangle. Nel client (Program) non viene fatto alcun riferimento alle classi MyCircle e MyRectangle. Dal momento che il client non conosce in modo diretto i tipi effettivamente creati dal factory, qualsiasi dipendenza di Program dai tipi concreti effettivamente utilizzati al suo interno viene eliminata. using System;
namespace DesignPatterns.AbstractFactory
{
public interface IShape
{
void Print();
}
public class Rectangle : IShape
{
public virtual void Print()
{
Console.WriteLine("Rectangle");
}
}
public class Circle : IShape
{
public virtual void Print()
{
Console.WriteLine("Circle");
}
}
public interface IShapeFactory
{
Rectangle CreateRectangle();
Circle CreateCircle();
}
public class MyRectangle : Rectangle
{
public override void Print()
{
Console.WriteLine("MyRectangle");
}
}
public class MyCircle : Circle
{
public override void Print()
{
Console.WriteLine("MyCircle");
}
}
public class MyShapeFactory : IShapeFactory
{
public Rectangle CreateRectangle()
{
return new MyRectangle();
}
public Circle CreateCircle()
{
return new MyCircle();
}
}
public class Program
{
static void Main(string[] args)
{
IShapeFactory fac = new MyShapeFactory();
Circle c = fac.CreateCircle();
Rectangle r = fac.CreateRectangle();
c.Print();
r.Print();
Console.ReadLine();
}
}
}
BUILDER Il pattern Builder consente di dividere la costruzione di un oggetto complesso e composito dalla sua rappresentazione, in maniera tale che lo stesso processo di costruzione possa essere utilizzato per creare rappresentazioni diverse. L’applicazione di questo pattern si rivela assai indicata quando l’algoritmo di creazione dell’oggetto composito deve essere mantenuto distinto dalle parti costituenti e dal modo con cui esse sono unite insieme a formare un tutt’uno, consentendo un migliore controllo del processo di costruzione e isolando da tutto il resto il codice di assemblaggio. I partecipanti di questo pattern sono: ƒ Builder Rappresenta l’interfaccia di riferimento (generalmente astratta) per la creazione delle parti costituenti l’oggetto da costruire. ƒ ConcreteBuilder (Wheel, Engine e Chassis) Genera e costruisce ogni singola parte concreta dell’oggetto composito tramite l’implementazione di Builder. Definisce un metodo di costruzione BuildPart e uno di accesso al risultato della costruzione GetResult. ƒ Director (CarBuilder) Assembla l’oggetto utilizzando l’interfaccia Builder. Infatti il client (Program) istanzia questo oggetto configurandolo in maniera tale da farlo operare con l’oggetto Builder desiderato. ƒ Product (Car) Rappresenta l’oggetto composito che è il risultato dell’operazione di costruzione e assemblaggio. Quello proposto è un esempio molto semplificato di applicazione del pattern in questione. Si tratta della costruzione di un oggetto di tipo Car che comprende quattro proprietà, ovvero un array composto da 4 elementi di tipo Wheel (ruota), un Engine (motore) e un Chassis (telaio). Ciascuna di queste parti implementa in modo particolare il metodo ToString di System.Object (lo possiamo considerare come l’equivalente del metodo GetResult nella rappresentazione generale) e definisce un costruttore, accettando eventuali parametri utili alla creazione delle singole istanze (lo possiamo pensare come l’equivalente del metodo BuildPart nella rappresentazione generale). L’oggetto che è incaricato di costruire l’assemblato è la classe CarBuilder che, tramite il metodo statico CreateCar, accetta i parametri di costruzione validi per le diverse parti e chiama i costruttori per la generazione delle istanze. Il metodo ToString della classe Car richiama internamente i metodi ToString delle parti costituenti per ottenere una rappresentazione completa dell’oggetto. using System;
namespace DesignPatterns.Builder
{
public class Car
{
private Wheel[] _wheels;
private Engine _engine;
private Chassis _chassis;
public Wheel Wheel1
{
set { _wheels[0] = value; }
get { return _wheels[0]; }
}
public Wheel Wheel2
{
set { _wheels[1] = value; }
get { return _wheels[1]; }
}
public Wheel Wheel3
{
set { _wheels[2] = value; }
get { return _wheels[2]; }
}
public Wheel Wheel4
{
set { _wheels[3] = value; }
get { return _wheels[3]; }
}
public Engine Engine
{
set { _engine = value; }
get { return _engine; }
}
public Chassis Chassis
{
set { _chassis = value; }
get { return _chassis; }
}
public Car()
{
_wheels = new Wheel[4];
}
public override string ToString()
{
return _wheels[0].ToString() + " / " +
_wheels[1].ToString() + " / " +
_wheels[2].ToString() + " / " +
_wheels[3].ToString() + " / " +
_engine.ToString() + " / " + _chassis.ToString();
}
}
public class Wheel
{
private double _size;
public Wheel(double size) { _size = size; }
public override string ToString()
{
return "Wheel " + _size.ToString();
}
}
public class Engine
{
private double _power;
public Engine(double power) { _power = power; }
public override string ToString()
{
return "Engine " + _power.ToString();
}
}
public class Chassis
{
public Chassis() {}
public override string ToString()
{
return "Chassis";
}
}
public class CarBuilder
{
public static Car CreateCar(double wheelSize, double enginePower)
{
Car c = new Car();
c.Wheel1 = new Wheel(wheelSize);
c.Wheel2 = new Wheel(wheelSize);
c.Wheel3 = new Wheel(wheelSize);
c.Wheel4 = new Wheel(wheelSize);
c.Engine = new Engine(enginePower);
c.Chassis = new Chassis();
return c;
}
}
public class Program
{
static void Main(string[] args)
{
Console.WriteLine(CarBuilder.CreateCar(180, 110).ToString());
Console.ReadLine();
}
}
}
FACTORY METHOD Il pattern Factory Method definisce un’interfaccia di classe per la creazione di un oggetto, lasciando ai tipi derivati la decisione su quale oggetto debba essere effettivamente istanziato. Il pattern può rivelarsi utile quando una classe non è in grado di conoscere a priori il tipo di oggetti da creare piuttosto che quando si vuole delegare la creazione di un oggetto alle sottoclassi. L’applicazione del pattern consente di eliminare le dipendenze dai tipi concreti utilizzati. I partecipanti di questo pattern sono: ƒ Product (Shape) Definisce l’interfaccia base per gli oggetti da creare. ƒ ConcreteProduct (Circle e Rectangle) Rappresenta una implementazione concreta di Product. ƒ Creator (ShapeCreator) Dichiara il metodo factory che restituisce un oggetto di tipo Product. ƒ ConcreteCreator Definisce il metodo factory effettivo per la creazione di un’istanza particolare di tipo Product. Nell’esempio riportato di seguito è presente unicamente la classe concreta ShapeCreator. Nella sua interfaccia essa include il metodo CreateShape(ShapeType), che, invece di essere virtuale o astratto per essere personalizzato nelle classi derivate, è direttamente implementato e accetta un parametro che consente di selezionare il tipo dell’istanza da creare. In base al valore del parametro, il metodo ritorna un’istanza di Circle o di Rectangle, a seconda dei casi. Il metodo factory ritorna sempre e comunque un oggetto di tipo Shape, classe astratta base per Circle e Rectangle. using System;
namespace DesignPatterns.FactoryMethod
{
public enum ShapeType
{
Rectangle = 1,
Circle = 2
}
public abstract class Shape
{
public abstract void Draw();
}
public class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("Rectangle");
}
}
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Circle");
}
}
public class ShapeCreator
{
private static ShapeCreator _instance = new ShapeCreator();
public static ShapeCreator Instance
{
get { return _instance; }
}
public Shape CreateShape(ShapeType type)
{
switch (type)
{
case ShapeType.Rectangle:
return new Rectangle();
case ShapeType.Circle:
return new Circle();
default:
throw new ArgumentException("type");
}
}
}
public class Program
{
static void Main(string[] args)
{
Shape[] shapes =
new Shape[] { ShapeCreator.Instance.CreateShape(ShapeType.Circle),
ShapeCreator.Instance.CreateShape(ShapeType.Rectangle)
};
foreach (Shape s in shapes)
s.Draw();
Console.ReadLine();
}
}
}
SINGLETON Lo scopo del pattern Singleton è quello di assicurare che per una determinata classe esista un’unica istanza attiva, fornendo un entry‐point globale all’istanza stessa. Questo pattern si può rivelare utile nel caso in cui si abbia la necessità di centralizzare informazioni e comportamenti in un’unica entità condivisa da tutti i suoi utilizzatori. La soluzione che più si adatta a risolvere la questione associata al pattern (unicità dell’istanza) consiste nell’associare alla classe stessa la responsabilità di creare le proprie istanze. In questo modo è la classe stessa che può assicurare che nessun’altra istanza possa essere creata, intercettando e gestendo in modo centralizzato le richieste di creazione di nuove istanze. L’unico partecipante del pattern pertanto è: ƒ Singleton (One, Two e Three) Definisce un membro per accedere all’unica istanza esistente, generalmente creata internamente alla classe stessa. L’esempio proposto mostra tre casistiche diverse di applicazione del pattern. La classe One prevede l’inizializzazione statica dell’istanza. La proprietà Instance ritorna l’oggetto equivalente di tipo One statico e privato. La classe Two prevede l’inizializzazione dinamica su richiesta tramite il controllo del riferimento all’istanza. La proprietà Instance ritorna anche in questo caso l’oggetto equivalente di tipo Two statico e privato. La classe Three effettua un doppio controllo sul riferimento all’istanza, dentro e fuori ad un blocco a mutua esclusione e in base ad esso attiva l’istanza. Ancora una volta la proprietà Instance ritorna l’oggetto equivalente di tipo Three statico e privato. Se i primi due casi non sono thread‐safe, il terzo lo è (nell’ambito di uno stesso appdomain). La presenza del blocco di mutua esclusione garantisce che la creazione dell’istanza sia effettivamente eseguita una volta sola, anche in un contesto multi‐thread. using System;
using System.Threading;
namespace DesignPatterns.Singleton
{
public sealed class One
{
private static One _instance = new One();
private One() {}
public static One Instance
{
get { return _instance; }
}
public void DoSomething()
{
Console.WriteLine("One");
}
} // One
public sealed class Two
{
private static Two _instance;
private Two() {}
public static Two Instance
{
get
{
if (_instance == null)
_instance = new Two();
return _instance;
}
}
public void DoSomething()
{
Console.WriteLine("Two");
}
} // Two
public sealed class Three
{
private static Three _instance;
private static object _syncLock = new object();
private Three() {}
public static Three Instance
{
get
{
if (_instance == null)
lock (_syncLock)
{
if (_instance == null)
_instance = new Three();
} // lock
return _instance;
}
}
public void DoSomething()
{
Console.WriteLine("Three");
}
} // Three
public class Program
{
static void Main(string[] args)
{
One.Instance.DoSomething();
Two.Instance.DoSomething();
Three.Instance.DoSomething();
Console.ReadLine();
}
}
}
Dal momento che la soluzione associata ad un design pattern viene espressa in un modo sufficientemente generale da lasciare numerosi gradi di libertà, l’implementazione non necessariamente segue lo schema di base. Non sempre esiste una corrispondenza esatta tra la forma generale e le scelte implementative che conducono alla soluzione della problematica concreta di sviluppo. Questo deve far riflettere sul reale ruolo che i pattern ricoprono e sul modo con cui devono essere utilizzati. Essi infatti forniscono di volta in volta una soluzione di massima ad un problema specifico di disegno, ma la scelta finale su come mettere in pratica questa soluzione dipende sempre dal buon senso e dalla consapevolezza di chi applica concretamente il pattern. 3.4.2
I DESIGN PATTERN STRUTTURALI [18] I design pattern di tipo strutturale riguardano le modalità con cui classi e oggetti vengono aggregati allo scopo di formare entità più complesse. In generale possiamo distinguere due tipologie di pattern strutturali: ƒ pattern strutturali basati sulle classi, che sfruttano l’ereditarietà multipla (se prevista) per combinare tra loro le interfacce e le implementazioni; ƒ pattern strutturali basati sugli oggetti, che descrivono le modalità di composizione di oggetti al fine di estendere in fase di esecuzione le funzionalità di una classe (cosa non possibile nel caso della composizione statica e tramite ereditarietà). In generale la maggior parte dei pattern strutturali sono basati sugli oggetti. Il pattern Adapter è un esempio di pattern basato sia sulle classi che sugli oggetti. ADAPTER L’Adapter (detto anche Wrapper) è un pattern strutturale basato sulle classi e sugli oggetti che ha lo scopo di convertire l’interfaccia di una classe in un’altra interfaccia. Questo pattern permette a classi diverse di operare insieme anche nel caso in cui questo non sarebbe possibile per via delle interfacce tra loro non compatibili. Questo pattern trova una forte applicabilità soprattutto in quegli scenari dove oggetti simili appartenenti ad ambienti o sistemi diversi hanno la necessità di cooperare. In questi casi l’utilizzo di un oggetto wrapper consente di adattare tra loro oggetti strutturalmente organizzati in modo diverso, di farli comunicare tra loro e di metterli in correlazione. Dal momento che in .NET Framework i tipi possono derivare da un unico tipo base, viene di seguito preso in considerazione unicamente il caso di composizione basata sugli oggetti, tralasciando la versione basata sulle classi che sfrutta l’ereditarietà multipla. I partecipanti di questo pattern sono: ƒ Target (Contact) Definisce l’interfaccia di riferimento alla quale l’oggetto Adaptee si deve adattare. ƒ Adaptee (Employee) Rappresenta l’interfaccia che deve essere adattata. ƒ Adapter (EmployeeAdapter) Adatta l’interfaccia di Adaptee all’interfaccia di Target. ƒ Client (Program) Utilizza unicamente oggetti compatibili con l’interfaccia di Target. L’esempio proposto per questo pattern include due classi Employee e Customer con interfacce diverse. La classe Employee include due proprietà FirstName e LastName che rappresentano il nome e il cognome dell’impiegato. La classe Customer (cliente) deriva direttamente dalla classe base astratta Contact che include un’unica proprietà FullName che rappresenta il nome completo del contatto, composto dalla concatenazione di nome e cognome. In questo scenario non è possibile assegnare direttamente un’istanza di Employee a un riferimento di tipo Contact. Per poterlo fare, occorre adattare l’interfaccia di Employee a quella di Contact. La classe EmployeeAdapter deriva da Contact e presenta un campo privato di tipo Employee. In fase di creazione l’istanza della classe Employee da adattare viene passata all’adapter come parametro sul costruttore. La proprietà FullName di EmployeeAdapter esegue la concatenazione del nome e del cognome dell’impiegato utilizzando le proprietà dell’oggetto interno. using System;
using System.Collections.Generic;
using System.Text;
namespace DesignPatterns.Adapter
{
public class Employee
{
private string _firstName;
private string _lastName;
public Employee(string firstName, string lastName)
{
_firstName = firstName; _lastName = lastName;
}
public string FirstName
{
get { return _firstName; }
}
public string LastName
{
get { return _lastName; }
}
}
public abstract class Contact
{
public abstract string FullName { get; }
}
public class Customer : Contact
{
private string _fullName;
public Customer(string fullName)
{
_fullName = fullName;
}
public override string FullName
{
get { return _fullName; }
}
}
public class EmployeeAdapter : Contact
{
private Employee _employee;
public EmployeeAdapter(Employee employee)
{
_employee = employee;
}
public override string FullName
{
get { return _employee.FirstName + " " + _employee.LastName; }
}
}
public class Program
{
public static void Main(string[] args)
{
Contact c = new Customer("Riccardo Golia");
Console.WriteLine(c.FullName);
c = new EmployeeAdapter(new Employee("Riccardo", "Golia"));
Console.WriteLine(c.FullName);
Console.ReadLine();
}
}
}
COMPOSITE Il pattern Composite, di tipo strutturale basato sugli oggetti, consente di creare gerarchie di oggetti aggregando insieme elementi primitivi e compositi direttamente a runtime. Questo pattern può essere applicato per rappresentare strutture ad albero in una logica parte‐tutto (part‐whole), dove ciascun elemento può a sua volta aggregare insieme altri elementi della stessa specie e dove oggetti singoli e composizioni possono essere trattati in modo uniforme. Uno dei pregi principali nell’applicazione di questo pattern risiede nel fatto di rendere molto agevole l’aggiunta di nuove tipologie di oggetti componenti. In più, dal momento che i vari elementi vengono trattati in modo uniforme, l’utilizzo della struttura composita nell’ambito del client risulta essere molto semplificata. Ciascun elemento, seppur presentando caratteristiche simili, può essere caratterizzato da comportamenti assai diversi, a seconda dei casi. Il client non conosce la differenza che esiste tra i diversi elementi e, in particolare, tra oggetti primitivi e oggetti compositi, pertanto tratta entrambe le tipologie allo stesso modo, con un significativo miglioramento della leggibilità del codice. I partecipanti di questo pattern sono: ƒ Component (DocumentElement) Fornisce l’interfaccia di riferimento valida per tutti gli elementi della struttura ad albero, ovvero sia per gli elementi terminali che per gli elementi intermedi. Rappresenta la classe base per Composite (oggetto composito) e per Leaf (oggetto primitivo). ƒ Composite (DocumentChapter) Definisce il comportamento degli elementi intermedi che hanno figli e che aggregano insieme altri Component. ƒ Leaf (DocumentParagraph) Definisce il comportamento degli elementi terminali che non hanno figli e che rappresentano gli oggetti primitivi. ƒ Client (Program) Utilizza la struttura composita, accedendo ai vari elementi tramite l’interfaccia Component. Consideriamo l’organizzazione di un documento suddiviso in capitoli e paragrafi rappresentati da altrettanti oggetti il cui tipo base è la classe astratta DocumentElement. Sebbene la struttura del documento considerata è volutamente molto semplificata per non introdurre una complessità tale da limitare l’efficacia e la chiarezza dell’esempio, il documento risulta essere un’aggregazione più o meno complessa dei suoi elementi costituenti. Nell’esempio ciascun capitolo (classe DocumentChapter) è un elemento composito che contiene uno o più paragrafi. L’elemento terminale della struttura del documento è rappresentato dal paragrafo (classe DocumentParagraph). Occorre fare alcune considerazioni. Si noti innanzitutto che, a parità di interfaccia, i diversi oggetti di tipo DocumentElement mostrano comportamenti peculiari a seconda dei casi: l’implementazione interna dell’elemento terminale (il paragrafo) è differente da quella dell’elemento composito (il capitolo). Questo fa sì che, nella rappresentazione del documento, il metodo Write (string) possa essere eseguito in modo iterativo mantenendo il codice molto essenziale e senza la necessità di introdurre strutture particolari di controllo del flusso, dal momento che ogni singola istanza presenta un comportamento particolare in funzione del tipo di appartenenza. Si noti inoltre che nulla vieta che un capitolo possa a sua volta contenere uno o più sottocapitoli sempre di tipo DocumentChapter, dato che gli elementi della lista dei suoi figli sono in ogni caso di tipo DocumentElement (il tipo base). Si noti infine come l’aggiunta di una nuova tipologia di elemento (per esempio, una ipotetica classe DocumentSection) non rappresenti un grosso problema, dal momento che la struttura è pensata per evolvere in modo flessibile nel tempo. using System;
using System.Collections.Generic;
namespace DesignPatterns.Composite
{
public abstract class DocumentElement
{
public abstract void Add(DocumentElement child);
public abstract void Remove(DocumentElement child);
public abstract void Write();
}
public class DocumentChapter : DocumentElement
{
private int _chapterNumber;
private List<DocumentElement> _children =
new List<DocumentElement>();
public DocumentChapter(int number)
{
_chapterNumber = number;
}
public override void Add(DocumentElement child)
{
_children.Add(child);
}
public override void Remove(DocumentElement child)
{
_children.Remove(child);
}
public override void Write()
{
Console.WriteLine("Chapter " + _chapterNumber.ToString());
foreach (DocumentElement child in _children)
child.Write();
}
}
public class DocumentParagraph : DocumentElement
{
private string _text = string.Empty;
public DocumentParagraph(string text) { _text = text; }
public override void Add(DocumentElement child)
{
throw new NotSupportedException();
}
public override void Remove(DocumentElement child)
{
throw new NotSupportedException();
}
public override void Write()
{
Console.WriteLine(_text);
}
}
public class Program
{
public static void Main(string[] args)
{
DocumentParagraph pg1 = new DocumentParagraph("1.1");
DocumentParagraph pg2 = new DocumentParagraph("1.2");
DocumentParagraph pg3 = new DocumentParagraph("2.1");
DocumentParagraph pg4 = new DocumentParagraph("2.2");
DocumentChapter chp1 = new DocumentChapter(1);
chp1.Add(pg1);
chp1.Add(pg2);
chp1.Write();
DocumentChapter chp2 = new DocumentChapter(2);
chp2.Add(pg3);
chp2.Add(pg4);
chp2.Write();
Console.ReadLine();
}
}
}
FACADE Il pattern Facade, di tipo strutturale basato sugli oggetti, permette di individuare un’interfaccia unificata per un insieme di interfacce nell’ambito di un sottosistema. Questo pattern in pratica consente di definire un’interfaccia a un livello più alto che semplifica l’accesso alle funzionalità erogate dal sottosistema e che fornisce un’entry‐point unico al sottosistema stesso. La presenza di un oggetto di facciata in un sottosistema permette di mascherare all’esterno la sua complessità interna, limitando le dipendenze dirette e l’accoppiamento. Il client comunica con il sottosistema inviando le sue richieste all’oggetto di facciata, il quale a sua volta funge da tramite verso le parti interne più idonee a fornire la risposta attesa. Il client non conosce come il sottosistema è strutturato e non ha accesso diretto ai suoi oggetti interni con i quali comunica unicamente tramite l’oggetto di facciata. I partecipanti di questo pattern sono: ƒ Facade (SystemManager) Conosce la struttura del sottosistema e delega agli oggetti interni più appropriati la richieste provenienti dall’esterno. ƒ Classi di Subsystem (SystemOne, SystemTwo e SystemThree) Forniscono le funzionalità interne adatte a rispondere alle richieste provenienti da Facade. Esse non hanno conoscenza dell’esistenza di Facade e non dipendono da esso. Nell’esempio proposto la classe statica SystemManager presenta un metodo DoSomething() che interagisce con le parti interne del sottosistema. Internamente al metodo vengono infatti richiamati in sequenza altri tre metodi di altrettante classi appartenenti al sottosistema. Il client non conosce l’esatto ordine con cui le chiamate dei metodi vengono effettuate, né quali classi del sottosistema sono effettivamente coinvolte. Di fatto la classe SystemManager disaccoppia il client dalle classi interne, eliminando completamente eventuali dipendenze. using System;
namespace DesignPatterns.Facade
{
public static class SystemManager
{
public static void DoSomething()
{
new SystemOne().DoSomething();
new SystemTwo().DoSomething();
new SystemThree().DoSomething();
}
}
internal class SystemOne
{
public void DoSomething()
{
Console.WriteLine("One");
}
}
internal class SystemTwo
{
public void DoSomething()
{
Console.WriteLine("Two");
}
}
internal class SystemThree
{
public void DoSomething()
{
Console.WriteLine("Three");
}
}
public class Program
{
public static void Main(string[] args)
{
SystemManager.DoSomething();
Console.ReadLine();
}
}
}
PROXY Lo scopo del pattern Proxy (detto anche Surrogate) è quello di fornire un surrogato o un segnaposto di un altro oggetto per controllarne l’accesso. Questo pattern, di tipo strutturale basato sugli oggetti, è applicabile ogni volta che si voglia disporre di un riferimento a un oggetto più versatile di un semplice puntatore, tale da permettere, per esempio, di controllare l’accesso all’oggetto vero e proprio piuttosto che di fornire una rappresentazione locale di un oggetto remoto. Il pattern in questione introduce un livello di indirezione nell’accesso a un oggetto. Questa indirezione ricopre significati diversi a seconda dei casi: ƒ si parla di proxy remoto quando si vuole nascondere al client che un oggetto risiede in uno spazio di indirizzamento diverso (esempio classico: Web Service); ƒ si parla di proxy virtuale quando si vuole eseguire un’ottimizzazione nella creazione di un oggetto particolarmente “costoso” e pesante piuttosto che memorizzare informazioni aggiuntive relative all’oggetto rappresentato per posticipare l’accesso all’oggetto stesso; ƒ si parla di proxy di protezione quando si vuole gestire l’accesso a un oggetto tramite l’esecuzione di azioni preliminari di controllo. I partecipanti di questo pattern sono: ƒ Proxy (ServiceProxy) Fornisce un’interfaccia identica a quella di Subject e agisce da sostituto di RealSubject. ƒ Subject (IService) Definisce l’interfaccia comune per Proxy e RealSubject, rendendo possibile l’uso di Proxy in tutte le situazioni in cui è possibile utilizzare RealSubject. ƒ RealSubject (MyService) Rappresenta l’oggetto vero e proprio di cui Proxy è il surrogato. Nell’esempio proposto l’interfaccia IService fornisce il contratto che deve essere rispettato sia dall’oggetto vero e proprio di tipo MyService, sia dal suo surrogato ServiceProxy. Tramite la classe factory ServiceFactory, il client attiva un’istanza della classe proxy che assegna a un riferimento di tipo IService. Il client peraltro non è consapevole di stare usando un surrogato, semplicemente richiama i membri definiti dal contratto, indipendentemente dal tipo concreto istanziato. La classe proxy permette di eseguire più codice rispetto all’oggetto originario: internamente al metodo HandleRequest() vengono infatti eseguite istruzioni prima e dopo la chiamata del metodo di destinazione. Nel caso dell’esempio il tipo di istruzioni aggiuntive incluse nella funzione sono davvero semplici, ma si può arrivare ad avere situazioni in cui il codice presente all’interno della classe proxy è molto più corposo e sostanzioso. using System;
namespace DesignPatterns.Proxy
{
public interface IService
{
void HandleRequest();
}
public class MyService : IService
{
public void HandleRequest()
{
Console.WriteLine("Handling the request...");
}
}
public class ServiceProxy : IService
{
private MyService _service;
public ServiceProxy(MyService svc)
{
_service = svc;
}
public void HandleRequest()
{
Console.WriteLine("Preprocessing by proxy...");
_service.HandleRequest();
Console.WriteLine("Postprocessing by proxy...");
}
}
public static class ServiceFactory
{
public static IService CreateService()
{
return new ServiceProxy(new MyService());
}
}
public class Program
{
static void Main(string[] args)
{
IService svc = ServiceFactory.CreateService();
svc.HandleRequest();
Console.ReadLine();
}
}
}
3.4.3
I DESIGN PATTERN COMPORTAMENTALI [19] I design pattern comportamentali si riferiscono alla distribuzione delle responsabilità tra oggetti tra loro correlati. Essi non affrontano unicamente gli aspetti relativi alla struttura degli oggetti e delle classi interagenti, ma si focalizzano soprattutto sulle modalità di comunicazione e collaborazione. La maggior parte di questi pattern fornisce soluzioni per incapsulare le diverse funzionalità in un oggetto specifico con l’intento di delegare ad esso l’esecuzione del codice vero e proprio. Questo approccio permette in generale di eliminare le dipendenze dirette tra i vari oggetti coinvolti, limitando l’accoppiamento e facilitando la possibilità di estendere e modificare il codice senza grossi sforzi. La distribuzione delle responsabilità porta inevitabilmente a ridurre la genericità di ciascun partecipante dei pattern, aspetto in gran parte dovuto alla forte specializzazione che caratterizza le diverse classi che di volta in volta sono delegate a fornire le funzionalità richieste. COMMAND Il pattern Command (noto anche come Action o Transaction) permette di inoltrare richieste ad oggetti senza conoscere assolutamente nulla dell’operazione da eseguire o del destinatario della richiesta. Questo è possibile per il fatto che il pattern in questione tratta la richiesta come un oggetto differente rispetto sia al richiedente che all’oggetto destinatario. Questo oggetto specifica l’azione da svolgere sul destinatario, sfruttandone i comportamenti in modo tale da poter portare a termine la richiesta. Il pattern Command permette quindi di incapsulare una richiesta in un oggetto permettendo al client di inoltrare richieste di varia natura, anche in funzione di destinatari diversi. Il pattern in questione può essere applicato: ƒ per parametrizzare gli oggetti rispetto ad una azione da compiere; ƒ per specificare, accodare ed eseguire svariate richieste in tempi diversi, anche trasferendo un comando da un contesto di esecuzione ad un altro; ƒ per consentire l’annullamento delle operazioni eseguite (undo, rollback), mantenendo preventivamente lo stato per annullare gli effetti dei comandi stessi. Il vantaggio più significativo nell’applicazione di questo pattern è il fatto di ottenere un perfetto disaccoppiamento tra l’oggetto che invoca il comando e il destinatario, ovvero quello che conosce il modo per portare a termine l’operazione. Questo aspetto consente di poter aggiungere comandi ulteriori associati a destinatari diversi in modo abbastanza immediato senza che sia necessario modificare le classi già esistenti. Inoltre il fatto che i comandi siano oggetti distinti permette di poterli eventualmente aggregare insieme (applicando il pattern Composite) allo scopo di formare un comando complesso, costituito da una serie arbitraria di azioni. I partecipanti di questo pattern sono: ƒ Command (ICommand) Definisce l’interfaccia di riferimento per ogni comando. ƒ ConcreteCommand (Open, Read, Write e Close) Definisce un legame tra il Receiver e un’azione. Implementa in modo particolare il metodo Execute() invocando i metodi del Receiver. ƒ Invoker (Reader e Writer) Aggrega i diversi comandi e delega a loro l’esecuzione delle azioni previste. ƒ Receiver (System.Console) Conosce il modo di eseguire le operazioni associate ad una particolare richiesta. ƒ Client (Program) Tramite l’Invoker attiva ed esegue un ConcreteCommand che va a interessare il Receiver corrispondente. L’esempio proposto si riferisce all’esecuzione di due sequenze di comandi da parte delle classi Reader e Writer. L’esecuzione di ciascuna azione consiste nella scrittura sulla console di una stringa contenente il nome di ciascun comando appartenente alla sequenza. L’oggetto di destinazione pertanto è System.Console: Reader e Writer portano a compimento le operazioni desiderate sfruttando il suo metodo WriteLine(string). I singoli comandi sono classi diverse che implementano in modo particolare il metodo Execute(), definito nell’interfaccia ICommand. Si noti come il client (classe Program) sia completamente all’oscuro sia di come ciascun comando viene eseguito, sia di quale sequenza effettiva di azioni viene intrapresa di volta in volta. using System;
namespace DesignPatterns.Command
{
public interface ICommand
{
void Execute();
}
public class Open : ICommand
{
public virtual void Execute()
{
Console.WriteLine("Open");
}
}
public class Read : ICommand
{
public virtual void Execute()
{
Console.WriteLine("Read");
}
}
public class Write : ICommand
{
public virtual void Execute()
{
Console.WriteLine("Write");
}
}
public class Close : ICommand
{
public virtual void Execute()
{
Console.WriteLine("Close");
}
}
public class Reader
{
private ICommand[] _commands;
public Reader()
{
_commands = new ICommand[] { new Open(), new Read(), new Close() };
}
public void Read()
{
foreach (ICommand cmd in _commands)
cmd.Execute();
}
}
public class Writer
{
private ICommand[] _commands;
public Writer()
{
_commands = new ICommand[] { new Open(), new Write(), new Close() };
}
public void Write()
{
foreach (ICommand cmd in _commands)
cmd.Execute();
}
}
public class Program
{
static void Main(string[] args)
{
Reader reader = new Reader();
reader.Read();
Writer writer = new Writer();
writer.Write();
Console.ReadLine();
}
}
}
OBSERVER Il pattern Observer (noto anche col nome Publish‐Subscribe) permette di definire una dipendenza uno a molti fra oggetti, in modo tale che se un oggetto cambia il suo stato interno, ciascuno degli oggetti dipendenti da esso viene notificato e aggiornato automaticamente. L’Observer nasce dall’esigenza di mantenere un alto livello di consistenza fra classi correlate, senza peraltro produrre situazioni di forte dipendenza e accoppiamento elevato. Il pattern Observer si presta ad essere utilizzato in diversi casi. Ad esempio, quando un’astrazione presenta due diversi aspetti tra loro dipendenti, è possibile definire due classi in cui incapsulare questi aspetti in modo tale da poterli utilizzare in maniera indipendente. In questo scenario occorre comunque prevedere un meccanismo di comunicazione che permetta di mantenere la consistenza tra le istanze delle due classi e il pattern Observer fornisce una soluzione elegante al problema senza generare accoppiamento. Un’altra situazione tipica di utilizzo si ha quando la modifica dello stato di un oggetto (per esempio, un controllo dell’interfaccia utente) implica un cambiamento dello stato di altri oggetti correlati, a prescindere dal loro numero (per esempio, altri controlli). In questo caso la modifica dello stato dell’oggetto (detto anche Publisher) si deve propagare agli oggetti correlati (detti anche Subscriber) in modo tale che essi possano aggiornare il loro stato interno di conseguenza. I partecipanti di questo pattern sono: ƒ Subject (delegate Subject.Notify) Conosce i suoi Observer. Fornisce l’interfaccia per associare e rimuovere oggetti Observer. ƒ Observer Fornisce l’interfaccia di notifica per gli oggetti a cui devono essere segnalati i cambiamenti di stato di Subject. ƒ ConcreteSubject (Subject) Contiene lo stato monitorato dagli Observer a cui viene inviata la notifica. ƒ ConcreteObserver (Observer) Mantiene un riferimento ad un oggetto ConcreteSubject. Contiene le informazioni da mantenere sincronizzate con lo stato del Subject. Implementa il metodo di gestione della notifica da eseguire allo scopo di mantenere sincronizzati gli stati degli oggetti. Il meccanismo per inviare notifiche nell’ambito del .NET Framework è fornito in modo nativo dai tipi delegate e dagli eventi. Una classe che funge da Publisher (classe Subject) espone in generale sulla sua interfaccia una serie di eventi corrispondenti ad un tipo particolare di delegate. Le classi Subscriber (classe Observer) sottoscrivono l’evento e ad esso associano un metodo interno (comunemente detto event handler) che deve rispettare la firma definita dal tipo delegate associato all’evento. L’event handler viene chiamato nel momento in cui il Publisher inoltra ai suoi Subscriber la notifica, rendendo possibile in questo modo l’esecuzione di codice in ciascun Subscriber al variare dello stato interno del Publisher. using System;
namespace DesignPatterns.Observer
{
public class Subject
{
public delegate void Notify();
public event Notify OnNotify;
public void DoSomething()
{
if (OnNotify != null)
{
Console.WriteLine("Subject fires event");
OnNotify();
}
}
}
public class Program
{
public class Observer
{
private static int _idx = 1;
private int _number;
public Observer(Subject s)
{
s.OnNotify += new Subject.Notify(EventHandler);
_number = _idx++;
}
public override string ToString()
{
return _number.ToString();
}
public void EventHandler()
{
Console.WriteLine("Observer {0} was called by subject", this);
}
}
public static void Main(string[] args)
{
Subject s = new Subject();
Observer o1 = new Observer(s);
Observer o2 = new Observer(s);
s.DoSomething();
Console.ReadLine();
}
}
}
STRATEGY Il pattern Strategy permette di definire una famiglia di algoritmi, di incapsularli e renderli intercambiabili fra loro. Questo pattern consente agli algoritmi di variare in modo indipendente rispetto al loro contesto di utilizzo, fornendo un basso accoppiamento tra le classi partecipanti del pattern e una alta coesione funzionale delle diverse strategie di implementazione. Il pattern Strategy fornisce di fatto un meccanismo di configurazione per una determinata classe, utilizzando un comportamento specifico scelto fra tanti e “iniettato” nel momento dell’utilizzo. L’operazione di iniezione può avvenire secondo diverse modalità, a seconda dei casi. La modalità più comune di iniezione prevede l’uso del costruttore della classe per selezionare l’algoritmo concreto da eseguire successivamente. L’esempio proposto si riferisce all’utilizzo di un algoritmo per ordinare un insieme di numeri interi. Dal momento che l’ordinamento può essere fatto in svariati modi, esistono diverse versioni parallele dello stesso algoritmo. Incapsulando ciascuna di queste implementazioni in un oggetto specifico, è possibile renderle tra loro intercambiabili senza impattare sul codice che effettivamente utilizza l’algoritmo e sul risultato dell’operazione. La classe astratta SortAlgorithm rappresenta il tipo base da cui ogni implementazione dell’algoritmo deriva. Nell’esempio sono inclusi tre tipi distinti di ordinamento corrispondenti ad altrettante classi concrete: QuickSort, BubbleSort e MergeSort. La classe Context carica l’istanza di un algoritmo passata tramite il costruttore. Il metodo SortArray(int[]) di Context internamente utilizza il metodo Sort(int) di SortAlgorithm, implementato in modo particolare da ciascuna delle classi derivate. In questo modo il client (classe Program) utilizza unicamente Context per eseguire gli ordinamenti e qualsiasi modifica degli algoritmi e delle classi relative non impatta su di esso. using System;
namespace DesignPatterns.Strategy
{
public abstract class SortAlgorithm
{
public abstract int[] Sort(int[] array);
}
public class QuickSort : SortAlgorithm
{
public override int[] Sort(int[] array)
{
Array.Sort<int>(array);
return array;
}
}
public class BubbleSort : SortAlgorithm
{
public override int[] Sort(int[] array)
{
throw new NotImplementedException();
}
}
public class MergeSort : SortAlgorithm
{
public override int[] Sort(int[] array)
{
throw new NotImplementedException();
}
}
public class Context
{
private SortAlgorithm _algorithm;
public Context(SortAlgorithm algorithm)
{
_algorithm = algorithm;
}
public int[] SortArray(int[] array)
{
return _algorithm.Sort(array);
}
}
public class Program
{
public static void Main(string[] args)
{
int[] array = { 21, 10, 71, 18, 8, 5, 20, 1, 67 };
Context ctx = new Context(new QuickSort());
array = ctx.SortArray(array);
foreach (int i in array)
Console.WriteLine(i.ToString());
Console.ReadLine();
}
}
}
TEMPLATE METHOD Il pattern Template Method permette di definire la struttura di un’algoritmo all’interno di un metodo di una superclasse (detto appunto metodo template), delegando alcuni passi alle classi derivate. Questo pattern lascia che le classi derivate possano definire in modo particolare alcuni passi dell’algoritmo senza dover implementare ogni volta da zero la struttura dell’algoritmo stesso. Il pattern Template Method fornisce un meccanismo molto efficace di riuso del codice. I metodi template infatti richiamano per lo più una serie di metodi astratti che devono essere implementati in modo particolare nelle classi derivate. Questo fa sì che nelle classi derivate non debba essere fornita ogni volta l’implementazione di tutto l’algoritmo, ma solamente delle parti salienti, limitando in questo modo le duplicazioni e migliorando la leggibilità e la manutenibilità del codice. Talvolta, invece dei metodi astratti, nella superclasse può risultare comodo utilizzare metodi virtuali per rappresentare i diversi passi dell’algoritmo. In questo caso la classe base fornisce per ciascun passo dell’algoritmo un’implementazione predefinita e la personalizzazione nelle classi derivate può essere omessa qualora non risulti necessaria. I partecipanti di questo pattern sono: ƒ AbstractClass (AlgorithmBase) Definisce i vari metodi astratti che rappresentano i diversi passi di un determinato algoritmo. Include un metodo generale che definisce la struttura dell’algoritmo e ingloba le chiamate ai vari metodi astratti. ƒ ConcreteClass (MyAlgorithm) Fornisce un’implementazione concreta dei vari metodi astratti definiti in AbstractClass. Nell’esempio proposto la funzione ExecuteAlgorithm() della classe AlgorithmBase è un metodo template. Internamente esso richiama tre metodi protetti e astratti corrispondenti ai vari passi dell’algoritmo da eseguire. Dal momento che AlgorithmBase è un classe astratta, i suoi membri astratti devono essere obbligatoriamente implementati nelle classi derivate (classe MyAlgorithm). Peraltro l’implementazione presente in MyAlgorithm non va ad interessare l’interfaccia pubblica definita nel tipo base, dal momento che i vari metodi astratti sono visibili unicamente nelle classi derivate in quanto protetti. In questo modo è possibile variare l’algoritmo per ogni diversa specializzazione di AlgorithmBase semplicemente ridefinendo di volta in volta i vari passi dell’algoritmo, senza peraltro modificare la struttura del tipo base e il modo in cui esso viene utilizzato nell’ambito del codice del client (classe Program). using System;
namespace DesignPatterns.TemplateMethod
{
public abstract class AlgorithmBase
{
public void ExecuteAlgorithm()
{
ExecuteStepOne();
ExecuteStepTwo();
ExecuteStepThree();
}
protected abstract void ExecuteStepOne();
protected abstract void ExecuteStepTwo();
protected abstract void ExecuteStepThree();
}
public class MyAlgorithm : AlgorithmBase
{
protected override void ExecuteStepOne()
{
Console.WriteLine("Step One");
}
protected override void ExecuteStepTwo()
{
Console.WriteLine("Step Two");
}
protected override void ExecuteStepThree()
{
Console.WriteLine("Step Three");
}
}
public class Program
{
public static void Main(string[] args)
{
new MyAlgorithm().ExecuteAlgorithm();
Console.ReadLine();
}
}
}
3.5 I PATTERN DI PROCESSO Per definire cosa è un pattern di processo è necessario prima soffermarsi sulle due parole che compongono tale termine: ‘processo’ e ‘pattern’. Un processo è definito come una serie di azioni in cui uno o più input sono usati per produrre uno o più output. Per quel che riguarda la definizione di pattern si fa riferimento, come già discusso in precedenza, ad Alexander che arriva a dare la definizione di pattern facendo notare che alcune caratteristiche diffuse ricorrono molto spesso, nonostante apparentemente esse non sono mai le stesse. Alexander mostra che sebbene ogni costruzione è unica, ciascuna può essere creata seguendo una collezione di pattern generali. In altre parole, un pattern è una soluzione generale a un problema comune, dalla quale è possibile derivare una soluzione specifica. Coplien, nel suo documento “A Generative Development‐Process Pattern Language”, arriva a dare una definizione del termine pattern di processo sostenendo che “i pattern delle attività dentro un’organizzazione (e quindi dentro i suoi progetti) sono chiamati pattern di processo”. Un pattern di processo è dunque una collezione di tecniche generali, azioni, e/o processi (attività) per sviluppare software object‐
oriented. Una importante caratteristica di un pattern di processo è che esso descrive cosa si dovrebbe fare, senza dare i dettagli esatti di come ciò dovrebbe essere fatto. Quando sono applicati insieme in modo organizzato, essi possono essere utilizzati per costruire processi software all’interno di un’ organizzazione. Poiché i pattern di processo non specificano come realizzare un dato processo, possono essere impiegati come blocchi riusabili da cui è possibile realizzare processi software su misura che vengono incontro alle esigenze di un’organizzazione. Collegati ai pattern di processo ci sono i pattern organizzativi, che descrivono tecniche di gestione comuni e strutture organizzative. C’è da dire che tali due tipologie di pattern vanno di pari passo, sebbene ora verrà focalizzata l’attenzione solo sui pattern di processo. Una caratteristica importante dei pattern di processo è che è possibile utilizzarli per tutti gli aspetti dello sviluppo. L’ambito di un singolo pattern di processo può variare da una visuale di alto livello di come le applicazioni sono sviluppate ad una visuale più dettagliata di una specifica parte del processo software. I pattern di processo possono essere classificati in tre tipologie che sono: 1. Task Process Pattern. Questa tipologia di pattern di processo mostra in maniera dettagliata i passi per realizzare uno specifico task; è questo il caso dei pattern Technical Review e Reuse First. 2. Stage Process Pattern. Questo tipo di pattern di processo evidenzia i passi, che sono spesso realizzati iterativamente, di un singolo stage di progetto. Uno stage di progetto è una forma di pattern di processo di alto livello, spesso costituita da numerosi task process pattern. E’ presentato uno stage process pattern per ogni stage di progetto di un processo software, è il caso del Program Stage mostrato in figura 18, appartenente all’Object Oriented Software Process della figura 20. 3. Phase Process Pattern. Questo tipo di pattern di processo mostra quelle che sono le interazioni tra i stage process pattern per una singola fase del progetto, come nel caso delle fasi Initiate e Delivery. Un phase process pattern, il pattern Construct è mostrato in figura 19, è una collezione di due o più stage process pattern. [20] Una credenza diffusa nel mondo della programmazione ad oggetti è che lo sviluppo object‐oriented sia di natura iterativa. Sebbene questo poteva essere in parte vero per i piccoli progetti dei primi anni 90, ciò non è più vero per le applicazioni mission‐critical e su larga scala di oggi. La realtà è che lo sviluppo OO è seriale nelle grandi release e iterativo in quelle più piccole che sono state sviluppate negli anni. I phase process pattern, che sono eseguiti in serie, sono costituiti da stage process pattern che sono eseguiti iterativamente. Fino ad oggi, la maggior parte del lavoro nei pattern di processo è stata impiegata nei task process pattern, mentre solo una piccola parte del lavoro è stata impiegata nelle rimanenti due tipologie di pattern. Si può dire che quando si guarda i pattern di processo dal punto di vista della definizione di un processo software per un’organizzazione allora è necessario che le tre tipologie di pattern di processo siano tutte efficaci. I task process pattern sono un componente chiave di un processo software, ma le altre due tipologie di pattern sono necessarie per organizzarli e per racchiuderli all’interno di un contesto significativo per l’organizzazione. 3.5.1
TASK PROCESS PATTERN Ciò che viene creato durante lo sviluppo necessita di essere convalidato per assicurare che venga incontro alle necessità della comunità di utenti e sia conforme agli standard di qualità dell’organizzazione. Il task process pattern Technical Review descrive come organizzare, condurre e comprendere attraverso la revisione di una o più realizzazioni. Tale pattern tratta la stessa tematica di altri pattern appartenenti alla stessa tipologia, come i pattern Validation by Teams [21], Review [22], Creator‐
Reviewer [23], and Group Validation. LE MOTIVAZIONI Ci sono numerose motivazioni che rendono necessario l’utilizzo del pattern di processo Technical Review. Per prima cosa, ciò che viene prodotto (modelli, prototipi, documenti, codice sorgente, …) durante il processo di sviluppo contribuisce alla realizzazione dei prodotti software da rilasciare alla comunità di utenti, perciò è necessario assicurarsi che ogni cosa sviluppata sia di qualità sufficiente per essere inserita nel prodotto. [24] In secondo luogo, poiché il costo della correzione dei difetti aumenta come una palla di neve che rotola giù per la montagna man mano che essi vengono individuati più in là nel ciclo di vita dello sviluppo, c’è l’esigenza di individuarli e correggerli il prima possibile. Poiché è difficile rivedere il proprio lavoro, è necessaria la presenza di ‘un altro paio di occhi’ che revisionino ciò che è stato prodotto. In aggiunta c’è la necessità di comunicare ad altri il proprio operato, e un modo per fare ciò è avere delle persone del team di sviluppo che revisionino ciò che è stato prodotto. IL CONTESTO INIZIALE Ci sono uno o più parti sviluppate che devono essere revisionate e il team di sviluppo attende che ciò sia fatto. LA SOLUZIONE La figura 17 mostra che ci sono sei passi di base nel pattern di processo Technical Review (model reviews, document reviews, prototype reviews, requirement reviews, e code inspections sono tutti specifici processi che seguono tale pattern). I passi sono i seguenti: 1. Il team di sviluppo si prepara per la revisione. Le parti che devono essere revisionate vengono raggruppate, organizzate appropriatamente, e inserite all’interno di package per poter essere presentate a coloro che devono revisionarle. 2. Il team di sviluppo indica che sono pronti per la revisione. Il team di sviluppo deve informare il manager di sviluppo, spesso un membro dell’assicurazione di qualità, quando essi sono pronti per far revisionare il loro operato oltre a comunicargli cosa essi vogliono che sia revisionato. 3. Il manager di revisione esegue una revisione superficiale. La prima cosa che il manager di revisione deve fare è determinare se il team di sviluppo ha prodotto qualcosa che è pronto per essere revisionato. Il manager probabilmente discuterà del lavoro svolto con il leader del team e farà un’analisi veloce di ciò che i membri del team hanno realizzato. Il principale obiettivo è che il lavoro che viene revisionato sia abbastanza buono da garantire una revisione dell’operato dell’intero team. 4. Il manager di revisione pianifica ed organizza la revisione. Il manager di revisione deve decidere dove effettuare la revisione e gli strumenti necessari per eseguirla, invitare persone appropriate, e distribuire in anticipo qualunque materiale necessario per la revisione. 5. Viene eseguita la revisione. Le revisioni tecniche possono richiedere da parecchie ore a parecchi giorni, dipende dalla dimensione di cosa si sta revisionando, sebbene le migliori revisioni durano meno di due ore allo scopo di non annoiare le persone coinvolte. L’intero team di sviluppo deve attendere, o almeno le persone responsabili di ciò che viene revisionato, per rispondere a domande e spiegare il proprio operato. Ci sono solitamente dai tre ai cinque revisionatori, oltre al manager di revisione, che sono responsabili dell’esecuzione della revisione. E’ importante che sia revisionato tutto il materiale. E’ abbastanza facile guardare qualcosa velocemente e assumere che sia corretto. E’ compito del moderatore della revisione assicurare che sia ispezionata e chiarita qualunque cosa. 6. Vengono riportati i risultati della revisione. Durante la revisione viene prodotto un documento che descrive sia i punti di forza che le debolezze dell’operato che è stato revisionato. Questo documento deve prevedere sia la descrizione di ogni debolezza che un’indicazione di cosa è necessario fare per risolverla. Questo documento verrà consegnato al team di sviluppo in maniera tale che i membri possano esaminarlo ed effettuare le correzioni appropriate dove sono stati riscontrati degli errori, e al manager di revisione che lo utilizzerà nelle successive revisioni per controllare che le debolezze sono state risolte. Figura 17 – Il pattern di processo Technical Preview IL CONTESTO RISULTANTE Il management superiore è così sicuro che il team di sviluppo ha prodotto dei risultati di qualità che vengono incontro alle esigenze della comunità di utenti. Il team di sviluppo e i revisionatori hanno una miglior comprensione di ciò che stanno realizzando e di come il loro lavoro si adatta al progetto software complessivo. I membri del team e i revisionatori probabilmente apprendono nuove tecniche durante la revisione, tecniche applicabili allo stesso operato prodotto, tecniche di gestione applicate durante la revisione, o tecniche di sviluppo suggerite durante la revisione per migliorare ciò che si è prodotto. 3.5.2
STAGE PROCESS PATTERN Un importante aspetto dello sviluppo software, uno di molti per essere esatti, è l’attuale sviluppo del codice sorgente. Il pattern Program stage process descrive gli iterativi task/attività della programmazione. LE MOTIVAZIONI I programmatori hanno bisogno di sviluppare del software che venga incontro alle necessità delle loro comunità di utenti, necessità che devono ripercuotersi nella forma dei modelli e dei documenti prodotti durante gli stadi di Model e Define and Validate Initial Requirements. Il codice sorgente sviluppato deve riflettere l’informazione contenuta in tali documenti, allo stesso tempo può guidare i loro cambiamenti man mano che i programmatori acquisiscono una comprensione sempre più dettagliata del dominio (solitamente più dettagliata di quella che possiedono i modellisti). Per di più, molte organizzazioni vogliono che il software sia sviluppato in maniera rapida ed efficiente, ma al tempo stesso vogliono che esso sia manutenibile ed estendibile in maniera tale che i successivi cambiamenti possano essere effettuati velocemente ed efficientemente. IL CONTESTO INIZIALE Prima che la codifica possa iniziare devono essere valutate numerose condizioni. Innanzitutto, devono essere presi in considerazione i modelli di progettazione per il codice che si intende scrivere. Inoltre va presa in considerazione l’infrastruttura del progetto, definita durante lo stadio Define Infrastructure della fase Initiate. L’infrastruttura include gli strumenti di sviluppo e supporto che gli sviluppatori useranno, come anche gli standard e le linee guida che essi seguiranno. Infine, i programmatori devono essere disponibili a fare il lavoro. LA SOLUZIONE La figura 18 mostra il pattern di processo per lo stadio Program, mostrando che c’è più a questo stadio che scrivendo semplicemente codice sorgente: è necessario comprendere i modelli, individuare gli artefatti riusabili per ridurre il carico di lavoro, documentare cosa si sta andando a scrivere, scrivere il codice, ispezionarlo e migliorarlo, testarlo e correggere gli errori, e infine inserirlo in package. Figura 18 – Il pattern di processo Program La prima cosa che i programmatori dovrebbero fare prima di iniziare la codifica è prendersi del tempo per comprendere i modelli che definiscono cosa essi devono realizzare. Sebbene questo suoni chiaro ed ovvio, molto spesso non viene fatto. Se i programmatori non comprendono il modello prima di iniziare la codifica, allora non ha senso la creazione avvenuta in precedenza dei modelli. L’obiettivo dei programmatori è comprendere i problemi di progettazione, e come il loro codice verrà inserito nell’intera applicazione. Prima di scrivere codice essi devono spendere tempo per comprendere sia il problema che la soluzione. Una volta che hanno fatto ciò, i programmatori dovrebbero dare uno sguardo a ciò che è stato già realizzato. Uno dei vantaggi dello sviluppo object‐oriented è di aumentare il riuso del codice, ma è necessario capire che si può incrementare il riuso solo sui propri progetti se non si adottano gli accorgimenti sopra citati. Ciò significa che qualcuno deve fare uno sforzo per riutilizzare ciò che è già stato sviluppato, e il miglior modo per fare ciò è dare uno sguardo alle parti riusabili prima di iniziare a scrivere codice. In fase di progettazione dovrebbero essere stati considerati anche i problemi di riuso. Prima di iniziare a scrivere codice, esso dovrebbe essere prima documentato. Sebbene questo sembri inizialmente non intuitivo, l’esperienza mostra che i programmatori che iniziano a scrivere brevi commenti che descrivono la loro logica di codifica sono molto più produttivi di quelli che non lo fanno. Il motivo di ciò è semplice: la parte difficile della programmazione consiste nella comprensione della logica esatta, non nella scrittura del codice che implementa la logica. Scrivere codice sorgente prima di documentarlo è un comune antipattern di processo, che bisogna cercare di evitare se possibile. Documentando la logica nella forma di brevi commenti, anche chiamati pseudocodice, si evita il problema di scrivere del codice che successivamente deve essere buttato perché non lavora correttamente. Una volta che i programmatori hanno speso del tempo per comprendere i modelli che stanno implementando, hanno cercato i componenti riusabili per ridurre il loro carico di lavoro, e hanno scritto almeno la documentazione iniziale per il loro codice, sono pronti per iniziare a scrivere codice sorgente object‐oriented. Il codice scritto dovrebbe essere conforme alle linee guida e agli standard definiti per il progetto; tale conformità verrà verificata attraverso successive revisioni del codice. Durante tutto il processo di codifica i programmatori devono costantemente spendere del tempo per sincronizzare il loro codice sorgente con il modello. Durante la codifica diviene chiaro se i modelli o la documentazione non includono tutta l’informazione necessaria al programmatore. In tal caso, egli dovrebbe prima parlare con il modellista per trovare una soluzione, e poi sia il codice che i modelli dovrebbero essere modificati per adeguarsi alla soluzione. La cosa importante è che il codice rispecchi l’informazione contenuta nei modelli e viceversa. Il codice sorgente prodotto dal team di sviluppo sarà ispezionato, in parte o nella sua interezza, come parte dello stadio Test in the Small. Per prepararsi ad una revisione del codice, che utilizzerà il task process pattern Technical Review, un programmatore dovrebbe assicurarsi che il proprio codice passerà ragionevolmente l’ispezione. Ciò significa che il codice deve soddisfare la progettazione, seguire gli standard, essere ben documentato, facilmente comprensibile, e scritto bene. Molto spesso si vuole lasciare l’ottimizzazione alla fine poiché si vuole ottimizzare solo il codice che necessità di ciò: molto spesso una piccola percentuale di codice impiega la grande maggioranza del tempo di processazione, e questo è il codice che dovrebbe essere ottimizzato. Un classico sbaglio fatto dai programmatori inesperti è cercare di ottimizzare tutto il loro codice, anche il codice che è già eseguito velocemente. E’ preferibile ottimizzare solo il codice che lo richiede ed occuparsi di cose più interessanti di cercare di spremere ogni singolo ciclo di CPU. La creazione di un build è costituita dalle operazioni di compilazione e di linking del codice sorgente come Java e C++, o dal raggruppamento del codice in package utilizzando linguaggi come Smalltalk. Gli strumenti utilizzati per creare i build, tra cui i compilatori, i linkers, e i pacchettizzatori, sono chiamati builders. I build ben riusciti producono software che può essere eseguito e testato, mentre dei build mal riusciti producono una indicazione di cosa riguardante il codice non va bene ai builders. I build sono importanti perché aiutano ad individuare i problemi di integrazione nel codice; se il codice non si integra bene, esso probabilmente fermerà il build, il quale mostrerà che il codice compila, ciò tira su di morale il team di sviluppo. Molto prima di provare ad integrare e a raggruppare in package l’applicazione è necessario prima avere un piano per fare ciò. Tre sono le parti chiave necessarie agli sviluppatori che sono responsabili dell’integrazione e della formazione dei package dell’applicazione: un piano di integrazione che descrive la schedulazione, le risorse, e l’approccio per integrare gli elementi di un’applicazione, un documento di descrizione della versione (VDD) che descrive gli elementi di un’applicazione e le loro relazioni, e un diagramma di deployment che mostra come il software verrà deployato alla comunità di utenti. Per integrare e raggruppare in package l’applicazione, è necessario realizzare e testare il software, produrre la documentazione di supporto, e sviluppare il processo di installazione per il software. Se si realizzano dei build giornalieri, allora il primo step è chiaro, è necessario seguire solamente la procedura di costruzione esistente per l’assemblaggio e successivamente realizzare i file del codice sorgente che costituiscono l’applicazione. IL CONTESTO RISULTANTE Le seguenti condizioni devono essere prese in considerazione prima che lo stadio Program possa essere considerato completato. Innanzitutto, il codice dovrebbe essere ispezionato. In secondo luogo, esso dovrebbe superare i test ed essere stato ottimizzato a sufficienza. Infine, se possibile il software dovrebbe essere integrato e raggruppato in package per la consegna. 3.5.3
PHASE PROCESS PATTERN Il principale obiettivo della fase di Construct, la seconda fase in serie del Processo Software Object‐Oriented (OOSP) di figura 20, è realizzare software che sia pronto per essere testato e consegnato alla comunità di utenti. Questo software sarà accompagnato dai modelli e dal codice sorgente usati nello sviluppo, da un piano di test per verificare che il software funzioni correttamente, da qualunque artefatto riutilizzabile in progetti futuri, dalla documentazione iniziale, e dai piani di addestramento che supportano il software. LE MOTIVAZIONI Ci sono diverse motivazioni applicabili alla Construct Phase, inclusa la comprensione di come lavora la fase sia attraverso il management superiore che attraverso gli sviluppatori; una non garantita focalizzazione sulla programmazione in assenza di modellizzazione, di testing, e generalizzazione; e una predilezione da parte di ciascuno coinvolto a smussare gli angoli e prendere scorciatoie che non migliorano la qualità del software che sempre più spesso è prodotto in ritardo e con costi che sono al di sopra del budget consentito. IL CONTESTO INIZIALE La fase di Construct può essere preceduta da due differenti fasi, o dalla fase Initiate o dalla fase di Maintain and Support (vedi la figura 20). In ogni caso, ci sono numerose condizioni che devono essere prese in considerazione prima che la fase di Construct possa iniziare. Innanzitutto, i documenti chiave di gestione del progetto (piano del progetto, stima, schedulazione, valutazione del rischio) dovrebbero essere disponibili e aggiornati. In secondo luogo, deve essere definita l’infrastruttura del progetto, o almeno una buona parte di essa, in maniera tale che gli strumenti, i processi, e gli standard siano a disposizione del team di sviluppo. Inoltre, dovrebbero essere presenti i requisiti di alto livello per il software come anche la carta del progetto per il team. Per di più, i cambiamenti applicabili al software che si sta costruendo dovrebbero essere riportati sulla release sulla quale si sta lavorando (ciò è applicabile solo al software esistente che è stato modificato). Infine, il team di sviluppo dovrebbe essere selezionato ed essere disponibile ogni qual volta il progetto lo richieda. LA SOLUZIONE La figura 19 mostra il phase process pattern Construct. Una importante conseguenza è che non si inizia dal nulla quando si entra nella fase di Construct, poiché sono stati definiti importanti documenti di gestione come il piano del progetto e la valutazione dei rischi iniziali, dovrebbero inoltre essere stati definiti i requisiti iniziali per l’applicazione, l’infrastruttura del progetto, e ottenuto il finanziamento per il progetto. I quattro stadi iterativi della fase di Construct sono altamente correlati. Lo stadio di Model si concentra sull’astrazione del dominio tecnico e/o del problema attraverso l’uso di diagrammi, documenti, e prototipi. Lo stadio Program (vedi la figura 18) si focalizza sullo sviluppo e la documentazione del codice sorgente del programma. Lo stadio Generalize è critico per i tentativi di riuso da parte dell’organizzazione poiché esso si focalizza sull’individuazione delle parti riutilizzabili, o che possono divenire riutilizzabili una volta modificate, da parte di un progetto software. Questo è effettivamente un ‘riuso opportunistico’ poiché si tenta di sviluppare parti riusabili raccogliendo il materiale dopo averlo prodotto, invece di un ‘riuso sistematico’ nel quale durante la modellizzazione si progetta software che sia riusabile. L’obiettivo dello stadio Test in the Small è di verificare e validare ciò che è stato prodotto dagli altri stadi della fase di Construct. In molti aspetti questo stadio è simile allo unit testing dal mondo strutturato combinato con le tecniche per assicurare la qualità come le ispezioni del codice e le revisioni tecniche. Figura 19 – Il pattern di processo Construct Solo perché la fase di Construct è per natura iterativa, ciò non significa che agli sviluppatori è consentito iniziare a fare come si vuole. La realtà dello sviluppo software è che si deve prima identificare e comprendere i requisiti, poi si deve modellarli, e infine codificarli. Se non sono stati definiti i requisiti, non ha senso codificare qualcosa. In verità gli sviluppatori migliori sanno che devono verificare il proprio operato prima di passare al task successivo. Non c’è alcuna utilità in requisiti di modellazione che non sono validi, o nella scrittura di codice sorgente basato su un modello sbagliato. Ciò significa che è necessario testare il proprio operato mentre si sviluppa, non alla fine quando è spesso troppo tardi per risolvere i problemi riscontrati. Ciò non significa che bisogna definire prima tutti i requisiti, poi effettuare tutta la modellazione, e infine scrivere tutto il codice; ma significa che qualunque codice si scriva dovrebbe essere basato su un modello convalidato, e ogni modello dovrebbe essere basato su uno o più requisiti convalidati. IL CONTESTO RISULTANTE La fase di Construct termina effettivamente quando un codice o un blocco sviluppato è stato dichiarato. Affinchè ciò sia ufficiale, è necessaria laddove applicabile la presenza delle seguenti componenti: i Modelli (Modello delle classi, Modello dei casi d’uso, diagrammi di sequenza, …), la Matrice di Allocazione dei Requisiti (RAM), il Codice Sorgente, il Piano di Test o di Stima dei Rischi, la Documentazione Utente, delle Operazioni, e di Supporto, il Software stesso, il Piano di Addestramento, e il Piano di Rilascio. A questo punto il software è pronto per passare alla fase di Deliver (vedi la figura 20) dove esso sarà testato nella sua interezza, rielaborato se necessario, e rilasciato alla comunità di utenti. Figura 20 – L’ Object‐Oriented Software Process (OOSP) 3.5.4
BENEFICI I pattern di processo sono un eccellente meccanismo per comunicare gli approcci allo sviluppo software che si sono rivelati essere efficaci nella pratica. Per di più, i pattern di processo sono blocchi riutilizzabili dai quali un’organizzazione può ottenere un processo software su misura. Per esempio, in figura 20 è possibile vedere una rappresentazione dell’ Object‐Oriented Software Process (OOSP), che è composto da quattro fasi in serie, ciascuna delle quali è composta da stadi iterativi [25]
. La freccia grande in basso al diagramma indica importanti task critici per il successo di un progetto che sono applicabili a tutti gli stadi dello sviluppo. I phase e i stage process pattern, come anche i task presenti nella freccia grande, sono a turno migliorati attraverso i task process pattern. I pattern di processo, nella forma dell’OOSP, sono stati usati per creare un processo software maturo per lo sviluppo su larga scala, un software mission‐critical usando la tecnologia ad oggetti. Come è possibile vedere in figura 20, ci sono quattro fasi di progetto dentro all’OOSP ‐ Initiate, Construct, Deliver, e Maintain and Support – ciascuna delle quali è descritta attraverso un phase process pattern. E’ possibile inoltre vedere che nell’OOSP ci sono 14 stadi di progetto ‐ Justify, Define and Validate Initial Requirements, Define Initial Management Documents, Define Infrastructure, Model, Program, Test In The Small, Generalize, Test In The Large, Rework, Release, Assess, Support, e Identify Defects and Enhancements – ciascuno dei quail è descritto da uno stage process pattern. Gli stadi del progetto vengono eseguiti in maniera iterativa dentro l’ambito di visibilità di una singola fase del progetto. Le fasi del progetto, dall’altro lato, sono eseguite in serie all’interno dell’OOSP. Come detto in precedenza, i pattern di processo sono l’elemento chiave per definire un processo software maturo per un’organizzazione. La realtà del miglioramento del processo, comunque, è che non è possibile effettuare tutti i cambiamenti che si desiderano immediatamente; è un cambiamento troppo grande per un’organizzazione assorbirli immediatamente. Questo accade perché ci sono tentativi come quelli del Capability Maturity Model (CMM) dell’ Software Engineering Institute’s (SEI) [26] e del Software Process Improvement Capability Determination Efforts (SPICE) dell’ International Standards Organization (ISO) [27]. Entrambe le organizzazioni suggeriscono di dare la priorità ai miglioramenti di processo di cui necessita l’organizzazione, pensare che ci vorranno parecchi anni per portare a termine i cambiamenti necessari, e pensare che si incontreranno numerose difficoltà nel fare ciò. L’esperienza mostra che le organizzazioni che provano a fare immediatamente cambiamenti di processo su larga scala facilmente falliscono. 4. IL PATTERN MVC 4.1 CARATTERISTICHE DEL PATTERN [28] Lo scopo di molte applicazioni è quello di recuperare dati all’interno di data store e visualizzarli in maniera opportuna a seconda delle esigenze degli utenti. Quando l’utente modifica i dati, l’applicazione riporta le modifiche sul data store. Poiché il flusso chiave di informazione avviene tra il dispositivo su cui sono memorizzati i dati e l’interfaccia utente, si è portati a legare insieme queste due parti per ridurre la quantità di codice e migliorare le performance dell’applicazione. Comunque, questo approccio apparentemente naturale presenta alcuni problemi significativi. Uno di questi problemi è che l’interfaccia utente tende a cambiare più in fretta rispetto al sistema di memorizzazione dei dati. Un altro problema che si ha nel mettere insieme i dati e l’interfaccia utente è che le applicazioni aziendali tendono ad incorporare logica di business che va al di là della semplice trasmissione di dati. Il problema è quello di rendere modulari le funzionalità dell’interfaccia utente di una Web application in maniera tale da poter facilmente modificare le singole parti. All’interno di tale contesto sul sistema agiscono le seguenti forze che devono essere riconciliate per trovare una soluzione al problema: ƒ La logica dell’interfaccia utente tende a cambiare con più frequenza rispetto alla logica di business, specialmente nelle applicazioni Web‐based. Ad ƒ
ƒ
ƒ
ƒ
ƒ
esempio, possono essere aggiunte nuove pagine dell’interfaccia utente, o può essere trasformato l’aspetto di pagine già esistenti. Dopo tutto, uno dei vantaggi di un’applicazione Web thin‐client è costituito dalla possibilità di cambiare l’interfaccia utente in qualunque momento senza dover ridistribuire l’applicazione. Se il codice dello strato di presentazione e la logica di business sono combinati in un singolo oggetto, è necessario modificare un oggetto contenente la logica di business ogni volta che viene cambiata l’interfaccia utente. Questo probabilmente introduce errori e richiede di ritestare tutta la logica di business dopo ogni minima modifica apportata all’interfaccia utente. In alcuni casi, l’applicazione visualizza i dati in differenti formati. Per esempio, un’ analista preferisce una vista dei dati su foglio elettronico mentre un manager preferisce visualizzare gli stessi dati su un grafico a torta. In alcune rich user interface sono mostrate più viste degli stessi dati contemporaneamente. Se l’utente cambia i dati in una vista, il sistema deve modificare automaticamente tutte le altre viste dei dati. La progettazione di pagine Html attraenti ed efficaci richiede generalmente un diverso insieme di abilità rispetto a quelle necessarie allo sviluppo di una logica di business complessa. Raramente una persona possiede entrambi questi insiemi di abilità. Perciò, è desiderabile separare lo sforzo di sviluppo di queste due parti. L’attività dell’interfaccia utente di solito consiste in due parti: presentazione e aggiornamento. La parte di presentazione recupera i dati da una sorgente di dati e sistema i dati per la visualizzazione. Quando l’utente effettua un’azione che coinvolge i dati, la parte di update passa il controllo alla logica di business per aggiornarli. Nelle Web application, la richiesta di una singola pagina combina la processazione dell’azione associata al link che l’utente ha selezionato con la visualizzazione della pagina di destinazione. In molti casi, tale pagina di output può non essere direttamente correlata all’azione effettuata. Per esempio, si immagini una semplice applicazione Web che mostra una lista di voci. L’utente ritorna alla pagina principale che contiene la lista o aggiungendo una voce alla lista o cancellando una voce da essa. Perciò, l’applicazione deve visualizzare la stessa pagina dopo l’esecuzione di due comandi molto diversi, il tutto deve avvenire all’interno della stessa richiesta Http. Il codice dell’interfaccia utente tende ad essere maggiormente dipendente dai dispositivi rispetto alla logica di business. Se si vuole migrare un’applicazione da una applicazione browser ad una applicazione per PDA o telefoni cellulari, è necessario sostituire la maggior parte del codice dell’interfaccia utente, mentre la logica di business può rimanere invariata. Una chiara separazione di queste due parti velocizza la migrazione e minimizza il rischio di introdurre errori nella logica di business. ƒ La creazione di test automatizzati per le interfacce utente è generalmente più difficile e costosa in termini di tempo della creazione di test per la logica di business. Perciò, la riduzione della quantità di codice che è legato direttamente all’interfaccia utente permette di testare l’applicazione con più facilità. La soluzione a tutto ciò è costituita dal pattern Model‐View‐Controller (MVC) che separa la modellizzazione del dominio, la presentazione, e le azioni basate sugli input degli utenti all’interno di tre classi separate: ƒ Model. Il model gestisce il comportamento e i dati del dominio dell’applicazione, risponde alle richieste di informazione riguardante il suo stato, e risponde alle istruzioni per il cambiamento dello stato. ƒ View. La view gestisce la visualizzazione dell’informazione. ƒ Controller. Il controller interpreta gli input del mouse e della tastiera, informando il model e/o la view di cambiare in maniera appropriata. La figura 21 mostra la relazione strutturale tra questi tre oggetti. Figura 21 – Struttura della classe MVC E’ importante notare che sia la view che il controller dipendono dal model. Comunque il model non dipende né dalla view né dal controller. Questo è uno dei benefici chiave di questa separazione, che consente al model di essere costruito e testato indipendentemente dalla presentazione grafica. In molte applicazioni desktop la separazione tra la view e il controller è secondaria e, infatti, molti framework dell’interfaccia utente implementano i ruoli come un oggetto. Nelle applicazioni Web, dall’altro lato, la separazione tra la view (il browser) e il controller (i componenti lato server che gestiscono le richieste Http) è davvero ben definita. Il Model‐View‐Controller è un design pattern per la separazione della logica dell’interfaccia utente dalla logica di business. Sfortunatamente, la popolarità dei pattern ha prodotto una serie di descrizioni sbagliate. In particolar modo , il termine controller è stato usato per indicare cose differenti in contesti diversi. Per fortuna, la diffusione delle applicazioni Web ha aiutato a risolvere alcune delle ambiguità poiché in tal caso la separazione tra la view e il controller è netta. 4.1.1
LE VARIANTI [29] Nella sua pubblicazione Application Programming in Smalltalk‐80: How to use Model‐View‐Controller (MVC), Steve Burbeck propone due varianti del pattern MVC: un model passivo ed un model attivo. • Il model passivo è utilizzato quando un controller manipola esclusivamente il model. Il controller modifica il model e poi informa la view che il model è cambiato, in maniera tale che la view può essere aggiornata (vedi la figura 22). Il model in tale scenario è completamente indipendente dalla view e dal controller, ciò significa che il model non ha motivo di riportare i cambiamenti che si sono verificati nel suo stato. Il protocollo Http è un esempio significativo di ciò. Non esiste nel browser un modo semplice per ricevere aggiornamenti asincroni dal server. Il browser visualizza la view e risponde agli input dell’utente, ma esso non individua i cambiamenti nei dati sul server. Solo quando l’utente richiede espressamente un refresh il server viene interrogato per ottenere i cambiamenti. Figura 22 – Il comportamento del model passivo • Il model attivo è, invece, usato quando il model cambia stato senza coinvolgere il controller. Questo può succedere quando altre sorgenti stanno cambiando i dati e i cambiamenti devono essere riportati sulle viste. Si consideri una visualizzazione di un insieme di dati. Si riceve un insieme di dati da una sorgente esterna e si vogliono aggiornare le viste (ad esempio una finestra di alert) quando i dati cambiano. Poiché solo il model individua i cambiamenti al suo stato interno quando essi si verificano, esso deve notificare alle viste di aggiornare la visualizzazione. Comunque, uno dei motivi per cui si usa il pattern MVC è quello di rendere il model indipendente dalle viste. Se il model deve notificare alle viste i cambiamenti, potrebbe essere reintrodotta la dipendenza che si stava cercando di evitare. Per fortuna, il pattern Observer prevede un meccanismo per avvertire gli altri oggetti dei cambiamenti di stato senza introdurre su essi delle dipendenze. Le singole viste implementano l’interfaccia Observer e si registrano al model. Il model traccia la lista di tutti gli osservatori che sottoscrivono i cambiamenti. Quando un model cambia, esso scorre la lista e notifica i cambiamenti a tutti coloro che sono registrati. Questo approccio è spesso chiamato ‘sottoscrizione pubblica’. Il model non richiede mai informazioni specifiche sulle viste. Infatti, in uno scenario in cui il controller necessita di essere informato dei cambiamenti avvenuti sul model (per esempio, l’attivazione o disattivazione di opzioni di menù), tutto ciò che il controller deve fare è implementare l’interfaccia Observer e sottoscrivere i cambiamenti al model. In una situazione in cui ci sono molte viste, ha senso definire più soggetti, ciascuno dei quali descrive uno specifico tipo di cambiamenti al model. Ciascuna vista può poi sottoscrivere soltanto i cambiamenti che sono rilevanti per essa. La figura 23 mostra la struttura di un MVC attivo che usa l’Observer e, come quest’ultimo, isola il model dal referenziare direttamente le viste. Figura 23 – Uso dell’Observer per separare il model dalla vista nel model attivo La figura 24 mostra, invece, come l’Observer informa le viste quando il model cambia. Sfortunatamente, non c’è un buon modo per dimostrare la separazione del model e della view attraverso un diagramma delle sequenze rappresentato in Unified Modeling Language (UML), poiché il diagramma rappresenta le istanze degli oggetti piuttosto che le classi e le interfacce. Figura 24 – Comportamento del model attivo IL TESTING La capacità di effettuare dei test migliora notevolmente quando viene impiegato il pattern Model‐View‐Controller. Testare i componenti diviene difficile quando essi sono altamente interdipendenti, specialmente quando si tratta di componenti dell’interfaccia utente. Questi tipi di componenti molto spesso richiedono un complesso settaggio anche solo per testare una semplice funzione. Ancor peggio, quando si verifica un errore, è difficile isolare il problema ad uno specifico componente. Questo è il motivo per cui la separazione dei concetti è un’importante guida architetturale. L’ MVC separa i concetti di memorizzazione, visualizzazione, ed aggiornamento dei dati in tre componenti che possono essere testati individualmente. A prescindere dai problemi derivanti dalle interdipendenze, i framework delle interfacce utente sono inerentemente difficili da testare. Il test delle interfacce utente richiede o fastidiosi test manuali oppure script di testing che simulano le azioni degli utenti. Questi test tendono ad essere molto dispendiosi in termini di tempo e fragili per lo sviluppo. L’ MVC non elimina la necessità di effettuare dei test sulle interfacce utente, ma separa il model dalla logica di presentazione permettendo al model di essere testato in modo indipendente dalla presentazione e riduce il numero di casi di test delle interfacce utente. 4.1.2
BENEFICI E SVANTAGGI [30] Solo suddividendo l’applicazione in tre componenti MVC, è possibile ottenere numerosi vantaggi. Quelli elencati sono i vantaggi più significativi individuati: • Chiarezza di progettazione: i metodi pubblici nel model si presentano come un’ API per tutti i comandi disponibili a manipolare i suoi dati e il suo stato. Dando un’occhiata alla lista dei metodi pubblici del model, dovrebbe essere •
•
•
•
•
facilmente comprensibile come controllare il comportamento del model. Quando si progetta l’applicazione, questa caratteristica rende l’intero programma più semplice da implementare e manutenere. Efficiente modularità: tale caratteristica di progettazione permette a qualunque componente di essere aggiunto e sostituito ogni volta che l’utente o il programmatore lo desidera. I cambiamenti ad un aspetto del programma sono legati ad altri aspetti, eliminando numerose difficoltose situazioni di debug. Inoltre, lo sviluppo dei vari componenti può avvenire in parallelo, poiché l’interfaccia tra i componenti è chiaramente definita. Viste multiple: l’applicazione può visualizzare lo stato del model in modi differenti e progettare tali modalità in maniera scalare e modulare. Questo avviene nei giochi, con una cabina e una vista radar, e in molte altre applicazioni, dove c’è magari una vista che visualizza lo stato del model e un’altra vista che colleziona i dati, effettua dei calcoli, e poi salva tali dati su disco. Entrambe le viste usano gli stessi dati, esse usano solamente l’informazione in maniera differente. Durante il processo di sviluppo, si può iniziare con una vista basata su testo, che stampa solo i dati che il modello sta generando. Successivamente, quando si creano nuove viste, si può usare la vista basata su testo per verificare le performance delle nuove viste. Capacità di crescita: i controller e le view possono crescere come accade con il model; vecchie versioni delle view e dei controller possono ancora essere usate purchè venga mantenuta una interfaccia comune (la vista testuale menzionata in precedenza). Per esempio, se un’applicazione necessità di due tipi di utenti, generico ed amministratore, essi possono usare lo stesso model, ma hanno solo differenti implementazioni del controller e della view. Questo è collegato alla somiglianza con l’architettura client/server dove le nuove viste e i server sono analoghi ai client. Capacità di distribuzione: con una coppia di proxy si può facilmente distribuire un’applicazione MVC alterando solamente il metodo di avvio dell’applicazione. Ora, l’applicazione diventa un insieme pienamente sviluppato di applicazioni client e server. Potenti interfacce utente: usando le API del model, l’interfaccia utente quando presenta i comandi all’utente può combinare le chiamate ai metodi. Le Macro possono essere viste come una serie di comandi standard inviati al model, tutti scatenati da una singola azione dell’utente. Questo consente al programma di presentare all’utente un’interfaccia più intuitiva. Non sono molte le problematiche derivanti dall’utilizzo del pattern MVC, le poche esistenti sono di seguito riportate: • Complessità: il pattern MVC introduce nuovi livelli di indirezione e perciò aumenta leggermente la complessità della soluzione. Ciò incrementa anche la natura event‐driven del codice dell’interfaccia utente, che può portare ad una maggior difficoltà nell’eseguire il debug. • Costo di aggiornamenti frequenti: la separazione del model dalla view non significa che coloro che si occupano dello sviluppo del model possono ignorare la natura delle viste. Per esempio, se il modello è sottoposto a frequenti cambiamenti, esso può inondare le viste di richieste di aggiornamento. Alcune viste possono impiegare del tempo per visualizzare i loro dati. Di conseguenza, la view può non riuscire a star dietro alle richieste di aggiornamento. Perciò, è necessario tenere a mente la vista quando si codifica il model. Per esempio, il model può gestire aggiornamenti multipli all’interno di una singola notifica alla vista. 4.2 MVC E LE WEB APPLICATION Si vuole ora approfondire il concetto di pattern MVC nell’ambito delle applicazioni Web. [31] Come già detto i pattern descrivono un problema, una soluzione e le possibili conseguenze della soluzione adottata; sono strettamente correlati alla programmazione Object‐Oriented perché nelle soluzioni proposte vengono presentate delle classi o interfacce con precise responsabilità. Un obiettivo spesso perseguito è proprio l’indipendenza della soluzione rispetto alla sua implementazione in modo tale da permettere un effettivo riutilizzo del codice. La struttura di una web application può basarsi sul modello MVC. Si può definire l’MVC come una vera e propria ‘composizione di pattern’; in particolare è un’applicazione del pattern Observer alle GUI (Graphic User Interface), cioè alle interfacce dell’utente. L'applicazione deve fornire un’interfaccia grafica, costituita da più schermate che mostrino vari dati all'utente e deve avere una natura modulare basata sulle responsabilità, al fine di ottenere una vera e propria applicazione basata sui componenti. Appare quindi evidente il bisogno di un'architettura che permetta la separazione netta tra i componenti software, che gestiscono il modo di presentare i dati, e i componenti che gestiscono i dati stessi. Le peculiarità di questa applicazione sono: ƒ accedere alla gestione dei dati con diverse tipologie di GUI (magari sviluppate con tecnologie diverse); ƒ aggiornare i dati dell'applicazione tramite diverse interazioni da parte dei client; ƒ supportare varie GUI ed interazioni non influendo sulle funzionalità di base dell'applicazione. Figura 25 – Struttura del pattern MVC L'applicazione deve separare i componenti software che implementano il modello delle funzionalità di business, dai componenti che implementano la logica di presentazione e di controllo che utilizzano tali funzionalità. Vengono quindi definiti tre tipologie di componenti che soddisfano tali requisiti (vedi fig.25) : • Model: analizzando la figura 25, si evince che il core dell'applicazione viene implementato dal Model, che incapsulando lo stato dell'applicazione, definisce i dati e le operazioni che possono essere eseguite su questi. Quindi definisce le regole di business per l'interazione con i dati, esponendo alla View ed al Controller rispettivamente le funzionalità per l'accesso e l'aggiornamento. Per lo sviluppo del Model quindi è vivamente consigliato utilizzare le tipiche tecniche di progettazione Object Oriented per ottenere un componente software che astragga al meglio concetti importati dal mondo reale. Il Model può inoltre avere la responsabilità di notificare ai componenti della View eventuali aggiornamenti verificatisi in seguito a richieste del Controller, al fine di permettere alle View di presentare agli occhi degli utenti dati sempre aggiornati. • View: la logica di presentazione dei dati viene gestita solo e solamente dalla View. Ciò implica che questa deve fondamentalmente gestire la costruzione dell'interfaccia grafica (GUI) che rappresenta il mezzo mediante il quale gli utenti interagiranno con il sistema. Ogni GUI può essere costituita da schermate diverse che presentano più modi di interagire con i dati dell'applicazione. Per far sì che i dati presentati siano sempre aggiornati è possibile adottare due strategie note come model attivo e model passivo. Il model attivo come già detto adotta il pattern Observer, registrando le View come osservatori del Model. Le View possono quindi richiedere gli aggiornamenti al Model in tempo reale grazie alla notifica di quest'ultimo. Benché questa rappresenti la strategia ideale, non è sempre applicabile . Inoltre la View delega al Controller l'esecuzione dei processi richiesti dall'utente dopo averne catturato gli input e la scelta delle eventuali schermate da presentare. • Controller: questo componente ha la responsabilità di trasformare le interazioni dell'utente della View in azioni eseguite dal Model. Ma il Controller non rappresenta un semplice ponte tra View e Model. Realizzando la mappatura tra input dell'utente e processi eseguiti dal Model e selezionando le schermate della View richieste, il Controller implementa la logica di controllo dell'applicazione. MVC nasce nel contesto della programmazione a oggetti tradizionale e viene adattato alle architetture web tenendo presente la mancanza in Http di meccanismi per mantenere lo stato dell’interazione e per notificare al client cambiamenti insorti lato server. Il risultato di tale adattamento prende il nome di architettura MVC 2 ed è rappresentato nella figura 26. Figura 26 – Architettura MVC 2 Nell’architettura MVC 2 il generatore delle richieste è il Browser. Quando l’utente attiva un collegamento ipertestuale nella pagina Html o usa una form, la richiesta Http viene indirizzata ad un’unica servlet che svolge le funzioni di Controller. Il Controller decide la sequenza di azioni necessarie per soddisfare la richiesta; le azioni ammissibili sono rappresentate sotto forma di componenti Object Oriented, chiamati action class. Specificamente, il Controller associa la richiesta Http alla corrispondente azione creando o riutilizzando un oggetto della action class e chiamando una sua funzione predefinita. Ogni action class racchiude una particolare funzione applicativa, che interroga e/o modifica lo stato dell’applicazione. Nella situazione più semplice, essa contiene tutta la logica necessaria a servire la richiesta Http; in scenari più complessi la classe action svolge puramente un ruolo di intermediazione nei confronti di altri oggetti, appartenenti al Model, che contengono la logica di business vera e propria. La action rappresenta il cuore di tutta l'applicazione in quanto racchiudere al suo interno tutta la logica di esecuzione. In genere, se il suo numero di righe di codice cresce, si può decidere di suddividerla in sottoclassi. L’esecuzione di una action può effettuare operazioni di modifica al database. Il meccanismo di esecuzione della logica contenuta in una action è molto semplice: il ServletRouter, dopo avere ricavato il nome della classe da utilizzare, ne invoca il metodo perform(). Utilizzando una terminologia differente, si può dire che la Servlet invia un messaggio alla action. Esempi di azioni eseguite o mediate da una action class possono essere l’esecuzione di un’interrogazione della base di dati, l’invio di messaggi di posta elettronica, oppure l’autenticazione dell’utente. La action agisce come un ponte tra le azioni dell’utente lato client e le operazioni business, svolgendo i seguenti passi: ƒ creazione di un unico oggetto per ogni classe Action dell’applicazione; ƒ definizione di un thread che invoca il metodo execute per ogni transazione utente; ƒ creazione di un meccanismo cache che associa il nome simbolico della action al nome della classe che la realizza. Al termine dell’esecuzione, l’azione comunica l’esito al Controller, che decide il passo successivo. Tipicamente il Controller invoca un template Jsp facente parte della View. Tale template accede agli oggetti del Model che contengono lo stato corrente dell’applicazione e costruisce una pagina (per esempio Html) che visualizza tale stato all’utente. La figura 27 riassume quanto detto, mostrando la sequenza delle interazioni tra i componenti dell’architettura MVC 2. Figura 27 – Flusso di controllo dell’architettura MVC 2 Il codice della action ha un andamento tipico: inizialmente si accede alla request o al form bean allo scopo di estrarre l’input dell’utente necessario all’evasione della richiesta. Successivamente si crea, o si riusa, l’oggetto di business più appropriato per il calcolo della risposta e si invocano i metodi necessari. I metodi dell’oggetto di business creano opportuni oggetti di stato (per esempio, JavaBean) che rappresentano la risposta alla richiesta; tali oggetti devono essere salvati nella request in modo da essere successivamente reperibili dai template della View. Infine, la action class effettua le verifiche appropriate sull’esito dei metodi di business e restituisce al chiamante (cioè al Controller) un oggetto che rappresenta il nome simbolico della risorsa a cui cedere il controllo. Questo modo di strutturare l’applicazione consente una netta separazione dei compiti tra i vari elementi dell’architettura: nulla nel codice che li realizza dipende dall’architettura web; ne consegue un facile riutilizzo in contesti differenti. I template della vista operano su strutture dati standard, come Java Bean, e ignorano il modo con cui tali oggetti sono costruiti. 5. MVC PER APPLICAZIONI SILVERLIGHT 5.1 LE MOTIVAZIONI In applicazioni piccole su cui lavora un numero limitato di persone probabilmente non si sente l’esigenza di usare un pattern, ma in applicazioni più grandi alle quali lavorano magari centinaia di persone l’uso di pattern è di fondamentale importanza. Questo è dovuto al fatto che grazie al loro uso è possibile orientarsi con più facilità all’ interno del codice scritto da altre persone (e molte volte anche all’interno del proprio codice), riusare delle parti di codice già codificate, ed introdurre senza particolari difficoltà nuovo personale all’interno dei team di sviluppo che lavorano alle applicazioni. Per tali motivi è sorta la necessità di riscrivere l’applicativo Silverlight già esistente e codificare la nuova parte utilizzando un pattern che possa essere facilmente comprensibile ed utilizzabile da chiunque. Si è cercato invano di trovare delle direttive di implementazione per la realizzazione di applicazioni con la piattaforma Silverlight, ma il tentativo di fare ciò è fallito poiché Silverlight è un plugin abbastanza recente. Per ovviare a questa mancanza si è deciso di ricercare un pattern tra quelli esistenti che potesse essere adeguato per lo sviluppo si applicazioni con la piattaforma Silverlight. Dopo aver visionato i vari pattern della Gang of Four, la scelta è ricaduta sul noto design pattern Model‐View‐Controller, descritto nel paragrafo successivo, per la sua capacità di organizzare e partizionare codice, package e componenti, e per la sua propensione a suddividere il codice per ruoli e responsabilità. Del pattern in questione è stata così sviluppata una particolare implementazione per Silverlight. 5.2 IMPLEMENTAZIONE DEL PATTERN Partendo dal MVC è stata sviluppata una sua particolare implementazione utilizzata per la codifica dell’applicativo, prendendo come esempio una implementazione esistente ampiamente utilizzata a partire dal 2006 da applicazioni RIA sviluppate da Adobe. Tale implementazione possiede concetti di facile comprensione ed è stato provato che lavora bene per lo sviluppo di applicazioni aziendali su larga scala. Essa ha lo scopo di organizzare e partizionare il codice, i package, e i componenti spingendo in tal modo i programmatori a suddividere il codice in base ai ruoli e alle responsabilità. L’implementazione creata conserva i concetti principali del pattern MVC, ovvero: ƒ Model: contiene al proprio interno gli oggetti di dati e lo stato di questi ultimi; ƒ Controller: processa la logica di business; ƒ Views: sono interfacce utente che visualizzano i dati e annunciano le azioni compiute dagli utenti attraverso la generazione di eventi; esse comunicano con il Controller attraverso gli eventi. Le viste, che sono composte solitamente da controlli dell’UI o da altre viste figlie, vedono gli oggetti presenti nel Model attraverso l’uso del data binding. Figura 27 – Mapping delle funzionalità con i layer del Pattern MVC Tale implementazione è ideale per i team di sviluppo e tra i suoi benefici prevede: ƒ Facilità di manutenzione e di debugging; ƒ Semplicità nell’aggiungere nuove caratteristiche e nell’effettuare delle modifiche; ƒ Possibilità di effettuare senza particolari problemi dei test riguardanti la logica di business e l’accesso ai dati. ƒ Sviluppo di applicazioni in cui le viste possono essere cambiate indipendentemente dalla logica di business e dall’accesso ai dati Essa possiede cinque componenti principali utilizzabili nelle applicazioni: ƒ ModelLocator: un repository per dati globali accedibile globalmente; ƒ Commands: componenti non appartenenti all’interfaccia utente che processano la logica di business; ƒ Events: sono eventi custom che inducono gli oggetti di business (i Commands) ad iniziare la processazione; ƒ Controller: componente necessario per indirizzare gli eventi di business verso i comandi per effettuare una processazione. ƒ Delegates: componenti utilizzati dai Commands per richiamare componenti come l’HttpWebRequest, il WebClient, e oggetti remoti utilizzati di frequente dalle applicazioni RIA per fornire e/o richiedere informazioni a Web server o servizi remoti. Quando si vuole mappare sul pattern MVC un codice già sviluppato, la prima cosa da fare è identificare e separare all’interno del codice le parti riguardanti i dati, quelle riguardanti l’interfaccia utente, e quelle che processano la logica di business o richiamano servizi. Grazie alla presenza del CLR (Common Language Runtime), in Silverlight, che utilizza per i suoi file un linguaggio chiamato XAML, la separazione tra il codice dell’interfaccia utente e il codice che rappresenta la logica applicativa è già effettuata a priori. E’ dunque necessario solo separare le parti di codice riguardanti i dati da quelle che elaborano la logica di business. Per mostrare un esempio di suddivisione del codice sulla base delle responsabilità ritengo per tal motivo opportuno mostrare una parte di codice derivante da un’applicazione Flash, che presenta all’interno di un unico file sia il codice dell’UI sia il codice contenente la logica applicativa. Figura 28 – Identificazione dei ruoli all’interno del codice Dopo aver individuato le responsabilità all’interno del codice, le parti appartenenti all’interfaccia utente verranno mappate all’interno del layer View del pattern, gli oggetti di dati saranno inseriti all’interno del componente ModelLocator all’interno del layer Model, mentre il codice rimanente va mappato all’interno del layer Controller spostando la logica di business all’interno di componenti Commands. E’ poi necessario creare eventi custom per mettere in comunicazione il layer View con il layer Controller e creare un componente FrontController in grado di associare ciascun evento di business al rispettivo command. Infine vanno modificati i componenti delle View in maniera tale che facciano binding sui dati contenuti all’interno del ModelLocator; questa operazione fa sì che ogni volta che viene fatta una modifica ai dati del model, i cambiamenti siano immediatamente visibili sui componenti UI del layer View. Figura 29 – Mapping del codice nei layer e componenti del Pattern MVC In Silverlight sono presenti tre modalità di databinding: ƒ OneTime: questa modalità è utilizzata per visualizzare dati che non cambieranno nel tempo. In pratica, i componenti UI legati in binding ad alcuni dati nel Model vedranno sempre gli stessi dati; ulteriori modifiche a questi ultimi non saranno visibili sulle View. ƒ OneWay: con tale modalità di binding, ogni volta che avviene un cambiamento ai dati contenuti nel Model i componenti dell’UI visualizzeranno immediatamente tale cambiamento. Non c’è la possibilità per l’utente di modificare i dati nel Model agendo sui componenti UI. ƒ TwoWay: questa modalità è simile alla OneWay, ma a differenza di questa l’utente può modificare i dati nel Model interagendo con i componenti dell’interfaccia che presentano il databinding. I benefici più evidenti derivanti da una rimodellazione del codice che usa l’implementazione del pattern MVC per applicazioni Silverlight sono i seguenti: ƒ Il codice delle View è comprensibile e facilmente identificabile; ƒ Le classi appartenenti al layer View non sanno nulla dei componenti appartenenti al layer Controller, se non attraverso l’uso degli eventi di business; ƒ I dati appartenenti al layer Model sono memorizzati e acceduti attraverso la classe ModelLocator; ƒ Tutte la variabili presenti nel ModelLocator supportano il databinding; ƒ I componenti di business non sono a conoscenza delle classi del layer View, i cui componenti utilizzano il databinding per visualizzare i dati memorizzati all’interno del componente ModelLocator. 5.3 I COMPONENTI I componenti appartenenti all’implementazione sono stati codificati come classi utilizzando il linguaggio C# e sono stati inseriti all’interno di una libreria di classi Silverlight, che verrà poi importata all’interno dall’applicativo che implementa le form dinamiche per poterlo così mappare sul pattern MVC. Va ricordato comunque che tale libreria non è finalizzata esclusivamente alla realizzazione del progetto in questione, ma potrà essere usata nello sviluppo di una qualunque applicazione realizzata usando la piattaforma Silverlight. 5.3.1
IL MODEL LOCATOR Il ModelLocator è un repository globale di tipo singleton per dati condivisi e contiene lo stato di tali dati. Il riferimento al pattern Singleton significa che può esistere all’interno dell’applicazione una sola istanza per volta della classe. Il ModelLocator non contiene al proprio interno la logica di business, ma serve esclusivamente come cache per la memorizzazione di dati che supportano il databinding per far sì che ci siano auto‐notifiche alle View ogni qual volta si verificano cambiamenti a tali dati. Ad essere precisi, non è il ModelLocator che contiene i dati ma la classe che erediterà da questo. L’implementazione dell’interfaccia INotifyPropertyChanged nella classe astratta ModelLocator assicura che la classe DFModel che la deriverà sia ‘bindable’ alla View (Xaml). Poiché implementa tale interfaccia, il ModelLocator deve implementare il metodo NotifyPropertyChanged, essenziale per il funzionamento del databinding, il quale notifica agli elementi che fanno binding sul Model che si è verificato un cambiamento di valore di una proprietà in esso presente. public abstract class ModelLocator : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
} La classe DFModel, che contiene i dati veri e propri, eredita dunque dalla classe astratta ModelLocator ed è implementata come una classe Singleton. Ogni volta che si modifica un oggetto all’interno della classe, viene scatenato l’evento NotifyPropertyChanged("PropertyName") che, specificando il nome della proprietà interessata, notifica che si è verificato un cambiamento nel valore. public class DFModel : ModelLocator
{
private static DFModel instance;
/// Tale metodo fa sì che ci sia una sola istanza alla volta
/// del ModelLocator
public static DFModel getInstance()
{
if (instance == null)
instance = new DFModel();
return instance;
}
/// Private constructor for singleton object
private DFModel()
{
if ( instance != null )
{
throw new PatternError("SINGLETON_EXCEPTION", "App model
(DFModel) should be a singelton object");
}
}
private ConfigurazioneASCX _configASCX = new ConfigurazioneASCX();
public ConfigurazioneASCX ConfigASCX
{
get { return _configASCX; }
set { _configASCX = value; NotifyPropertyChanged("ConfigASCX"); }
}
}
Nell’esempio è possibile notare che la classe DFModel, che eredita della classe ModelLocator, possiede una variabile statica istance di tipo DFModel ed un metodo getInstance() che se non esiste ancora un’istanza della classe la crea, altrimenti restituisce l’istanza già esistente. E’ da notare anche che, quando viene eseguito l’accessor set (consente la modifica del valore di una proprietà) relativo alla proprietà ConfigASCX, viene chiamato il metodo NotifyPropertyChanged(“ConfigASCX”) che notifica alle View che è cambiato il valore della proprietà. 5.3.2
GLI EVENTI DI BUSINESS Tra le varie categorie di eventi esistenti (eventi di sistema, eventi framework, eventi utente, …), vengono presi in considerazione esclusivamente gli eventi di business, i quali sono registrati nel componente FrontController appartenente al Business (o Controller) layer e sono utilizzati per notificare azioni compiute dagli utenti ed attivare la processazione da parte dei Command. Ciascun evento di business possiede un identificatore univoco che lo contraddistingue da tutti gli altri. Le viste appartenenti al layer View possono creare ed inviare eventi. L’unico che può gestire e processare gli eventi è il Control layer. Di seguito è riportato un esempio di classe Event chiamata GenericEvent, dalla quale erediteranno tutti gli eventi di business creati nell’ambito dell’applicazione: public class GenericEvent
{
public string Name { get; set; }
public List<object> Params { get; set; }
public GenericEvent(String name)
{
this.Name = name;
}
public GenericEvent(String name, List<object> param)
{
this.Name = name;
this.Params = param;
}
/// Helper funtion to raise event without requiring client code to call
GenericEventDispatcher.getInstance()
public void dispatch()
{
GenericEventDispatcher.getInstance().dispatchEvent(this);
}
} La classe presa in considerazione possiede un metodo dispatch() che è invocato quando l’evento viene scatenato; tale metodo a sua volta richiama il metodo dispatchEvent() della classe internal (fa sì che la classe sia utilizzabile solo nell’assembly in cui è definita) GenericEventDispatcher, che semplifica il codice dell’applicazione necessario per lanciare un evento di business. Anche la classe GenericEventDispatcher implementa il pattern Singleton, che permette che ci sia nell’applicazione una sola istanza alla volta della classe. internal class GenericEventDispatcher
{
private static GenericEventDispatcher instance;
public static GenericEventDispatcher getInstance()
{
if (instance == null)
instance = new GenericEventDispatcher();
return instance;
}
private GenericEventDispatcher()
{
}
public
delegate
GenericEventArgs args);
void
EventDispatchDelegate(object
sender,
/// The single event raised whenever a system event occurs
public event EventDispatchDelegate EventDispatched;
public void dispatchEvent(GenericEvent genericEvent)
{
if (EventDispatched != null)
{
GenericEventArgs args = new GenericEventArgs(genericEvent);
EventDispatched(null, args);
}
}
} Quando viene invocato il metodo dispatchEvent(), al suo interno viene lanciato l’evento EventDispatched() di tipo EventDispatchDelegate che verrà catturato e gestito dalla classe FrontController, di cui si parlerà in seguito. Qui sotto vengono mostrate la creazione e l’invio di un evento di business in seguito alla richiesta di salvataggio di una configurazione da parte dell’utente. void imgSave_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
SavePPConfigEvent scEvent = new SavePPConfigEvent("SavePPConfig");
scEvent.dispatch();
} Quando l’utente richiede il salvataggio, viene creato un evento della classe SavePPConfigEvent con un ID univoco che è ‘SavePPConfig’ e successivamente viene inviato attraverso il metodo dispatch(). La classe in questione estende la classe generica GenericEvent, ereditandone così proprietà e metodi. public class SavePPConfigEvent : GenericEvent
{
public SavePPConfigEvent(String name)
:base(name)
{
}
} 5.3.3
I COMMANDS Il Controller layer punta sui Commands per gestire la logica di business e rispondere agli eventi di business. Ciascuna classe Command rappresenta una specifica caratteristica di business a cui è associata la logica di business e la capacità di processazione. I comandi modificano il ModelLocator aggiungendo ad esso nuovi dati o modificando i dati esistenti. Le implementazioni dei Command hanno nomi di classe che sono equivalenti a quelli degli eventi di business, ad esempio SavePPConfigEvent e SavePPConfigCommand. Tutte le classi Command create devono implementare le interfacce IResponder e ICommand. L’interfaccia ICommand dichiara il metodo execute() che sarà implementato dalla classe Command e che rappresenta il punto di partenza nella processazione della logica di business. public interface ICommand
{
void execute(GenericEvent genericEvent);
}
L’interfaccia IResponder è implementata da tutte quelle classi (in tal caso la classe Command) che vogliono gestire i dati ritornanti da una richiesta di servizio ad un server. Tali classi dovranno dunque implementare i suoi metodi onResult() e onFault(). public interface IResponder
{
void onResult(object result);
void onFault(string errorMessage);
} Vediamo un esempio di implementazione di una classe Command, in particolare analizziamo la classe ASCXLoadCommand che ha il compito di caricare un insieme di configurazioni ASCX in seguito ad un’azione compiuta dall’utente su un componente UI di una View. public class ASCXLoadCommand : ICommand, IResponder
{
public XDocument xmlDefGerarchie;
private DFModel model = DFModel.getInstance();
TextBlock txtNome, txtTipo, txtDefault;
TextBox txtValore;
CheckBox cbxEreditato;
Button btnAggiungiParam, btnEliminaParam, btnSalvaParam;
#region ICommand Members
public void execute(GenericEvent genericEvent)
{
if (genericEvent.Params[0] is TextBlock)
txtNome = (genericEvent.Params[0] as TextBlock);
if (genericEvent.Params[1] is TextBlock)
txtTipo = (genericEvent.Params[1] as TextBlock);
if (genericEvent.Params[2] is TextBlock)
txtDefault = (genericEvent.Params[2] as TextBlock);
if (genericEvent.Params[3] is TextBox)
txtValore = (genericEvent.Params[3] as TextBox);
if (genericEvent.Params[4] is CheckBox)
cbxEreditato = (genericEvent.Params[4] as CheckBox);
if (genericEvent.Params[5] is Button)
btnAggiungiParam = (genericEvent.Params[5] as Button);
if (genericEvent.Params[6] is Button)
btnEliminaParam = (genericEvent.Params[6] as Button);
if (genericEvent.Params[7] is Button)
btnSalvaParam = (genericEvent.Params[7] as Button);
//begin talk to web service
ASCXLoadDelegate ascxDelegate = new ASCXLoadDelegate(this);
ascxDelegate.SendRequest();
PulisciParametriEDettaglio();
Manager.WaitingFormOn();
}
#endregion
#region IResponder Members
public void onResult(object result)
{
DisplayASCX((string)result);
Manager.WaitingFormOff();
}
public void onFault(string errorMessage)
{
}
#endregion
private void PulisciParametriEDettaglio()
{
PulisciDettaglio();
PulisciParPers();
model.ParametriDefaultList= null;
model.ASCXPersParList= null;
model.ASCXPersList = null;
}
private void PulisciDettaglio()
{
this.txtNome.Text = "";
this.txtTipo.Text = "";
this.txtDefault.Text = "";
}
private void PulisciParPers()
{
this.txtValore.Text = "";
this.txtValore.IsReadOnly = false;
this.cbxEreditato.IsChecked = false;
this.btnAggiungiParam.IsEnabled = false;
this.btnEliminaParam.IsEnabled = false;
this.btnSalvaParam.IsEnabled = false;
}
private void DisplayASCX(string xmlContent)
{
xmlContent
=
xmlContent.Replace("xmlns=\"http://tempuri.org/StrutturaGerarchieFinale.xsd\"",
"");
xmlDefGerarchie = XDocument.Parse(xmlContent);
ReLoadASCXList();
}
private void ReLoadASCXList()
{
//svuoto e riassegno la source alla lista ASCX
model.ASCXList = null;
model.ASCXSelectedIndex = -1;
model.ASCXList = getListaASCX();
}
private List<ASCXDetail> getListaASCX()
{
ASCXDetail gerarchia;
Parametro parametro;
List<ASCXDetail> lista = new List<ASCXDetail>();
var
itemsGerarchie
=
from
ASCX
in
xmlDefGerarchie.Root.Element("RecordsetGerarchie").Elements("Gerarchia")
select ASCX;
//ricostruzione della classe defGerarchie
foreach (XElement ger in itemsGerarchie)
{
gerarchia = new ASCXDetail();
gerarchia.ModID = (string)ger.Attribute("ModID").Value;
gerarchia.ClassID = (string)ger.Attribute("ClassID").Value;
gerarchia.GerID = (string)ger.Attribute("GerID").Value;
gerarchia.GerCod = (string)ger.Attribute("GerCod").Value;
gerarchia.GerDes = (string)ger.Attribute("GerDes").Value;
//creazione dell'insieme parametri
foreach
(XElement
xPar
in
ger.Element("InsiemeParametri").Elements("Pa"))
{
parametro = new Parametro();
parametro.ParCod = (string)xPar.Attribute("ParCod").Value;
parametro.Vers = (string)xPar.Attribute("Vers").Value;
parametro.Default = (string)xPar.Attribute("Default").Value;
parametro.Ereditato = false;
gerarchia.Parametri.Add(parametro);
}
lista.Add(gerarchia);
}
return lista;
}
} Come è possibile notare, nel codice è evidenziata in giallo l’istanza della classe DFModel che consentirà al codice esecutivo della classe ASCXLoadCommand di modificare i dati contenuti nel Model. E’ evidenziata, invece, con un colore celeste l’assegnazione alla proprietà ASCXList presente nel Model di una lista ritornante da una chiamata al metodo getListaASCX(). Sono inoltre facilmente individuabili i metodi execute() che avvia la processazione della logica, e i metodi onResult() e onFault() che consentono di compiere operazioni in seguito al ritorno del controllo e dei dati da una chiamata ad un server o un Web Service. C’è da dire che all’interno di Silverlight tutte le chiamate effettuate ai server sono asincrone; ciò risulta molto vantaggioso in quanto l’applicativo può continuare a svolgere il proprio lavoro evitando così di rimanere bloccato in attesa del ritorno della chiamata. All’interno del metodo execute() è poi evidenziata la creazione della classe ASCXLoadDelegate che consente alla classe Command di effettuare una richiesta di un servizio al server. 5.3.4
I DELEGATES I Delegates sono classi che, attraverso l’uso di componenti WebClient o service proxy, permettono alle applicazioni di interagire con Web Service o Web server in maniera asincrona e fortemente tipizzata. Ciascun Delegate rappresenta un contratto tra i team di sviluppo lato client e i team di sviluppo lato server. Un delegate viene creato e invocato dal corrispondente Command, e definisce e configura un oggetto di tipo IResponder, cioè il Command, per far sì che questo possa ricevere il risultato ritornato da una chiamata ad un servizio remoto. public abstract class GenericDelegate
{
protected IResponder responder;
/// A Constructor, receiving an IResponder instance,
/// used as "Callback" for the delegate's results /// through its OnResult and OnFault methods.
protected GenericDelegate(IResponder responder)
{
this.responder = responder;
}
} Il codice proposto qui sopra mostra una generica classe Delegate dalla quale deriveranno tutti i delegate creati all’interno dell’applicazione. La classe GenericDelegate possiede un costruttore che riceve un’istanza dell’interfaccia IResponder, che sarà utilizzata all’interno della classe derivata per richiamare i metodi onResult() e onFault() per la processazione dei dati ritornanti dalla chiamata asincrona effettuata dal delegate. public class ASCXLoadDelegate : GenericDelegate
{
public ASCXLoadDelegate(IResponder responder)
: base(responder)
{
}
public void SendRequest()
{
string PostUrl = "http://******/listenerDF.aspx?op=getDefGer";
//PostUrl = PostUrl.Replace("******",Manager.ip);
// Chiamata asincrona al web service attraverso il listener
WebClient DFService = new WebClient();
DFService.DownloadStringCompleted
+=
new
DownloadStringCompletedEventHandler(DFService_DownloadDefGerarchieCompleted);
DFService.DownloadStringAsync(Manager.getPostAddress(PostUrl));
}
void
DFService_DownloadDefGerarchieCompleted(object
sender,
DownloadStringCompletedEventArgs e)
{
if (null != e.Error)
responder.onFault("Exception! (" + e.Error.Message + ")");
else
responder.onResult(e.Result);
}
} L’esempio mostra la classe ASCXLoadDelagate che è istanziata all’interno della classe ASCXLoadCommand ed eredita dalla classe GenericDelegate. Nel codice esecutivo del metodo SendRequest() viene istanziato un oggetto di tipo WebClient che permette di effettuare una chiamata asincrona al Web Service. Quando il web service termina l’elaborazione della richiesta, il controllo torna al thread che aveva lanciato la richiesta e se non ci sono stati errori nell’elaborazione viene chiamato il metodo onResult() dell’istanza di IResponder, altrimenti viene richiamato il metodo onFault(). 5.3.5
IL FRONTCONTROLLER Il FrontController è un componente interno al Business layer che intercetta gli eventi di business inviati e associa ciascuna istanza di un evento alla corrispondente istanza di un Command; in pratica esso è come un ‘router’ per gli eventi di business e possiede un registro di mapping evento‐comando. public abstract class FrontController
{
/// <summary>
/// The dictionary of eventNames and corresponding commands to be
executed
/// </summary>
private Dictionary<string, ICommand> eventMap = new Dictionary<string,
ICommand>();
public FrontController()
{
GenericEventDispatcher.getInstance().EventDispatched +=
new
GenericEventDispatcher.EventDispatchDelegate(ExecuteCommand);
}
/// Whenever the GenericEventDispatcher raises an event, this
/// method gets the GenericEventArgs inside it - and calls
/// the execute() method on the corresponding ICommand
void ExecuteCommand(object sender, GenericEventArgs args)
{
if (eventMap.ContainsKey(args.raisedEvent.Name))
{
eventMap[args.raisedEvent.Name].execute(args.raisedEvent);
}
}
} /// register a business event to FrontController
public void addCommand(string genericEventName, ICommand command)
{
eventMap.Add(genericEventName, command);
}
Come è possibile notare la classe FrontController è una classe astratta (identificata dalla keyword abstract), ciò significa che non può essere istanziata e fornisce un insieme di membri comuni che più classi derivate possono condividere. La classe possiede un’istanza di una classe Dictionary che consente di associare al nome o ID univoco di un evento la relativa istanza della classe Command. All’interno del costruttore FrontController() viene associato all’evento EventDispatched della classe GenericEventDispatched un gestore che, al verificarsi dell’evento, esegue il metodo ExecuteCommand(). Tale metodo va a controllare se l’evento di business catturato è presente all’interno dal suo registro (l’istanza eventMap) e in tal caso chiama il metodo execute() del corrispondente Command. Per quel che riguarda il metodo addCommand(), esso serve per aggiungere una coppia evento‐comando a quelle già esistenti nel registro. public class DFController : FrontController
{
private static DFController instance;
/// Returns the single instance of the controller
public static DFController getInstance()
{
if (instance == null)
instance = new DFController();
return instance;
}
private DFController()
{
//prima che venga chiamato il metodo addCommand(),viene chiamato
//il costruttore della superclasse,cioè di FrontController
base.addCommand("ASCXLoad", new ASCXLoadCommand());
base.addCommand("ImpiantoLoad", new ImpiantoLoadCommand());
base.addCommand("DettagliLoad", new DettagliLoadCommand());
base.addCommand("ParamsPersLoad", new ParamsPersLoadCommand());
base.addCommand("GestPersParamLoad",
new
GestPersParamLoadCommand());
base.addCommand("ASCXPreview", new ASCXPreviewCommand());
base.addCommand("PersPreview", new PersPreviewCommand());
base.addCommand("AddPers", new AddPersCommand());
base.addCommand("DelPers", new DelPersCommand());
base.addCommand("SaveConfig", new SaveConfigCommand());
base.addCommand("CheckEredita", new CheckEreditaCommand());
base.addCommand("UncheckEredita", new UncheckEreditaCommand());
base.addCommand("SaveConf", new SaveConfCommand());
base.addCommand("DeleteParam", new DeleteParamCommand());
base.addCommand("ClosePanelPreview",
new
ClosePanelPreviewCommand());
base.addCommand("SavePPConfig", new SavePPConfigCommand());
base.addCommand("AddNewTab", new AddNewTabCommand());
base.addCommand("AddNewFieldset", new AddNewFieldsetCommand());
base.addCommand("SaveNewTab", new SaveNewTabCommand());
base.addCommand("OpenVocab", new OpenVocabCommand());
base.addCommand("SaveConfVocab", new SaveConfVocabCommand());
base.addCommand("FiltraVocab", new FiltraVocabCommand());
base.addCommand("OpenTIDetail", new OpenTIDetailCommand());
base.addCommand("SaveFieldset", new SaveFieldsetCommand());
base.addCommand("SaveField", new SaveFieldCommand());
base.addCommand("OpenSelIndex", new OpenSelIndexCommand());
base.addCommand("SaveConfSta", new SaveConfStaCommand());
base.addCommand("EliminaFieldset", new EliminaFieldsetCommand());
base.addCommand("SaveTab", new SaveTabCommand());
base.addCommand("EliminaTab", new EliminaTabCommand());
base.addCommand("OpenContrDetail", new OpenContrDetailCommand());
base.addCommand("MDClose", new MDCloseCommand());
base.addCommand("SaveDetailPanel", new SaveDetailPanelCommand());
base.addCommand("OpenSetStato", new OpenSetStatoCommand());
base.addCommand("OpenSelPers", new OpenSelPersCommand());
base.addCommand("OpenManBitmask", new OpenManBitmaskCommand());
base.addCommand("SaveComboPers", new SaveComboPersCommand());
base.addCommand("SaveManBitmask", new SaveManBitmaskCommand());
base.addCommand("SaveComboTI", new SaveComboTICommand());
base.addCommand("SaveStateContr", new SaveStateContrCommand());
base.addCommand("OpenFieldDetail", new OpenFieldDetailCommand());
base.addCommand("SubControlResize", new SubControlResizeCommand());
base.addCommand("MouseMove", new MouseMoveCommand());
base.addCommand("MouseButtonUp", new MouseButtonUpCommand());
base.addCommand("MouseButtonDown", new MouseButtonDownCommand());
base.addCommand("SelectTab", new SelectTabCommand());
base.addCommand("SectionPreview", new SectionPreviewCommand());
base.addCommand("OpenSectionDetail",
new
OpenSectionDetailCommand());
base.addCommand("SaveDetailSection",
new
SaveDetailSectionCommand());
base.addCommand("RunTest", new RunTestCommand());
} }
La classe DFController rappresenta il Controller dell’applicazione Silverlight per la realizzazione di form dinamiche. Anche tale classe implementa il pattern Singleton, limitando così l’istanzazione della classe ad un solo elemento per volta; inoltre essa eredita dalla classe FrontController. E’ da tenere a mente che le View non sono a conoscenza del Controller, come anche dei Command o dei Delegate. Esse semplicemente visualizzano i dati memorizzati nel Model layer ed inviano eventi in seguito al verificarsi di azioni compiute dagli utenti sui componenti UI. Alla creazione della classe DFController vengono registrate, attraverso il metodo addCommand() della classe astratta FrontController, tutte le coppie possibili evento‐comando utilizzabili dall’applicazione. In tal modo, quando un evento di business viene inviato al Business layer viene automaticamente gestito dal Controller che lancia l’esecuzione del corrispondente comando per effettuare la processazione. 6. WEB APPLICATION PER FORM DINAMICHE Si tratta di un’applicazione web, chiamata ManagerParametriDF, realizzata con la piattaforma Silverlight all’interno dell’ambiente di sviluppo Microsoft Visual Studio. Tale applicazione consente agli utenti di realizzare e gestire form dinamiche; per far ciò è necessaria una frequente interazione con il sistema di backend, descritto precedentemente, che consente a coloro che lo utilizzano di gestire tramite software le varie procedure di fornitura di servizi ai clienti. Tra i vari servizi rientra anche la possibilità di poter cambiare in un qualunque momento la struttura delle form dell’applicazione attraverso l’esecuzione di semplici operazioni, come quelle di drag and drop e di modifica di valori di alcuni campi che gestiscono i parametri delle form, effettuabili sull’interfaccia utente. Nella figura 30 viene mostrato un esempio di come le form gestite dal ManagerParametriDF vengono visualizzate all’interno di pagine ASP.NET prodotte dal sistema. Figura 30 – Preview di una form all’interno di una pagina ASP.NET L’applicazione prende in input dall’applicativo di backend presente sul server una lista di controlli ASCX con i relativi parametri e personalizzazioni, offrendo all’utente la possibilità di aggiungere nuove personalizzazioni o di cancellare alcune tra quelle esistenti. Per ogni controllo o personalizzazione è inoltre possibile visualizzare all’interno di una finestra popup un’anteprima per la modifica della form. Le modifiche effettuate sono poi riportate in output sul server per essere visibili all’utente finale che visualizza le web form in ASP.NET. Figura 31 – GUI principale del ManagerParametriDF Il linguaggio utilizzato per la realizzazione dell’applicativo di creazione di form dinamiche è stato il C#, linguaggio ad oggetti facente parte del framework .NET e per certi aspetti molto simile al linguaggio Java abbondantemente utilizzato in ambito universitario, in conformità a quello che è il linguaggio che l’azienda già utilizza per la realizzazione delle sue applicazioni, tra le quali quella di cui fa parte e con cui deve interfacciarsi l’applicativo stesso. Poiché la parte già esistente dell’applicativo era stata sviluppata utilizzando l’ambiente di sviluppo Microsoft Visual Studio 2005, prima di iniziare a codificare la nuova parte è stato necessario eseguire un porting dell’intera applicazione ad una versione dell’ambiente di sviluppo più aggiornata e con più funzionalità, ossia a Visual Studio 2008. 6.1 STRUTTURA DELL’APPLICAZIONE Quando l’applicazione Silverlight realizzata viene compilata, viene creato un file che la contiene. Tale file ha estensione .xap ed è a tutti gli effetti un file ‘zip’, che contiene gli assembly e le risorse che il progetto Silverlight compilato produce. [32] In particolare l’application package contiene: ƒ Un file AppManifest.xaml, che identifica gli assembly del package e il punto di entrata dell’applicazione; ƒ Un application assembly, che include la classe applicazione; ƒ Zero o più librerie di assembly; ƒ Zero o più risorse, come file contenenti immagini o video. Per quel che riguarda il file AppManifest.xaml, esso viene tipicamente generato dal processo di build e utilizza un markup XAML per dichiarare un oggetto Deployment per l’applicazione. L’elemento Deployment possiede un attributo RuntimeVersion per identificare la versione del plugin Silverlight richiesta, e gli attributi EntryPointType ed EntryPointAssembly per specificare il punto di entrata dell’applicazione. L’elemento Deployment include anche una proprietà Deployment.Parts che possiede un elemento figlio AssemblyPart per ciascun assembly contenuto nell’application package. <Deployment
xmlns="http://schemas.microsoft.com/client/2007/deployment"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
EntryPointAssembly="ManagerParametriDF" EntryPointType="ManagerParametriDF.App"
RuntimeVersion="2.0.30523.6">
<Deployment.Parts>
<AssemblyPart x:Name="ManagerParametriDF" Source="ManagerParametriDF.dll" />
<AssemblyPart
x:Name="CraigN.Windows.Controls"
Source="CraigN.Windows.Controls.dll" />
<AssemblyPart x:Name="SilverlightPattern" Source="SilverlightPattern.dll" />
<AssemblyPart
x:Name="System.Windows.Controls.Extended"
Source="System.Windows.Controls.Extended.dll" />
<AssemblyPart x:Name="System.Xml.Linq" Source="System.Xml.Linq.dll" />
<AssemblyPart
x:Name="System.Xml.Serialization"
Source="System.Xml.Serialization.dll" />
</Deployment.Parts>
</Deployment> Tale package deve includere necessariamente il file manifest e l’assembly che rappresenta il punto di entrata dell’applicazione. Per quel che riguarda gli altri componenti, essi possono essere deployati come file in‐package oppure come file on‐demand. I primi sono file che vengono inclusi nel package dell’applicazione; sono file che l’applicazione richiede all’avvio o che devono essere disponibili in lettura per evitare ritardi dopo l’avvio. Tra questi file è possibile includere anche risorse come immagini, che è possibile inserire in file assembly o all’interno dell’application package come file separati. I file on‐demand sono dei file che vengono deployati sul server, tipicamente nella stessa locazione dell’application package. L’applicazione può ricercare tali file dopo l’attivazione. A seconda del tipo di file e della loro dimensione, ci sono parecchie opzioni per recuperare tali file su richiesta. Per esempio, è possibile usare riferimenti URI diretti per recuperare file contenenti immagini, oppure è possibile avviare download asincroni per ritrovare librerie assembly o file zip. La figura 31 mostra un diagramma che riassume la struttura del package dell’applicazione e le opzioni di deployment per i file dell’applicazione. Figura 31 – Struttura file .xap e opzioni di deployment Per poter essere utilizzato all’interno di un progetto web, il file .xap prodotto deve essere inserito all’interno della cartella ClientBin presente nel progetto del sito che lo ospita. Figura 32 – L’archivio .xap all’interno del progetto Web Per richiamare il plugin da una pagina aspx utilizziamo il controllo <asp:Silverlight>, che espone una serie di attributi tra cui ci sono quelli che consentono di specificare il path per referenziare il file .xap, la versione del plugin Silverlight utilizzata e lo spazio occupato dall’applicazione all’interno della pagina aspx. <%@ Page Language="C#" AutoEventWireup="true" %>
<%@
Register
Namespace="System.Web.UI.SilverlightControls"
TagPrefix="asp" %>
Assembly="System.Web.Silverlight"
<!DOCTYPE
html
PUBLIC
"-//W3C//DTD
XHTML
1.0
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
Transitional//EN"
<html xmlns="http://www.w3.org/1999/xhtml" style="height:100%;">
<head runat="server">
<title>Test Page For ManagerParametriDF</title>
</head>
<body style="height:100%;margin:0;">
<form id="form1" runat="server" style="height:100%;">
<asp:ScriptManager
ID="ScriptManager1"
runat="server"></asp:ScriptManager>
<div style="height:100%;">
<asp:Silverlight
ID="Xaml1"
runat="server"
Source="~/ClientBin/ManagerParametriDF.xap"
Version="2.0"
Width="100%"
Height="100%" />
</div>
</form>
</body>
</html> 6.2 L’APPLICATION CLASS Come tutte le applicazioni Silverlight, anche il progetto ManagerParametriDF presenta quattro file (due file .xaml e due file .cs), che risultano essere fondamentali nello sviluppo dell’applicazione. I file App.xaml e App.xaml.cs sono rispettivamente il file di markup e il file contenente il codice code‐behind della classe App; tale classe rappresenta il punto di ingresso dell’applicazione e deve derivare dalla classe Application. La classe Application fornisce un evento Startup che consente di gestire l’inizializzazione dell’applicazione e la sua interfaccia utente, ed un evento Exit che consente di eseguire una serie di operazioni desiderate al momento della chiusura dell’applicazione. La classe in questione fornisce inoltre altri servizi comunemente usati dalle applicazioni, come l’estrazione di file di risorse dal package dell’applicazione o da file zip scaricati. Viene di seguito mostrato il codice C# del file App.xaml.cs: public partial class App : Application
{
public App()
{
this.Startup += this.Application_Startup;
this.Exit += this.Application_Exit;
this.UnhandledException += this.Application_UnhandledException;
InitializeComponent();
}
private void Application_Startup(object sender, StartupEventArgs e)
{
// Load the main control
//this.RootVisual = new Page();
this.RootVisual = new PageSwitcher();
//create Controller instance
DFController cntrller = DFController.getInstance();
//lettura dei parametri di inizializzazione del plugin
string lingua;
string tf;
string ak;
try
{
lingua = e.InitParams["Lingua"];
}
catch (Exception)
{
//default se non passato
lingua = "IT";
}
try
{
tf = e.InitParams["tf"];
}
catch (Exception)
{
//default se non passato
if ((Manager.condition == "development") && (Manager.startForm
== "Struttura"))
tf = "Struttura";
else
tf = "MasterForm";
}
try
{
ak = e.InitParams["ak"];
}
catch (Exception)
{
if ((Manager.condition == "development") && (Manager.startForm
== "Struttura"))
ak = "struttura1";
else
ak = "";
}
//imposto i riferimenti statici
Manager.setInitialIdLingua(lingua);
Manager.setInitialTargetForm(tf);
Manager.setInitialAccessoryKeys(ak);
}
private void Application_Exit(object sender, EventArgs e)
{
}
private
void
Application_UnhandledException(object
ApplicationUnhandledExceptionEventArgs e)
{
}
} sender,
Come è possibile notare dal codice riportato sopra, all’interno del metodo di gestione dell’evento Startup, oltre alla definizione dei riferimenti statici dell’applicazione attraverso la lettura dei parametri di inizializzazione (lingua, form target iniziale, …) del plugin, viene caricato lo User Control principale definito attraverso la classe PageSwitcher e viene creata l’istanza della classe DFController derivata dalla classe FrontController. La classe DFController rientra tra quelle che sono le classi necessarie per mappare l’applicazione ManagerParametriDF sul Pattern MVC. Quando viene richiamato il costruttore di tale classe devono essere aggiunte, attraverso il metodo addCommand() ereditato dalla superclasse, al suo registro (il Dictionary) tutte le possibili coppie evento‐comando che l’applicazione può utilizzare durante la sua esecuzione. Per quel che riguarda la classe PageSwitcher, essa si occupa del caricamento del vocabolario di profilazione dal server e del reperimento della form che dovrà essere visualizzata all’avvio dell’applicazione. A tal riguardo, la form iniziale potrà essere quella di default, ossia quella contenuta nella classe Page se si desidera visualizzare la MasterForm, oppure quella relativa alla classe TestStruttura nel caso in cui si intenda effettuare un test. public partial class PageSwitcher : UserControl
{
//evento DictionaryLoaded
/// <summary>
/// Evento che indica l'avvenuto caricamento del vocabolario
/// e il reperimento della pagina di destinazione
/// </summary>
public event EventHandler PageSwitcherReady;
//pagina da caricare
UserControl TargetPage;
//riferimento al dom
HtmlDocument _doc;
private DFModel model = DFModel.getInstance();
public PageSwitcher()
{
InitializeComponent();
_doc = HtmlPage.Document;
this.Loaded += new RoutedEventHandler(Page_Loaded);
this.PageSwitcherReady
+=
EventHandler(PageSwitcher_PageSwitcherReady);
new
}
/// <summary>
/// Funzione per il redirezionamento (se TargetPage=null apre la form di
default)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void PageSwitcher_PageSwitcherReady(object sender, EventArgs e)
{
Manager.WaitingFormOff();
if (TargetPage == null)
TargetPage = new Page();
this.Content = TargetPage;
}
/// <summary>
/// Funzione che recupera i parametri provenienti dal chiamante
/// che individueranno la form da caricare
/// </summary>
private void ReadExternalPars()
{
string targetForm;
string accessoryKey;
//lettura dei parametri esterni e impostazione di TargetPage
targetForm = Manager.getInitialTargetForm();
accessoryKey = Manager.getInitialAccessoryKeys();
if
"MasterForm"))
{
((string.IsNullOrEmpty(targetForm))
TargetPage = null;
RaisePageSwitcherReady();
}
||
(targetForm
==
else
{
if (string.IsNullOrEmpty(accessoryKey))
{
TargetPage = null;//se ho chiesto la struttura ma la chiave
è nulla rimando a MasterPage
RaisePageSwitcherReady();
}
else
{
getDescStruttura(accessoryKey);
return;
}
}
}
/// <summary>
/// Metodo che a partire da una chiave recupera la struttura
corrispondente dal listener
/// crea una struttura di tipo DynamicForm, istanzia la classe
TestStruttura, usando la DynamicForm
/// creata sul costruttore, setta targetPage a un riferimento alla
classe istanziata e chiama RaisePageSwitcherReady()
/// </summary>
/// <param name="accessoryKey"></param>
/// <returns></returns>
private void getDescStruttura(string accessoryKey)
{
//split dei parametri ricevuti all'inizializzazione del plugin
//in accessoryKeys ho l'id della configurazione e la descrizione
della configurazione
//concatenati con pipe
string[] args;
string PostUrl;
args = accessoryKey.Split('|');
if(Manager.FlagStrutturaTest)
PostUrl = "http://******/listenerDF.aspx?op=getStruTest&par=" +
args[0];
else //passo al listener la chiave e la descrizione della
configurazione
PostUrl
=
"http://******/listenerDF.aspx?op=getStru&par="
+
args[0] + "&par2=" + args[1];
//PostUrl = PostUrl.Replace("******", Manager.ip);
// Chiamata asincrona al web service attraverso il listener
WebClient DFService = new WebClient();
DFService.DownloadStringCompleted
+=
DownloadStringCompletedEventHandler(DFService_DownloadStrutturaCompleted);
DFService.DownloadStringAsync(Manager.getPostAddress(PostUrl));
}
//callBack chiamata al carica struttura
void
DFService_DownloadStrutturaCompleted(object
DownloadStringCompletedEventArgs e)
{
//in e.Result c'è la struttura serializzata
//quindi ricreo un oggetto di tipo DynamicForm
//creo TestStruttura(DynamicForm)
//assegno a TargetForm TestStruttura
if (e.Error == null)
new
sender,
TargetPage = new TestStruttura(Deserialize(e.Result));
else
{
Manager.msgBox("Errore recupero struttura.");
TargetPage = null;
}
RaisePageSwitcherReady();
}
private DynamicForm Deserialize(string p)
{
// Se sono in condizioni di test (FlagStrutturaTest=true) allora
verrà generata una struttura di tipo DynamicForm finta al volo
DynamicForm fakeDF = new DynamicForm();
List<Section> lstSec = new List<Section>();
Section sec;
XDocument xmlStructure;
if(Manager.FlagStrutturaTest)
{//struttura finta creata al volo
#region Costruzione struttura per test
//per test creo al volo 2 DynamicForm fittizie
if(p=="struttura1")
{
sec = new Section();
sec.Id = "primo";
sec.Order = 2;
sec.Tipologia = "~/ASCX/Sezione3.ascx";
sec.PersASCX = "Parametrizzazione_1";
lstSec.Add(sec);
sec = new Section();
sec.Id = "secondo";
sec.StatoSezione = "expanded";
sec.Tipologia = "~/ASCX/Griglia.ascx";
sec.PersASCX = "Parametrizzazione_Grid_1";
sec.AllowExportExcel = true;
sec.Order = 0;
lstSec.Add(sec);
sec = new Section();
sec.KeyVocabolario = "TIT_VOC";
sec.Tipologia = "~/ASCX/Sezione1.ascx";
sec.PersASCX = "P_2";
sec.Id = "terzo";
sec.Visibility = false;
sec.Order = 1;
lstSec.Add(sec);
fakeDF.Sezioni = lstSec;
}
else
{
sec = new Section();
sec.Id = "primo";
sec.Order = 2;
sec.Tipologia = "~/ASCX/Sezione3.ascx";
sec.PersASCX = "Parametrizzazione_1";
lstSec.Add(sec);
sec = new Section();
sec.Id = "secondo";
sec.StatoSezione = "expanded";
sec.Tipologia = "~/ASCX/Griglia.ascx";
sec.PersASCX = "Parametrizzazione_Grid_1";
sec.AllowExportExcel = true;
sec.Order = 0;
lstSec.Add(sec);
fakeDF.Sezioni = lstSec;
}
#endregion
}
else
{//caso reale di struttura recuperata dal db
xmlStructure = XDocument.Parse(p);
//setto le proprietà di DynamicForm a primo livello
fakeDF.Id = xmlStructure.Root.Element("Id").Value;
fakeDF.CssForm = xmlStructure.Root.Element("CssForm").Value;
fakeDF.CssTitolo = xmlStructure.Root.Element("CssTitolo").Value;
fakeDF.KeyVocabolario
=
xmlStructure.Root.Element("KeyVocabolario").Value;
var
itemsSection
=
from
xmlStructure.Root.Element("Sezioni").Elements("Section")
select Sect;
Sect
////ricostruzione della classe section
foreach (XElement xsec in itemsSection)
{
sec = new Section();
sec.PFlag = bool.Parse(xsec.Element("PFlag").Value);
sec.AllowExportExcel
bool.Parse(xsec.Element("AllowExportExcel").Value);
sec.Order = int.Parse(xsec.Element("Order").Value);
sec.Visibility
bool.Parse(xsec.Element("Visibility").Value);
sec.Ismultimodel
bool.Parse(xsec.Element("IsMultiModel").Value);
sec.Id = xsec.Element("Id").Value;
sec.PersASCX = xsec.Element("PersASCX").Value;
sec.HSez = int.Parse(xsec.Element("HSez").Value);
sec.WSez = int.Parse(xsec.Element("WSez").Value);
sec.CssPanel = xsec.Element("CssPanel").Value;
sec.CssTitolo=xsec.Element("CssTitolo").Value;
sec.StatoSezione
getStatoSez(xsec.Element("StatoSez").Value);
sec.KeyVocabolario = xsec.Element("KeyVocabolario").Value;
sec.SezModel = xsec.Element("SezModel").Value;
sec.Tipologia = xsec.Element("Tipologia").Value;
sec.Top = int.Parse(xsec.Element("Top").Value);
sec.Left = int.Parse(xsec.Element("Left").Value);
sec.Provenienza = xsec.Element("Provenienza").Value;
fakeDF.Sezioni.Add(sec);
}
}
return fakeDF;
}
in
=
=
=
=
/// <summary>
/// Funzione di raccordo per lo stato (collassato o espanso) della
sezione
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
private string getStatoSez(string p)
{
if (p.ToLower() == "open")
return "expanded";
else
return "collapsed";
}
void RaisePageSwitcherReady()
{
if (this.PageSwitcherReady != null)
PageSwitcherReady(this, null);
}
void Page_Loaded(object sender, RoutedEventArgs e)
{//appena è stata caricata la form lancio la modale di attesa
//carico il vocabolario
//calcolo delle strutture eventualmente necessarie per la targetForm
richiesta
//test
Manager.WaitingFormOn();
InizializzaVocabolario();
}
/// <summary>
/// Metodo per navigare tra le pagine
/// </summary>
/// <param name="nextPage"></param>
public void Navigate(UserControl nextPage)
{
this.Content = nextPage;
}
/// <summary>
/// Metodo per l'inizializzazione del vocabolario profilato
/// </summary>
private void InizializzaVocabolario()
{
string PostUrl = "http://******/listenerDF.aspx?op=getVoc";
//PostUrl = PostUrl.Replace("******", Manager.ip);
// Chiamata asincrona al web service attraverso il listener
WebClient DFService = new WebClient();
DFService.DownloadStringCompleted
+=
new
DownloadStringCompletedEventHandler(DFService_DownloadVocabolarioCompleted);
DFService.DownloadStringAsync(Manager.getPostAddress(PostUrl));
}
//callBack chiamata al carica vocabolario
void
DFService_DownloadVocabolarioCompleted(object
sender,
DownloadStringCompletedEventArgs e)
{
string idLingua = Manager.getInitialIdLingua();
if (e.Error == null)
model.Vocabolario = getVocabolarioFromXml(idLingua, e.Result);
else
{
Manager.msgBox("Errore caricamento vocabolario.");
return;
}
ReadExternalPars();
}
private VocabolarioDFProf getVocabolarioFromXml(string idLingua, string
p)
{
//inizializzo la struttura vocabolario
VocabolarioDFProf voc = new VocabolarioDFProf();
ItemVocabolarioDFProf itemVoc;
voc.KeyLingua = idLingua;
XDocument xmlVoc;
xmlVoc = XDocument.Parse(p);
var
itemsVocabolario
=
from
ItemVoc
in
xmlVoc.Root.Element("ItemChiaveVocabolarioCollection").Elements("ItemChiaveVocab
olario")
select ItemVoc;
foreach (XElement xVoc in itemsVocabolario)
{
itemVoc = new ItemVocabolarioDFProf();
//per.Element("ParCod").Value
//(string)ger.Attribute("ModID").Value
itemVoc.Key = xVoc.Element("Key").Value;
foreach
(XElement
itemLingua
in
xVoc.Element("ItemLinguaCollection").Elements("ItemLingua"))
{
if (itemLingua.Element("IdLingua").Value == idLingua)
{
itemVoc.Content = itemLingua.Element("Valore").Value;
break;
}
}
voc.Items.Add(itemVoc);
}
return voc;
}
} Ritornando alla classe Application, per quel che riguarda il file di markup (App.xaml), esso contiene gli oggetti di stile di riferimento che saranno utilizzati da alcuni controlli dell’interfaccia grafica. Per modificare l’aspetto dei controlli di un’applicazione ci sono varie metodologie, tra le più comuni ci sono queste tre: ƒ Usare i controlli così come sono, si modifica leggermente il loro aspetto attraverso l’impostazione degli stili all’interno del file xaml (o attraverso la finestra di proprietà in Visual Studio o Blend); ƒ Creare oggetti Style riusabili per fornire lo stesso aspetto ai controlli che svolgono la stessa funzione; ƒ Creare dei template per ridisegnare i controlli, in tal modo il loro aspetto cambia radicalmente mantenendo immutato il loro comportamento. Il modo più semplice e forse più comune di impostare lo stile di un controllo è farlo manualmente all’interno del file di markup che contiene il controllo, oppure nella finestra di proprietà di Visual Studio 2008 o di Expression Blend. Ciò è utile quando si vuole che tutti i controlli usino ad esempio una diversa dimensione per il loro font. Figura 33 – Impostazione della proprietà FontSize nello Xaml Figura 33 – Impostazione dello stile in Blend Quando si vuole invece che tutti i controlli abbiano determinate caratteristiche di stile, sarebbe molto dispendioso in termini di tempo e fatica modificare tutti i controlli a mano all’interno dello Xaml; risulta in tal caso molto più conveniente creare una Style Resource a cui è possibile assegnare un nome e applicarla a ciascuno dei controlli appartenenti al TargetType specificato. Tale metodologia consente di creare un aspetto uniforme e fornisce una singola posizione per cambiare in una sola volta l’aspetto di tutti i controlli. Tali oggetti Style vengono inseriti all’interno del markup della classe Application come mostrato di seguito. <Application xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ManagerParametriDF.App"
xmlns:controls="clrnamespace:DraggableWindow;assembly=ManagerParametriDF"
>
<Application.Resources>
<Style x:Key="ContentBlock" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Trebuchet MS"/>
<Setter Property="TextAlignment" Value="Left"/>
<Setter Property="FontSize" Value="10"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="TitleBlock" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Trebuchet MS"/>
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="TextAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="ParBlock" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Trebuchet MS"/>
<Setter Property="Foreground" Value="Blue"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="TextAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="DFCloseButton" TargetType="Button">
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="Width" Value="50"/>
<Setter Property="Height" Value="25"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border
x:Name="brd1"
Width="22"
Height="22"
CornerRadius="15">
TextAlignment="center"
FontFamily="Webdings"/>
<TextBlock
x:Name="txt1"
Foreground="#222"
Text="r"
FontSize="11"
VerticalAlignment="center"
<Border.Background>
<RadialGradientBrush
GradientOrigin=".3,
.3">
<GradientStop
Color="#FFF"
Offset=".15"/>
<GradientStop Color="#777" Offset="1"/>
</RadialGradientBrush>
</Border.Background>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- CloseButton Style per le windows -->
<Style x:Key="CloseButton" TargetType="Button">
<Setter Property="Width" Value="30" />
<Setter Property="Height" Value="12" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Width="22" Height="12" CornerRadius="0,0,3,3">
<TextBlock
Foreground="WhiteSmoke"
Text="r"
FontSize="10"
FontFamily="Webdings"
TextAlignment="Center"
VerticalAlignment="Center"
>
</TextBlock>
<Border.Background>
<LinearGradientBrush
EndPoint="0.5,1"
StartPoint="0.5,0">
<GradientStop Color="#FFDB9F96" Offset="0"/>
<GradientStop
Color="#FFBE6D5F"
Offset="0.499"/>
<GradientStop
Color="#FFA63A29"
Offset="0.5"/>
<GradientStop Color="#FFbE6E5A" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style> A volte si ha la necessità di andare oltre quello che può essere un semplice stile, modificando radicalmente l’aspetto di un controllo senza modificare il suo comportamento. In tali situazioni i template giocano un ruolo fondamentale. Vediamo un esempio di utilizzo dei template applicato ad un controllo di tipo Button. Aggiungiamo innanzitutto il controllo all’interno del markup di una pagina Silverlight. Al momento della sua creazione, il controllo ha il seguente aspetto: Figura 34 – Controllo di tipo Button prima dell’utilizzo del template Applichiamo ora un template di nome RoundButton al controllo in questione, inserendo tale codice all’interno del file di markup App.xaml: Per poter applicare il template al controllo è necessario che quest’ultimo faccia riferimento ad esso all’interno della sua proprietà Style: Fatto ciò, il controllo di tipo Button presenta il seguente aspetto, che è radicalmente diverso da quello che esso aveva in precedenza. Figura 35 – Controllo di tipo Button dopo l’utilizzo del template 6.3 L’APPLICATION CORE Ritornando a quelli che sono i file essenziali per lo sviluppo di un’applicazione, all’interno del progetto ManagerParametriDF la classe Page, con il suo file di markup Page.xaml ed il suo file di code‐behind Page.xaml.cs, rappresenta quello che è il core vero e proprio dell’applicativo. Nel caso in cui si desideri visualizzare la MasterForm, la classe Page contiene nel suo markup la form che verrà visualizzata all’avvio dell’applicazione. <UserControl x:Class="ManagerParametriDF.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WS="clr-namespace:ManagerParametriDF;assembly=ManagerParametriDF"
xmlns:SL="clrnamespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:controls="clr-namespace:DraggableWindow;assembly=ManagerParametriDF"
xmlns:myCustom="clrnamespace:ManagerParametriDF;assembly=ManagerParametriDF"
xmlns:a="clr-namespace:ManagerParametriDF.ServiceControls"
Loaded="UserControl_Loaded">
<Grid x:Name="LayoutRoot" ShowGridLines="True"
<Grid.RowDefinitions>
Background="#FFDEDEDE">
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Popup x:Name="popup" IsOpen="False">
<ContentControl>
<myCustom:Preview
x:Name="preview1"
Visibility="Collapsed"
Test="False" Configurazione="{Binding ConfigASCX, Mode=TwoWay}" Grid.Row="2"/>
</ContentControl>
</Popup>
<Grid Background="#FFDEDEDE" Grid.Row="0" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<!--Bottoni Carica -->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button x:Name="btnFullScreen" Grid.Column="0" Content="Full
Screen" Click="btnFullScreen_Click" />
<Button x:Name="btnLoad" Grid.Column="1" Content="Load/ReLoad"
Click="btnLoad_Click" />
<Button x:Name="btnPreview" Grid.Column="2" Content="Preview"
Click="btnPreview_Click" />
<Button
x:Name="btnTest"
Grid.Column="3"
Content="Test"
Click="btnTest_Click" />
</Grid>
<TextBlock
Text="Dettagli"
Margin="5"
Grid.Row="1"
Style="{StaticResource TitleBlock}"/>
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Nome: " Margin="5" Grid.Row="0" Grid.Column="0"
Style="{StaticResource TitleBlock}"/>
<TextBlock Text="Tipo: " Margin="5" Grid.Row="1" Grid.Column="0"
Style="{StaticResource TitleBlock}"/>
<TextBlock
Text="Default:
"
Margin="5"
Grid.Row="2"
Grid.Column="0" Style="{StaticResource TitleBlock}"/>
<TextBlock x:Name="txtNome" Text=""
Margin="5" Grid.Row="0"
Grid.Column="1" Style="{StaticResource ParBlock}"/>
<TextBlock x:Name="txtTipo" Text="" Margin="5" Grid.Row="1"
Grid.Column="1" Style="{StaticResource ParBlock}"/>
<TextBlock x:Name="txtDefault" Text="" Margin="5" Grid.Row="2"
Grid.Column="1" Style="{StaticResource ParBlock}"/>
</Grid>
</Grid>
<!--Lista ASCX-->
<Grid Background="LightGray" Grid.Row="0" Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock
Text="ASCX"
Margin="5"
Grid.Row="0"
Style="{StaticResource TitleBlock}"/>
<ListBox
x:Name="ASCXList"
Background="#FFDEDEDE"
Grid.Row="1"
SelectionChanged="ASCXList_SelectionChanged"
ItemsSource="{Binding Path=ASCXList}" SelectedIndex="{Binding
Path=ASCXSelectedIndex, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="200"></ColumnDefinition>
<ColumnDefinition
Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- Lista ASCX-->
<TextBlock
Text="{Binding
Path=GerCod}"
Margin="5" Grid.Row="0" Grid.Column="0" Style="{StaticResource ContentBlock}"/>
<TextBlock
Text="{Binding
Path=GerDes}"
Margin="5" Grid.Row="0" Grid.Column="1" Style="{StaticResource ContentBlock}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
<!--Lista Parametri default-->
<Grid Background="LightGray" Grid.Row="0" Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<Grid x:Name="pulsantierad" Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button x:Name="btnPreviewDefault" Content="PW" Grid.Column="0"
Click="btn_Preview_Default_click"></Button>
</Grid>
<TextBlock
Text="Parametri
Default
DefGerarchie"
Margin="5"
Grid.Row="0" Style="{StaticResource TitleBlock}"/>
<ListBox
x:Name="ParametriDefaultList"
Background="#FFDEDEDE"
Grid.Row="1" SelectionChanged="ParametriDefaultList_SelectionChanged"
ItemsSource="{Binding
Path=ParametriDefaultList}"
SelectedIndex="{Binding Path=ParametriDefaultSelectedIndex, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="200"></ColumnDefinition>
<ColumnDefinition
Width="100"></ColumnDefinition>
<ColumnDefinition
Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- Lista ASCX-->
<TextBlock Text="{Binding ParCod}" Margin="5"
Grid.Row="0" Grid.Column="0" Style="{StaticResource ContentBlock}"/>
<TextBlock
Text="{Binding
Vers}"
Margin="5"
Grid.Row="0" Grid.Column="1" Style="{StaticResource ContentBlock}"/>
<TextBlock Text="{Binding Default}" Margin="5"
Grid.Row="0"
Grid.Column="2"
Foreground="red"
Style="{StaticResource
ContentBlock}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
<!--Lista Personalizzazioni ASCX-->
<Grid Background="LightGray" Grid.Row="1" Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<Grid x:Name="pulsantiera" Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button x:Name="btnAggiungiPers" Content="Add" Grid.Column="0"
Click="btn_New_pers_click"></Button>
<Button x:Name="btnEliminaPers" Content="Del" Grid.Column="1"
Click="btn_Del_pers_click"></Button>
</Grid>
<TextBlock Text="Personalizzazioni ASCX" Margin="5" Grid.Row="0"
Style="{StaticResource TitleBlock}"/>
<ListBox x:Name="ASCXPersList" Background="#FFDEDEDE" Grid.Row="1"
SelectionChanged="ASCXPersList_SelectionChanged"
ItemsSource="{Binding
Path=ASCXPersList}"
SelectedIndex="{Binding Path=ASCXPersSelectedIndex, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="200"></ColumnDefinition>
<ColumnDefinition
Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- Lista ASCX-->
<TextBlock Text="{Binding GerCod}" Margin="5"
Grid.Row="0" Grid.Column="0" Style="{StaticResource ContentBlock}"/>
<TextBlock Text="{Binding OggCod}" Margin="5"
Grid.Row="0" Grid.Column="1" Style="{StaticResource ContentBlock}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
<!--Lista Parametri per personalizzazione-->
<Grid Background="LightGray" Grid.Row="1" Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="titParametri" Text="Parametri/Personalizzazione
ASCX" Margin="5" Grid.Row="0" Style="{StaticResource TitleBlock}"/>
<ListBox
x:Name="ASCXPersParList"
Background="#FFDEDEDE"
Grid.Row="1" SelectionChanged="ASCXParPersList_SelectionChanged"
ItemsSource="{Binding
Path=ASCXPersParList}"
SelectedIndex="{Binding Path=ASCXPersParSelectedIndex, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="22"></ColumnDefinition>
<ColumnDefinition
Width="auto"></ColumnDefinition>
<ColumnDefinition
Width="200"></ColumnDefinition>
<ColumnDefinition
Width="100"></ColumnDefinition>
<ColumnDefinition
Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- Lista Parametri x personalizzazione-->
<CheckBox
IsChecked="{Binding
Ereditato}"
IsEnabled="False" Grid.Row="0" Grid.Column="1" ></CheckBox>
<TextBlock
x:Name="txtNomePar"
Text="{Binding
ParCod}"
Margin="5"
Grid.Row="0"
Grid.Column="2"
Style="{StaticResource
ContentBlock}"/>
<TextBlock
Text="{Binding
Vers}"
Margin="5"
Grid.Row="0" Grid.Column="3" Style="{StaticResource ContentBlock}"/>
<TextBlock
x:Name="txtValDef"
Text="{Binding
Default}"
Margin="5"
Grid.Row="0"
Grid.Column="4"
Foreground="red"
Style="{StaticResource ContentBlock}"/>
<Image
Height="20"
Width="20"
Cursor="Hand"
Grid.Row="0"
Grid.Column="0"
Margin="1,1,1,1"
MouseLeftButtonUp="DeletePar_Click"
Source="../FakeControls/Resource/DeletePar.png">
</Image>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
<!--Valore Parametro modifica/aggiungi/annulla-->
<Grid Background="#FFDEDEDE" Grid.Row="1" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock
Text="Gestione
Parametro"
Margin="5"
Grid.Row="0"
Style="{StaticResource TitleBlock}"/>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Valore" Margin="5" Grid.Row="0" Grid.Column="0"
Style="{StaticResource TitleBlock}"/>
<TextBlock
Text="Ereditato"
Margin="5"
HorizontalAlignment="Right" Grid.Row="0" Grid.Column="1" Style="{StaticResource
TitleBlock}"/>
<CheckBox
x:Name="cbxEreditato"
IsEnabled="False"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Grid.Row="0"
Grid.Column="2" ></CheckBox>
<TextBox x:Name="txtValore" Text=""
Height="20" Width="auto"
FontSize="12" TextAlignment="Center" Grid.Row="1" Grid.ColumnSpan="3"></TextBox>
</Grid>
<!--pulsanti salvataggio aggiunta cancellazione -->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button x:Name="btnSalvaParam"
Grid.Column="0" Content="Salva"
Click="btnSalvaParam_Click" />
<Button
x:Name="btnEliminaParam"
Grid.Column="1"
Content="Elimina" Click="btnEliminaParam_Click" />
<Button
x:Name="btnAggiungiParam"
Grid.Column="2"
Content="Aggiungi" Click="btnAggiungiParam_Click" />
</Grid>
</Grid>
<SL:GridSplitter
Width="5"
HorizontalAlignment="Left"
VerticalAlignment="Stretch" Grid.Column="1" Grid.RowSpan="3"></SL:GridSplitter>
<SL:GridSplitter
Width="5"
HorizontalAlignment="Left"
VerticalAlignment="Stretch" Grid.Column="2" Grid.RowSpan="3"></SL:GridSplitter>
<SL:GridSplitter
Height="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom" Grid.Row="0" Grid.ColumnSpan="3"></SL:GridSplitter>
<SL:GridSplitter
Height="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom" Grid.Row="1" Grid.ColumnSpan="3"></SL:GridSplitter>
</Grid>
</UserControl> Come è possibile notare dal codice riportato, all’interno del controllo di tipo Grid principale (LayoutRoot) viene specificato il numero di righe e di colonne che esso possiede; in tal modo la pagina viene suddivisa in blocchi (o sezioni) all’interno dei quali verranno caricati in sottogriglie tutti i controlli dell’interfaccia grafica di cui necessita l’applicazione. Figura 36 – MasterForm principale dell’applicazione Alcuni controlli presenti nell’interfaccia fanno binding sugli elementi presenti all’interno della classe DFModel, di cui si è discusso in precedenza, che rappresenta il contenitore dei dati utilizzati dalle varie viste; ogni cambiamento ai dati di questa classe si ripercuote sul rendering degli elementi delle viste legati ad essi in binding. E’ il caso ad esempio del controllo ASCXList di tipo ListBox della sezione ASCX che visualizza la lista delle gerarchie ASCX. Come la classe Application, anche la classe Page è un controllo utente; per questo motivo essa eredita dalla classe UserControl. All’interno del costruttore della pagina avviene l’inizializzazione di tutti i componenti in essa presenti, viene impostato in base alla lingua il valore della caption dei pulsanti presenti nella sezione Gestione Parametro, viene agganciato un handler all’evento FullScreenChanged per la gestione del FullScreen, e viene assegnato alla variabile Griglia presente nel Model il valore della Grid principale che contiene tutti i controlli (questo serve per una corretta gestione delle finestre di popup). Quando viene caricata la pagina viene eseguito il metodo Page_Loaded() che lancia la modale di attesa per aspettare la risposta asincrona del caricamento del vocabolario di profilazione. public Page()
{
InitializeComponent();
_doc = HtmlPage.Document;
//risoluzione caption in lingua pulsanti
this.btnAggiungiPers.Content
=
ManagerConst.getCaptionByLanguageId(ManagerConst.PageBtnAggiungiPersCaption) as
string;
this.btnEliminaPers.Content
=
ManagerConst.getCaptionByLanguageId(ManagerConst.PageBtnEliminaPersCaption)
as
string;
this.btnPreviewDefault.Content
=
ManagerConst.getCaptionByLanguageId(ManagerConst.PageBtnPreviewDefaultCaption)
as string;
//aggancio evento per la gestione del fullscreen
SilverlightHost host = Application.Current.Host;
host.Content.FullScreenChanged
+=
EventHandler(this.FullScreenChanged);
//salvo l'originale per poter cambiare la caption
visualizzazione dei parametri di default
OrigtitoloParametri = this.titParametri.Text;
//PulisciParPers();
lista = new List<Parametro>();
new
in
caso
di
DFModel.getInstance().Griglia = this.LayoutRoot;
}
void Page_Loaded(object sender, RoutedEventArgs e)
{
//appena è stata caricata la form lancio la modale di attesa
//per aspettare la risposta asincrona del caricamento vocabolario
Manager.WaitingFormOn();
} Una operazione fondamentale per poter implementare correttamente il Pattern MVC all’interno dell’applicativo è l’assegnazione, al momento del caricamento dello UserControl, dell’istanza della classe DFModel alla proprietà DataContext del controllo Grid principale. Con tale assegnamento si specifica la sorgente di dati per la vista e per tutti i suoi componenti che fanno binding sul Model. Poiché il DataContext viene ereditato lungo la struttura dei controlli, non è necessario impostarlo sui singoli controlli. private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
DFModel model = DFModel.getInstance();
//bind model to view
LayoutRoot.DataContext = model;
} Prima dell’utilizzo dell’implementazione del Pattern MVC, all’interno della classe Page venivano eseguite quasi tutte le operazioni necessarie per soddisfare una richiesta dell’utente che interagiva con l’interfaccia grafica. Ciò, oltre a causare un notevole accumulo di codice all’interno della classe e quindi una maggior difficoltà nella lettura del codice, comportava anche una gestione da codice dei cambiamenti ai dati che la vista doveva visualizzare, con conseguente aumento della complessità della codifica. Ecco come si presentava la classe prima del mapping dell’applicazione sul pattern. public partial class Page : UserControl
{
//Riferimenti
public XDocument xmlDefGerarchie;
public XDocument xmlImpiantoCorrente;
public XDocument xmlParsCorrenti;
public XDocument xmlTabsFieldsetCorrenti;
public string ASCXCorrente;
public List<Parametro> lista;
public List<Personalizzazione> listaPersonalizzazioniPerASCXCorrente;
public string OrigtitoloParametri;
//Variabili per la gestione del drag & drop
public bool isMouseCaptured;
public double mouseVerticalPosition;
public double mouseHorizontalPosition;
//struttura contenente una configurazione (quella corrente)
public ConfigurazioneASCX ConfigurazioneCorrente;
public Personalizzazione PersonalizzazioneCorrente;
public
EventHandler<ManagerParametriDF.ServiceControls.ModalDialogClosedEventArgs>
delegatoChiusuraPopUp;
ServiceControls.ModalDialog md;
//riferimento al dom
HtmlDocument _doc;
public string rootPathMultimediaSource = "/MultimediaContent/";
public Page()
{
InitializeComponent();
_doc = HtmlPage.Document;
//risoluzione caption in lingua pulsanti
this.btnAggiungiPers.Content
=
ManagerConst.getCaptionByLanguageId(ManagerConst.PageBtnAggiungiPersCaption) as
string;
this.btnEliminaPers.Content
=
ManagerConst.getCaptionByLanguageId(ManagerConst.PageBtnEliminaPersCaption)
as
string;
this.btnPreviewDefault.Content
=
ManagerConst.getCaptionByLanguageId(ManagerConst.PageBtnPreviewDefaultCaption)
as string;
//aggancio evento per la gestione del fullscreen
SilverlightHost host = Application.Current.Host;
host.Content.FullScreenChanged
+=
EventHandler(this.FullScreenChanged);
//salvo l'originale per poter cambiare la caption
visualizzazione dei parametri di default
OrigtitoloParametri = this.titParametri.Text;
PulisciParPers();
lista = new List<Parametro>();
}
new
in
caso
di
void Page_Loaded(object sender, RoutedEventArgs e)
{//appena è stata caricata la form lancio la modale di attesa
//per aspettare la risposta asincrona del caricamento vocabolario
Manager.WaitingFormOn();
}
private void FullScreenChanged(object sender, EventArgs e)
{
}
/// <summary>
/// Pulsante Load/Reload
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnLoad_Click(object sender, RoutedEventArgs e)
{
string PostUrl = "http://******/listenerDF.aspx?op=getDefGer";
//PostUrl = PostUrl.Replace("******",Manager.ip);
// Chiamata asincrona al web service attraverso il listener
WebClient DFService = new WebClient();
DFService.DownloadStringCompleted
+=
new
DownloadStringCompletedEventHandler(DFService_DownloadDefGerarchieCompleted);
DFService.DownloadStringAsync(Manager.getPostAddress(PostUrl));
PulisciParametriEDettaglio();
//this.LayoutRoot.Opacity = 0.5;
Manager.WaitingFormOn();
}
private void PulisciParametriEDettaglio()
{
PulisciDettaglio();
this.ParametriDefaultList.ItemsSource = null;
this.ASCXPersParList.ItemsSource = null;
}
private void PulisciDettaglio()
{
this.txtNome.Text = "";
this.txtTipo.Text = "";
this.txtDefault.Text = "";
}
private void PulisciParPers()
{
this.txtValore.Text = "";
this.txtValore.IsReadOnly = false;
this.cbxEreditato.IsChecked = false;
this.btnAggiungiParam.IsEnabled = false;
this.btnEliminaParam.IsEnabled = false;
this.btnSalvaParam.IsEnabled = false;
}
//callBack chiamata al carica defGerarchie
void
DFService_DownloadDefGerarchieCompleted(object
DownloadStringCompletedEventArgs e)
{
if (e.Error == null)
DisplayASCX(e.Result);
//this.LayoutRoot.Opacity = 1;
Manager.WaitingFormOff();
}
sender,
/// <summary>
/// Rilettura di xmlDefGerarchie
/// </summary>
/// <param name="xmlContent"></param>
private void DisplayASCX(string xmlContent)
{
xmlContent
=
xmlContent.Replace("xmlns=\"http://tempuri.org/StrutturaGerarchieFinale.xsd\"",
"");
xmlDefGerarchie = XDocument.Parse(xmlContent);
ReLoadASCXList();
}
/// <summary>
/// funzione per ricaricare la lista degli ASCX
defGerarchie
/// </summary>
private void ReLoadASCXList()
{
//svuoto e riassegno la source alla lista ASCX
this.ASCXList.ItemsSource = null;
this.ASCXList.SelectedIndex = -1;
this.ASCXList.ItemsSource = getListaASCX();
}
/// <summary>
a
partire
da
/// Metodo che ricostruisce una lista di oggetti gerarchie a fronte di
una rilettura di XmlDefGerarchie
/// </summary>
/// <returns></returns>
private List<ASCXDetail> getListaASCX()
{
ASCXDetail gerarchia;
Parametro parametro;
List<ASCXDetail> lista = new List<ASCXDetail>();
var
itemsGerarchie
=
from
ASCX
in
xmlDefGerarchie.Root.Element("RecordsetGerarchie").Elements("Gerarchia")
select ASCX;
//ricostruzione della classe defGerarchie
foreach (XElement ger in itemsGerarchie)
{
gerarchia = new ASCXDetail();
gerarchia.ModID = (string)ger.Attribute("ModID").Value;
gerarchia.ClassID = (string)ger.Attribute("ClassID").Value;
gerarchia.GerID = (string)ger.Attribute("GerID").Value;
gerarchia.GerCod = (string)ger.Attribute("GerCod").Value;
gerarchia.GerDes = (string)ger.Attribute("GerDes").Value;
//creazione dell'insieme parametri
foreach
(XElement
xPar
in
ger.Element("InsiemeParametri").Elements("Pa"))
{
parametro = new Parametro();
parametro.ParCod = (string)xPar.Attribute("ParCod").Value;
parametro.Vers = (string)xPar.Attribute("Vers").Value;
parametro.Default = (string)xPar.Attribute("Default").Value;
parametro.Ereditato = false;
gerarchia.Parametri.Add(parametro);
}
lista.Add(gerarchia);
}
} return lista;
Nel codice è stato messo in evidenza l’assegnamento da codice al componente ListBox della lista aggiornata delle gerarchie ASCX. Ogni volta che tale lista viene modificata deve essere aggiornato manualmente il contenuto della proprietà ItemsSource dell’oggetto ListBox. In seguito all’utilizzo del pattern MVC, nel Model sono stati introdotti i dati su cui i componenti UI della vista fanno binding (evitando così di modificare da codice le proprietà dei componenti in seguito all’aggiornamento dei dati che la vista deve visualizzare); inoltre, ogni volta che l’utente interagisce con un componente viene scatenato un evento, che viene inviato e catturato dal componente Controller. Tale componente controlla se l’evento è già stato catalogato e, se la ricerca va a buon fine, provvede a richiamare il metodo execute() del comando associato all’evento, consentendo così l’esecuzione delle operazioni necessarie a soddisfare la richiesta dell’utente. Per quel che riguarda la classe Page, l’introduzione del pattern fa diminuire notevolmente il numero di righe di codice, rendendo quest’ultimo più ordinato e leggibile, poiché le operazioni di elaborazione vengono spostate all’interno dei comandi associati agli eventi; inoltre questo consente un maggior riuso del codice da parte dei programmatori. 6.3.1
SEZIONE ASCX All’interno di tale sezione viene visualizzata la lista delle gerarchie ASCX. La sezione si presenta graficamente come una Grid all’interno della quale è presente un componente di tipo TextBox (ossia una label) ed un oggetto di tipo ListBox che fa binding sull’oggetto ASCXList presente all’interno della classe DFModel. <Grid Background="LightGray" Grid.Row="0" Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock
Text="ASCX"
Margin="5"
Grid.Row="0"
Style="{StaticResource TitleBlock}"/>
<ListBox
x:Name="ASCXList"
Background="#FFDEDEDE"
Grid.Row="1"
SelectionChanged="ASCXList_SelectionChanged"
ItemsSource="{Binding Path=ASCXList}" SelectedIndex="{Binding
Path=ASCXSelectedIndex, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="200"></ColumnDefinition>
<ColumnDefinition
Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- Lista ASCX-->
<TextBlock
Text="{Binding
Path=GerCod}"
Margin="5" Grid.Row="0" Grid.Column="0" Style="{StaticResource ContentBlock}"/>
<TextBlock
Text="{Binding
Path=GerDes}"
Margin="5" Grid.Row="0" Grid.Column="1" Style="{StaticResource ContentBlock}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid> Osservando il codice, si può notare che ora la proprietà ItemsSource dell’oggetto ListBox non viene più impostata da codice in fase di esecuzione, ma viene settata già all’interno del markup specificando l’oggetto del Model (cioè l’ASCXList) dal quale devono essere recuperati i dati che devono essere visualizzati. Quando l’utente interagisce con l’interfaccia premendo il pulsante Load/Reload, viene eseguito il metodo btnLoad_Click associato all’evento onClick del pulsante: private void btnLoad_Click(object sender, RoutedEventArgs e)
{
List<object> param = new List<object>();
param.Add(this.txtNome);
param.Add(this.txtTipo);
param.Add(this.txtDefault);
param.Add(this.txtValore);
param.Add(this.cbxEreditato);
param.Add(this.btnAggiungiParam);
param.Add(this.btnEliminaParam);
param.Add(this.btnSalvaParam);
ASCXLoadEvent ascxEvent = new ASCXLoadEvent("ASCXLoad", param);
ascxEvent.dispatch();
} All’interno del metodo viene creato un evento di tipo ASCXLoadEvent, passandogli una lista di oggetti che verranno utilizzati per l’esecuzione delle elaborazioni all’interno del comando di tipo ASCXLoadCommand associato all’evento; successivamente l’evento viene inviato attraverso il metodo dispatch(). Tale evento verrà catturato dal FrontController il quale, dopo aver verificato che l’evento sia stato precedentemente registrato, andrà a richiamare il metodo execute() del comando. /// Whenever the GenericEventDispatcher raises an event, this
/// method gets the GenericEventArgs inside it - and calls
/// the execute() method on the corresponding ICommand
void ExecuteCommand(object sender, GenericEventArgs args)
{
if (eventMap.ContainsKey(args.raisedEvent.Name))
{
eventMap[args.raisedEvent.Name].execute(args.raisedEvent);
}
} All’interno del metodo execute() della classe ASCXLoadCommand, la prima cosa che viene fatta è recuperare gli oggetti che erano stati inseriti nella lista che era stata passata come parametro al momento della creazione dell’evento di business. Dopo aver fatto ciò, poiché è necessario effettuare una chiamata asincrona al Web Service, viene creato un oggetto Delegate di tipo ASCXLoadDelegate e richiamato il suo metodo SendRequest(). Si ricorda che i Delegate sono gli unici componenti che, all’interno della metodologia di sviluppo introdotta con il Pattern MVC, possono effettuare chiamate a web service o a web server. public class ASCXLoadDelegate : GenericDelegate
{
public ASCXLoadDelegate(IResponder responder)
: base(responder)
{
}
public void SendRequest()
{
string PostUrl = "http://******/listenerDF.aspx?op=getDefGer";
//PostUrl = PostUrl.Replace("******",Manager.ip);
// Chiamata asincrona al web service attraverso il listener
WebClient DFService = new WebClient();
DFService.DownloadStringCompleted
+=
new
DownloadStringCompletedEventHandler(DFService_DownloadDefGerarchieCompleted);
DFService.DownloadStringAsync(Manager.getPostAddress(PostUrl));
}
void
DFService_DownloadDefGerarchieCompleted(object
sender,
DownloadStringCompletedEventArgs e)
{
if (null != e.Error)
responder.onFault("Exception! (" + e.Error.Message + ")");
else
responder.onResult(e.Result);
}
} La classe in questione eredita dalla classe GenericDelegate definita all’interno della libreria SilverlightPattern importata nel progetto. In particolare all’interno del metodo SendRequest() viene creato un oggetto WebClient, che rappresenta il proxy per la chiamata asincrona al web service. Poiché la chiamata è asincrona, dopo che essa è stata effettuata l’applicativo può proseguire nell’esecuzione delle proprie operazioni. Quando il web service ha terminato la propria elaborazione, invia al chiamante i risultati; in tal caso viene così richiamato il metodo (DFService_DownloadDefGerarchieCompleted) associato all’evento di completamento della chiamata. Nel caso in cui si sia verificato un errore nell’elaborazione dei dati ad opera del web service, il metodo in questione chiama il metodo onFault() della classe ASCXLoadCommand per poter gestire tale situazione, in caso contrario esso provvede a richiamare il metodo onResult() della classe medesima passandogli i dati ritornati dalla chiamata. Nel caso in questione i dati ritornati dalla chiamata asincrona rappresentano la lista delle gerarchie ASCX e sono contenuti all’interno di un file xml, del quale viene fatto il parsing per poter recuperare i dati. public class ASCXLoadCommand : SilverlightPattern.Business.Command.ICommand,
IResponder
{
public XDocument xmlDefGerarchie;
private DFModel model = DFModel.getInstance();
TextBlock txtNome, txtTipo, txtDefault;
TextBox txtValore;
CheckBox cbxEreditato;
Button btnAggiungiParam, btnEliminaParam, btnSalvaParam;
#region ICommand Members
public void execute(GenericEvent genericEvent)
{
if (genericEvent.Params[0] is TextBlock)
txtNome = (genericEvent.Params[0] as TextBlock);
if (genericEvent.Params[1] is TextBlock)
txtTipo = (genericEvent.Params[1] as TextBlock);
if (genericEvent.Params[2] is TextBlock)
txtDefault = (genericEvent.Params[2] as TextBlock);
if (genericEvent.Params[3] is TextBox)
txtValore = (genericEvent.Params[3] as TextBox);
if (genericEvent.Params[4] is CheckBox)
cbxEreditato = (genericEvent.Params[4] as CheckBox);
if (genericEvent.Params[5] is Button)
btnAggiungiParam = (genericEvent.Params[5] as Button);
if (genericEvent.Params[6] is Button)
btnEliminaParam = (genericEvent.Params[6] as Button);
if (genericEvent.Params[7] is Button)
btnSalvaParam = (genericEvent.Params[7] as Button);
//begin talk to web service
ASCXLoadDelegate ascxDelegate = new ASCXLoadDelegate(this);
ascxDelegate.SendRequest();
PulisciParametriEDettaglio();
Manager.WaitingFormOn();
}
#endregion
#region IResponder Members
public void onResult(object result)
{
DisplayASCX((string)result);
Manager.WaitingFormOff();
}
public void onFault(string errorMessage)
{
}
#endregion
private void PulisciParametriEDettaglio()
{
PulisciDettaglio();
PulisciParPers();
model.ParametriDefaultList= null;
model.ASCXPersParList= null;
model.ASCXPersList = null;
}
private void PulisciDettaglio()
{
this.txtNome.Text = "";
this.txtTipo.Text = "";
this.txtDefault.Text = "";
}
private void PulisciParPers()
{
this.txtValore.Text = "";
this.txtValore.IsReadOnly = false;
this.cbxEreditato.IsChecked = false;
this.btnAggiungiParam.IsEnabled = false;
this.btnEliminaParam.IsEnabled = false;
this.btnSalvaParam.IsEnabled = false;
}
private void DisplayASCX(string xmlContent)
{
xmlContent
=
xmlContent.Replace("xmlns=\"http://tempuri.org/StrutturaGerarchieFinale.xsd\"",
"");
xmlDefGerarchie = XDocument.Parse(xmlContent);
ReLoadASCXList();
}
private void ReLoadASCXList()
{
//svuoto e riassegno la source alla lista ASCX
model.ASCXList = null;
model.ASCXSelectedIndex = -1;
model.ASCXList = getListaASCX();
}
private List<ASCXDetail> getListaASCX()
{
ASCXDetail gerarchia;
Parametro parametro;
List<ASCXDetail> lista = new List<ASCXDetail>();
var
itemsGerarchie
=
from
ASCX
in
xmlDefGerarchie.Root.Element("RecordsetGerarchie").Elements("Gerarchia")
select ASCX;
//ricostruzione della classe defGerarchie
foreach (XElement ger in itemsGerarchie)
{
gerarchia = new ASCXDetail();
gerarchia.ModID = (string)ger.Attribute("ModID").Value;
gerarchia.ClassID = (string)ger.Attribute("ClassID").Value;
gerarchia.GerID = (string)ger.Attribute("GerID").Value;
gerarchia.GerCod = (string)ger.Attribute("GerCod").Value;
gerarchia.GerDes = (string)ger.Attribute("GerDes").Value;
//creazione dell'insieme parametri
foreach
(XElement
xPar
in
ger.Element("InsiemeParametri").Elements("Pa"))
{
parametro = new Parametro();
parametro.ParCod = (string)xPar.Attribute("ParCod").Value;
parametro.Vers = (string)xPar.Attribute("Vers").Value;
parametro.Default = (string)xPar.Attribute("Default").Value;
parametro.Ereditato = false;
gerarchia.Parametri.Add(parametro);
}
lista.Add(gerarchia);
}
return lista;
}
} Le elaborazioni effettuate dal comando hanno l’obiettivo di modificare i dati contenuti nel Model, e precisamente l’oggetto ASCXList che conterrà la lista delle gerarchie ASCX sulla quale fa binding l’oggetto ListBox citato in precedenza. Come risultato dunque del click del mouse sul pulsante Load/Reload viene visualizzata a video, all’interno della sezione ASCX, la lista delle gerarchie ASCX. Figura 37 – Lista delle gerarchie ASCX 6.3.2
PARAMETRI DEFAULT GERARCHIE Tale sezione ha il compito di visualizzare quelli che sono i parametri di default della gerarchia selezionata all’interno della sezione vista in precedenza. Anche tale sezione, come la sezione precedente, è caratterizzata per quel che riguarda l’xaml da una TextBlock, che contiene il titolo della sezione, e da un oggetto di tipo ListBox che stavolta contiene i parametri di default. In aggiunta stavolta si ha la presenza di un oggetto di tipo Button che quando viene premuto mostra una Preview della configurazione ASCX di default selezionata. <Grid Background="LightGray" Grid.Row="0" Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<Grid x:Name="pulsantierad" Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button x:Name="btnPreviewDefault" Content="PW" Grid.Column="0"
Click="btn_Preview_Default_click"></Button>
</Grid>
<TextBlock
Text="Parametri
Default
DefGerarchie"
Margin="5"
Grid.Row="0" Style="{StaticResource TitleBlock}"/>
<ListBox
x:Name="ParametriDefaultList"
Background="#FFDEDEDE"
Grid.Row="1" SelectionChanged="ParametriDefaultList_SelectionChanged"
ItemsSource="{Binding
Path=ParametriDefaultList}"
SelectedIndex="{Binding Path=ParametriDefaultSelectedIndex, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="200"></ColumnDefinition>
<ColumnDefinition
Width="100"></ColumnDefinition>
<ColumnDefinition
Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- Lista ASCX-->
<TextBlock Text="{Binding ParCod}" Margin="5"
Grid.Row="0" Grid.Column="0" Style="{StaticResource ContentBlock}"/>
<TextBlock
Text="{Binding
Vers}"
Margin="5"
Grid.Row="0" Grid.Column="1" Style="{StaticResource ContentBlock}"/>
<TextBlock Text="{Binding Default}" Margin="5"
Grid.Row="0"
Grid.Column="2"
Foreground="red"
Style="{StaticResource
ContentBlock}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid> L’oggetto di tipo ListBox evidenziato nel codice precedente fa binding sull’oggetto ParametriDefaultList, che è una lista di oggetti di tipo Parametro contenuta nella classe DFModel, attraverso la proprietà ItemsSource. Quando all’interno della Sezione ASCX viene selezionata una gerarchia, viene scatenato un evento SelectionChanged che viene catturato dal corrispondente gestore, che richiama un metodo chiamato ASCXList_SelectionChanged. All’interno del metodo viene creata una lista contenente gli oggetti dell’interfaccia che devono essere acceduti in lettura e/o scrittura durante l’elaborazione del comando. Successivamente viene creato un evento appartenente alla classe ImpiantoLoadEvent, passandogli come parametro la lista di oggetti, e viene inviato attraverso l’operazione di dispatch. private void ASCXList_SelectionChanged(object sender, SelectionChangedEventArgs
e)
{
DFModel.getInstance().ASCXSelectedIndex=this.ASCXList.SelectedIndex;
List<object> param = new List<object>();
param.Add(this.txtNome);
param.Add(this.txtTipo);
param.Add(this.txtDefault);
param.Add(this.txtValore);
param.Add(this.cbxEreditato);
param.Add(this.btnAggiungiParam);
param.Add(this.btnEliminaParam);
param.Add(this.btnSalvaParam);
ImpiantoLoadEvent impEvent = new ImpiantoLoadEvent("ImpiantoLoad",
param);
} impEvent.dispatch();
Dopo essere stato lanciato, l’evento di business creato viene catturato dal Controller che verifica se l’evento è presente all’interno della sua lista di coppie evento‐
comando e, se il controllo va a buon fine, manda in esecuzione il comando corrispondente, cioè il comando ImpiantoLoadCommand. All’interno del metodo execute() relativo al comando, vengono innanzitutto recuperati gli oggetti dell’UI passati in una lista al momento della creazione dell’evento di business. Successivamente viene assegnato l’oggetto ParametriDefaultList, contenuto nel Model, sul quale fa binding la ListBox che visualizza i parametri di default della gerarchia selezionata. Poi, ricorrendo alla creazione di un Delegate appartenente alla classe ImpiantoLoadDelegate, viene inviata una richiesta al web service. public class ImpiantoLoadDelegate : GenericDelegate
{
public ImpiantoLoadDelegate(IResponder responder)
: base(responder)
{
}
public void SendRequest(string gerCod)
{
string PostUrl = "http://******/listenerDF.aspx?op=getImp&par="
+
gerCod;
WebClient DFService = new WebClient();
DFService.DownloadStringCompleted
+=
DownloadStringCompletedEventHandler(DFService_DownloadImpiantoCompleted);
DFService.DownloadStringAsync(Manager.getPostAddress(PostUrl));
}
new
//callBack chiamata al carica impianto
void
DFService_DownloadImpiantoCompleted(object
sender,
DownloadStringCompletedEventArgs e)
{
if (null != e.Error)
responder.onFault("Exception! (" + e.Error.Message + ")");
else
responder.onResult(e.Result);
}
} Il Delegate, che eredita dalla classe GenericDelegate, attraverso l’oggetto WebClient effettua una chiamata asincrona al web service chiedendogli di restituirgli in output l’impianto corrispondente alla chiave (il codice della gerarchia selezionata) passata in input al web service. Quando i risultati sono inviati al chiamante, ossia al Delegate, questo richiama il metodo onResult() del comando passandogli i risultati stessi, in maniera tale che questi possano essere in un qualche modo elaborati. Se si è verificato un errore e per un qualche motivo i risultati non sono disponibili al Delegate al ritorno della chiamata, viene mandato in esecuzione il metodo onFault() del comando che provvederà a gestire tale situazione anomala. public class ImpiantoLoadCommand : SilverlightPattern.Business.Command.ICommand,
IResponder
{
private DFModel model = DFModel.getInstance();
public string ASCXCorrente;
public List<Personalizzazione> listaPersonalizzazioniPerASCXCorrente;
public XDocument xmlImpiantoCorrente;
TextBlock txtNome, txtTipo, txtDefault;
TextBox txtValore;
CheckBox cbxEreditato;
Button btnAggiungiParam, btnEliminaParam, btnSalvaParam;
#region ICommand Members
public void execute(GenericEvent genericEvent)
{
if (genericEvent.Params[0] is TextBlock)
txtNome = (genericEvent.Params[0] as TextBlock);
if (genericEvent.Params[1] is TextBlock)
txtTipo = (genericEvent.Params[1] as TextBlock);
if (genericEvent.Params[2] is TextBlock)
txtDefault = (genericEvent.Params[2] as TextBlock);
if (genericEvent.Params[3] is TextBox)
txtValore = (genericEvent.Params[3] as TextBox);
if (genericEvent.Params[4] is CheckBox)
cbxEreditato = (genericEvent.Params[4] as CheckBox);
if (genericEvent.Params[5] is Button)
btnAggiungiParam = (genericEvent.Params[5] as Button);
if (genericEvent.Params[6] is Button)
btnEliminaParam = (genericEvent.Params[6] as Button);
if (genericEvent.Params[7] is Button)
btnSalvaParam = (genericEvent.Params[7] as Button);
ASCXDetail gerarchia ;
if (model.ASCXSelectedIndex == -1)
gerarchia = null;
else
gerarchia = model.ASCXList[model.ASCXSelectedIndex];
if (gerarchia != null)
{
Manager.WaitingFormOn();
model.ParametriDefaultList = gerarchia.Parametri;
PulisciDettaglio();
PulisciParPers();
model.ASCXPersParList = null;
ASCXCorrente = gerarchia.GerCod;
//begin talk to web service
ImpiantoLoadDelegate
impDelegate
=
ImpiantoLoadDelegate(this);
impDelegate.SendRequest(gerarchia.GerCod);
}
}
#endregion
#region IResponder Members
public void onResult(object result)
{
DisplayPersASCX((string)result);
Manager.WaitingFormOff();
}
public void onFault(string errorMessage)
{
Manager.WaitingFormOff();
model.ASCXPersList = null;
model.ASCXPersSelectedIndex = -1;
Manager.msgBox("Network Error: " + errorMessage);
Manager.WaitingFormOff();
}
#endregion
private void PulisciDettaglio()
{
this.txtNome.Text = "";
this.txtTipo.Text = "";
this.txtDefault.Text = "";
}
private void PulisciParPers()
{
this.txtValore.Text = "";
this.txtValore.IsReadOnly = false;
this.cbxEreditato.IsChecked = false;
this.btnAggiungiParam.IsEnabled = false;
this.btnEliminaParam.IsEnabled = false;
this.btnSalvaParam.IsEnabled = false;
}
private void DisplayPersASCX(string xmlContent)
{
new
xmlContent
=
xmlContent.Replace("xmlns=\"http://tempuri.org/StrutturaGerarchieFinale.xsd\"",
"");
xmlImpiantoCorrente = XDocument.Parse(xmlContent);
Manager.setCurrentPathASCX(xmlImpiantoCorrente);
ReLoadPersASCXList();
}
private void ReLoadPersASCXList()
{
//svuoto e riassegno la source alla lista PersASCX
model.ASCXPersList = null;
model.ASCXPersSelectedIndex = -1;
model.ASCXPersList = (List<Personalizzazione>)getListaPersASCX();
}
///
Metodo
per
la
ricostruzione
di
una
lista
personalizzazione dell'ascx corrente
private System.Collections.IEnumerable getListaPersASCX()
{
di
oggetti
Personalizzazione pers;
List<Personalizzazione> lista = new List<Personalizzazione>();
var
itemsPers
=
from
PersASCX
xmlImpiantoCorrente.Root.Element("Lv").Elements("Lv")
select PersASCX;
////ricostruzione della classe
foreach (XElement per in itemsPers)
{
pers = new Personalizzazione();
pers.GerCod = ASCXCorrente;
pers.OggCod = (string)per.Attribute("OggCod").Value;
lista.Add(pers);
}
in
listaPersonalizzazioniPerASCXCorrente = lista;
return lista;
}
} La classe ImpiantoLoadCommand, come anche tutti le altre classi che svolgono il ruolo di comandi, deve implementare due interfacce della libreria di classi SilverlightPattern importata nel progetto; si tratta dell’interfaccia ICommand e dell’interfaccia IResponder riportate sotto: public interface ICommand
{
void execute(GenericEvent genericEvent);
} //The Responder interface is implemented by classes that wish to handle
//data returned as the result of a service-call to the server.
public interface IResponder
{
void onResult(object result);
void onFault(string errorMessage);
} Le due interfacce in questione, in linea con i principi della programmazione ad oggetti, vincolano la classe ad implementare i loro metodi; in particolare il metodo execute() dell’interfaccia ICommand e i metodi onResult() e onFault() dell’interfaccia IResponder. Per quel che riguarda il metodo onResult(), al suo interno viene eseguito il metodo DisplayPersASCX() che effettuerà il parsing dei risultati ritornati dal web service ed andrà a salvare all’interno del Model la lista delle personalizzazioni per l’ascx selezionato. Quando un utente seleziona sull’interfaccia una gerarchia ASCX, all’interno della Sezione Parametri Default Gerarchie verrà visualizzata la lista dei parametri di default per la gerarchia selezionata. Figura 38 – Lista di parametri a fronte della selezione di un ascx 6.3.3
PERSONALIZZAZIONI ASCX All’interno della suddetta sezione viene visualizzata la lista delle personalizzazioni a fronte della selezione di una particolare gerarchia ASCX all’interno della Sezione ASCX. Per quel che riguarda la struttura dell’xaml, la sezione si compone di un oggetto Grid all’interno del quale sono presenti un oggetto TextBlock che contiene il titolo della sezione, un oggetto di tipo ListBox per poter visualizzare la lista delle personalizzazioni per un ascx, e due componenti Button per poter aggiungere e cancellare una personalizzazione dalla lista delle personalizzazioni. <Grid Background="LightGray" Grid.Row="1" Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<Grid x:Name="pulsantiera" Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button x:Name="btnAggiungiPers" Content="Add" Grid.Column="0"
Click="btn_New_pers_click"></Button>
<Button x:Name="btnEliminaPers" Content="Del" Grid.Column="1"
Click="btn_Del_pers_click"></Button>
</Grid>
<TextBlock Text="Personalizzazioni ASCX" Margin="5" Grid.Row="0"
Style="{StaticResource TitleBlock}"/>
<ListBox x:Name="ASCXPersList" Background="#FFDEDEDE" Grid.Row="1"
SelectionChanged="ASCXPersList_SelectionChanged"
ItemsSource="{Binding
Path=ASCXPersList}"
SelectedIndex="{Binding Path=ASCXPersSelectedIndex, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="200"></ColumnDefinition>
<ColumnDefinition
Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- Lista ASCX-->
<TextBlock Text="{Binding GerCod}" Margin="5"
Grid.Row="0" Grid.Column="0" Style="{StaticResource ContentBlock}"/>
<TextBlock Text="{Binding OggCod}" Margin="5"
Grid.Row="0" Grid.Column="1" Style="{StaticResource ContentBlock}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid> Attraverso la proprietà ItemsSource il componente ListBox specifica l’oggetto della classe DFModel su cui viene effettuato il binding, in maniera tale che qualunque modifica a tale oggetto si propaga alla vista, che può così fare il rendering dai dati aggiornati. L’oggetto in questione è la ASCXPersList, una variabile contenente una lista di oggetti Personalizzazione, che è stata assegnata in seguito all’elaborazione dei dati (l’impianto per una determinata configurazione ascx) ritornati dal web service alla classe ImpiantoLoadCommand analizzata nella sezione precedente. Dalla figura si evince che, dopo la selezione di un determinato ascx all’interno della Sezione ASCX, viene mostrata all’interno dell’oggetto ListBox la lista delle personalizzazioni attualmente disponibili per quell’ascx. Figura 39 – Lista di personalizzazioni a fronte della selezione di un ascx Quando un utente fa click sul pulsante Nuova Personalizzazione viene scatenato un evento che è catturato dal rispettivo handler, il quale manda in esecuzione il metodo btn_New_pers_click(). Internamente al metodo viene dapprima creata una lista di oggetti dell’UI che saranno acceduti durante il processo di elaborazione da parte di un comando, poi viene creato un evento di tipo AddPersEvent passandogli la lista come parametro, e infine viene inviato l’evento. Come al solito, seguendo le linee guida fornite dal Pattern MVC implementato, l’evento inviato viene catturato dalla classe DFController che ricerca l’evento all’interno del suo Dictionary e, se lo trova, manda in esecuzione il comando ad esso associato. Il comando che viene eseguito appartiene alla classe AddPersCommand e implementa le interfacce ICommand e IResponder. Quando viene mandato in esecuzione il suo metodo execute(), innanzitutto vengono recuperati gli oggetti dell’UI che devono essere acceduti in lettura e/o scrittura, poi viene creato un oggetto di tipo Popup che viene aggiunto, per una corretta visualizzazione del suo contenuto, all’oggetto LayoutRoot di tipo Grid presente all’interno della classe Page. Successivamente viene creato un oggetto ManagerNewConfiguration, che è utilizzato per gestire l’aggiunta di una personalizzazione e viene aperto in modale all’interno della popup precedentemente creata. La classe ModalDialog è quella che consente l’apertura in modale della popup attraverso i suoi metodi show(). La figura sottostante mostra una popup che consente l’aggiunta di una personalizzazione per l’ascx selezionato. Figura 40 – Finestra di aggiunta personalizzazione Per quel che riguarda la struttura del markup contenuto nel file xaml relativo alla classe, essa è costituita da una Grid principale che contiene tutti i componenti UI della classe, tra cui un oggetto di tipo TextBlock che contiene il nome dell’ascx selezionato, un oggetto di tipo TextBox che consente di inserire il nome della nuova personalizzazione, un oggetto CheckBox che consente di decidere se la nuova personalizzazione deve ereditare da una già esistente o se deve assumere la configurazione di default, un oggetto ListBox che contiene la lista delle personalizzazioni (o parametrizzazioni) correnti dell’ascx da cui è possibile ereditare, e infine un pulsante (Button) di chiusura della popup ed una immagine (utilizzata come pulsante grazie all’assegnamento del valore Hand alla proprietà Cursor) che consente il salvataggio dei dati e la chiusura della popup. <UserControl
x:Class="ManagerParametriDF.ServiceControls.ManagerNewConfiguration"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="500" Height="400" Loaded="UserControl_Loaded">
<Grid x:Name="layoutRoot" Background="Beige" ShowGridLines="False" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="25"></RowDefinition>
</Grid.RowDefinitions>
<ScrollViewer
Grid.Row="1"
Grid.ColumnSpan="2"
VerticalScrollBarVisibility="Auto" >
<Grid
x:Name="InternalLayoutRoot"
Background="Transparent"
ShowGridLines="False" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.6*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Text="ASCX :" VerticalAlignment="Center" Grid.Row="0"
Grid.Column="0" HorizontalAlignment="Right" ></TextBlock>
<TextBlock
x:Name="lblNome"
Text="Name
:"
VerticalAlignment="Center"
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Right" ></TextBlock>
<TextBlock
x:Name="lblNomeASCX"
Text="Griglia.ascx"
VerticalAlignment="Center"
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Center" ></TextBlock>
<TextBlock
x:Name="lblEredita"
Text="Inherit
from
:"
VerticalAlignment="Top" Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right"
></TextBlock>
<TextBox x:Name="txtNomePers" TextAlignment="Left" FontSize="12"
Text="" Margin="5" Grid.Row="1" Grid.Column="1"></TextBox>
<CheckBox
x:Name="cbxEredita"
Checked="cbxEredita_Checked"
Unchecked="cbxEredita_Unchecked"
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Top" HorizontalAlignment="Left" Margin="5"></CheckBox>
<ScrollViewer
Grid.Row="2"
Grid.Column="1"
Margin="5"
VerticalScrollBarVisibility="Auto">
<ListBox
x:Name="PersList"
ItemsSource="{Binding
Path=ASCXPersList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Height="20" Text="{Binding OggCod}"
Margin="5" Style="{StaticResource ContentBlock}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
</Grid>
</ScrollViewer>
<Rectangle Fill="Gray" Grid.Row="0" Grid.ColumnSpan="2"></Rectangle>
<Rectangle Fill="Gray" Grid.Row="2" Grid.ColumnSpan="2"></Rectangle>
<TextBlock
x:Name="lblTitle"
HorizontalAlignment="Left"
FontFamily="Currier"
Foreground="Gold"
Grid.Column="0"
Grid.Row="0"
FontSize="12" VerticalAlignment="Center" Text="Manager New Personalization :
"></TextBlock>
<Image Height="20" Width="20" Cursor="Hand" HorizontalAlignment="Right"
Margin="1,1,15,1" MouseLeftButtonUp="Save_ConfSta" Grid.Column="1" Grid.Row="2"
Source="../../FakeControls/Resource/Salva.png"></Image>
<Button
x:Name="btnClose"
Cursor="Hand"
Click="CloseBtnSta_Click"
Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Top"
Content="Close" Style="{StaticResource DFCloseButton}"/>
</Grid>
</UserControl> Dopo che l’utente ha inserito il nome della nuova personalizzazione e deciso se essa deve ereditare da una personalizzazione già esistente o se deve assumere la configurazione di default, se decide di salvare facendo click sull’immagine di salvataggio, viene scatenato l’evento click. Tale evento viene catturato dal suo gestore, il quale manda in esecuzione il metodo Save_ConfSta(). All’interno di tale metodo viene per prima cosa creata una lista di componenti UI a cui accederà il comando che verrà mandato in esecuzione per effettuare le sue elaborazioni. Tale lista viene passata come parametro di input al costruttore della classe SaveConfigEvent che permette la creazione dell’evento; successivamente tale evento sarà inviato tramite il metodo dispatch() e catturato dal Controller appartenente al Business layer. Viene dunque eseguito il metodo execute() del comando SaveConfigCommand associato all’evento. private void Save_ConfSta(object sender, MouseButtonEventArgs e)
{
List<object> param = new List<object>();
param.Add(this.MDContainer);
param.Add(this.TargetASCX);
param.Add(this.txtNomePers);
param.Add(this.cbxEredita);
param.Add(this.PersList);
SaveConfigEvent scEvent = new SaveConfigEvent("SaveConfig", param);
scEvent.dispatch();
}
All’interno del metodo execute() del comando, dopo aver recuperato i componenti dell’user interface, vengono effettuati tutti i controlli di rito per verificare se ad esempio non è stato impostato un nome per la nuova personalizzazione, o se il nome immesso è già presente, oppure se è indicata una ereditarietà ma non è selezionata la personalizzazione da cui si eredita nella lista. public class SaveConfigCommand : SilverlightPattern.Business.Command.ICommand,
IResponder
{
DFModel model = DFModel.getInstance();
ModalDialog MDContainer;
string TargetASCX;
TextBox txtNomePers;
CheckBox cbxEredita;
ListBox PersList;
#region ICommand Membri di
public void execute(GenericEvent genericEvent)
{
foreach (object param in genericEvent.Params)
{
if (param is ModalDialog)
MDContainer = (param as ModalDialog);
if (param is string)
TargetASCX = (param as string);
if (param is TextBox)
txtNomePers = (param as TextBox);
if (param is CheckBox)
cbxEredita = (param as CheckBox);
if (param is ListBox)
PersList = (param as ListBox);
}
NewConfigDetail resp = new NewConfigDetail();
resp.ASCX = this.TargetASCX;
//se non è impostato un nome per la nuova personalizzazione esci
if (string.IsNullOrEmpty(this.txtNomePers.Text))
return;
resp.NewPersName = this.txtNomePers.Text;
//se il nome immesso è già presente esci
if (isPresent(this.txtNomePers.Text))
return;
if ((bool)this.cbxEredita.IsChecked)
{
resp.Ereditato = true;
if (this.PersList.SelectedIndex == -1)
return;//se è indicata una ereditarietà ma non è selezionato
in lista il padre esci
else
resp.EreditaDa
=
(model.ASCXPersList[this.PersList.SelectedIndex] as Personalizzazione).OggCod;
}
//chiude modale e restituisce la risposta
this.MDContainer.Close(wDialogResult.OK, resp);
}
#endregion
}
Dopo aver fatto ciò, viene chiamato il metodo Close() di chiusura della modale, passandogli come parametro una istanza della classe NewConfigDetail che contiene i dettagli della nuova configurazione che si vuole salvare. public class NewConfigDetail
{
public NewConfigDetail()
{
this.Ereditato = false;
}
public bool Ereditato { get; set; }
public string ASCX { get; set; }
public string NewPersName { get; set; }
public string EreditaDa { get; set; }
} All’interno del metodo Close() della classe ModalDialog, oltre all’esecuzione delle istruzioni per la chiusura della finestra popup, viene generato un evento closed il cui gestore delegatoChiusuraPopUp è definito all’interno della classe AddPersCommand citata in precedenza. public void Close(wDialogResult result, object data)
{
backgroundPopup.IsOpen = false;
contentPopup.IsOpen = false;
backgroundPopup = null;
contentPopup = null;
appRootVisual.SizeChanged -= new SizeChangedEventHandler(AppSizeChanged);
appRootVisual = null;
ModalDialogClosedEventArgs
e
=
new
ModalDialogClosedEventArgs(result,
data);
} if (Closed != null)
{
Closed(null, e);
}
Il delegato richiama così il metodo ModalDialog_NewConf_Closed(), il quale rimuove dapprima l’handler per l’evento di chiusura della popup e poi va a salvare sul backend i dettagli della nuova personalizzazione attraverso il metodo SalvaInBackend() della classe ManagerSave. Tale metodo riceve come parametri in ingresso il comando da eseguire (in tal caso ‘salvaNuovaPersonalizzazione’), i dati da inviare e la funzione di callback, poi invia i dati aggiornati al server utilizzando il metodo Execute() della classe HttpHelper che consente una comunicazione asincrona tra client e server. public class ManagerSave
{
public
static
void
SalvaInBackend(string
comando,
string
HttpResponseCompleteEventHandler callBack)
{
HttpHelper
helper
=
HttpHelper(Manager.getPostAddressHttpRequest(Manager.URL_Saver), "POST",
new KeyValuePair<string, string>("cmd", comando),
new KeyValuePair<string, string>("msg", dati));
helper.ResponseComplete
HttpResponseCompleteEventHandler(callBack);
helper.Execute();
}
+=
dati,
new
new
public static void SalvaInBackend(string comando, string dati, string
dati1, HttpResponseCompleteEventHandler callBack)
{
HttpHelper
helper
=
new
HttpHelper(Manager.getPostAddressHttpRequest(Manager.URL_Saver), "POST",
new KeyValuePair<string, string>("cmd", comando),
new KeyValuePair<string, string>("msg", dati),
new KeyValuePair<string, string>("msg1", dati1));
helper.ResponseComplete
HttpResponseCompleteEventHandler(callBack);
helper.Execute();
}
} +=
new
Quando il controllo ritorna al chiamante, viene eseguita la funzione di callback CallBackNewPersonalization() che, se il salvataggio è andato a buon fine, effettua un riallineamento e aggiorna la lista delle personalizzazioni contenuta nel Model, in maniera tale che grazie al binding i cambiamenti siano immediatamente visibili sull’interfaccia grafica. Nel caso in cui il salvataggio non sia andato a buon fine, non viene eseguito il riallineamento ma viene visualizzata a video una finestra di alert. /// Evento asincrono di callback al salvataggio di una nuova personalizzazione
private void CallBackNewPersonalization(HttpResponseCompleteEventArgs e)
{
if (e.Response == "OK")
{//salvataggio andato a buon fine
//riallineamento
currentDispatcher.BeginInvoke(delegate()
{
LoadASCXPersonalization();
//comunque per sicurezza cancello le strutture in locale e
obbligo una rilettura in backend e una ricostruzione
Manager.setCurrentConfig(null);
Manager.setTabsFieldsetsCurrentConfig(null);
Manager.msgBox(ManagerConst.getCaptionByLanguageId(ManagerConst.msgSalvataggioOK
));
Manager.WaitingFormOff();
});
}
else
{//se il salvataggio non è andato a buon fine visualizzo
//un alert e non riallineo e cancello le strutture in modo tale
che rieffettuando
//una preview mantengo ancora vive le modifiche e si può
ritentare il salvataggio...
currentDispatcher.BeginInvoke(delegate()
{
Manager.msgBox(ManagerConst.getCaptionByLanguageId(ManagerConst.msgSalvataggioKO
) + e.Response);
Manager.WaitingFormOff();
});
}
} All’interno della sezione Personalizzazioni ASCX, oltre alla possibilità di aggiunta di una o più personalizzazioni, si offre agli utenti la possibilità di cancellare le personalizzazioni appartenenti alla gerarchia ASCX selezionata. Quando l’utente fa click sul pulsante ‘Cancella Personalizzazione’, viene eseguito l’evento click che viene catturato dal suo gestore. Quest’ultimo manda in esecuzione il metodo btn_Del_pers_click(), all’interno del quale come ormai noto viene creata la lista di componenti UI da utilizzare nel comando, viene creato un evento di tipo DelPersEvent passando al suo costruttore la lista, e infine viene inviato l’evento tramite il metodo dispatch(). L’evento anche questa volta viene catturato dalla classe DFController e viene mandato in esecuzione il corrispondente comando DelPersCommand. Quando viene mandato in esecuzione il suo metodo execute(), come al solito vengono recuperati gli oggetti dell’UI che devono essere acceduti in lettura e/o scrittura, poi viene creato un oggetto di tipo Popup che viene aggiunto, per una corretta visualizzazione del suo contenuto, all’oggetto LayoutRoot di tipo Grid presente all’interno della classe Page. Successivamente viene creato un oggetto ManagerDeleteConfiguration, utilizzato per consentire la cancellazione di una personalizzazione, che viene aperto in modale all’interno della popup precedentemente creata grazie al metodo show() della classe ModalDialog. In figura viene mostrato quello che l’utente visualizza dopo aver premuto il pulsante per la cancellazione di una personalizzazione. Figura 41 – Finestra di cancellazione personalizzazione La classe ManagerDeleteConfiguration è composta principalmente da un oggetto Grid che contiene al proprio interno un componente ListBox che fa binding, grazie alla proprietà ItemsSource, sull’oggetto ASCXPersList contenuto nella classe DFModel che contiene la lista degli oggetti di tipo Personalizzazione, un oggetto di tipo Button per la chiusura della finestra popup, e da un oggetto Image che permette il salvataggio dei dati e la chiusura della finestra di dialogo. <UserControl
x:Class="ManagerParametriDF.ServiceControls.ManagerDeleteConfiguration"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="500" Height="400" Loaded="UserControl_Loaded">
<Grid x:Name="LayoutRoot" Background="Beige" ShowGridLines="False" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="25"></RowDefinition>
</Grid.RowDefinitions>
<ScrollViewer
Grid.Row="1"
Grid.ColumnSpan="2"
VerticalScrollBarVisibility="Auto" >
<Grid
x:Name="InternalLayoutRoot"
Background="Transparent"
ShowGridLines="False" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.6*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="lblCancel" Text="Delete Personalization :"
VerticalAlignment="Top" Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right"
></TextBlock>
<ScrollViewer
Grid.Column="1"
Margin="5"
VerticalScrollBarVisibility="Auto">
<ListBox
x:Name="PersList"
ItemsSource="{Binding
Path=ASCXPersList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Height="20" Text="{Binding OggCod}"
Margin="5" Style="{StaticResource ContentBlock}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
</Grid>
</ScrollViewer>
<Rectangle Fill="Gray" Grid.Row="0" Grid.ColumnSpan="2"></Rectangle>
<Rectangle Fill="Gray" Grid.Row="2" Grid.ColumnSpan="2"></Rectangle>
<TextBlock
x:Name="lblTitle"
HorizontalAlignment="Left"
FontFamily="Currier"
Foreground="Gold"
Grid.Column="0"
Grid.Row="0"
FontSize="12" VerticalAlignment="Center" Text="Manager Delete Personalization :
"></TextBlock>
<Image Height="20" Width="20" Cursor="Hand" HorizontalAlignment="Right"
Margin="1,1,15,1" MouseLeftButtonUp="Save_ConfSta" Grid.Column="1" Grid.Row="2"
Source="../../FakeControls/Resource/Salva.png"></Image>
<Button
x:Name="btnClose"
Cursor="Hand"
Click="CloseBtnSta_Click"
Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Top"
Content="Close" Style="{StaticResource DFCloseButton}"/>
</Grid>
</UserControl> Dopo aver selezionato una personalizzazione da cancellare tra quelle elencate nella lista, se l’utente decide di salvare facendo click sull’immagine di salvataggio, viene generato un evento e mandato in esecuzione il suo handler, il quale richiama il metodo Save_ConfSta() della classe ManagerDeleteConfiguration. Internamente a tale metodo viene creato un evento di tipo SaveConfEvent, passando al suo costruttore la lista di componenti UI, e viene successivamente inviato. private void Save_ConfSta(object sender, MouseButtonEventArgs e)
{
List<object> param = new List<object>();
param.Add(this.MDContainer);
param.Add(this.TargetASCX);
param.Add(this.PersList);
SaveConfEvent scEvent = new SaveConfEvent("SaveConf", param);
scEvent.dispatch();
} Dopo essere stato catturato dal Controller viene eseguita la funzione execute() del comando di classe SaveConfCommand associato all’evento. All’interno di tale funzione vengono recuperati i componenti UI, viene creata una istanza della classe NewConfigDetail e vengono inizializzate le sue proprietà, si controlla se è stata selezionata dalla lista una personalizzazione da eliminare, e infine viene richiamato il metodo Close() di chiusura della modale passandogli come parametro l’istanza creata. public class SaveConfCommand : SilverlightPattern.Business.Command.ICommand,
IResponder
{
private DFModel model = DFModel.getInstance();
ModalDialog MDContainer;
string TargetASCX;
ListBox PersList;
#region ICommand Membri di
public void execute(GenericEvent genericEvent)
{
foreach (object param in genericEvent.Params)
{
if (param is ModalDialog)
MDContainer = (param as ModalDialog);
if (param is string)
TargetASCX = (param as string);
if (param is ListBox)
PersList = (param as ListBox);
}
NewConfigDetail resp = new NewConfigDetail();
resp.ASCX = this.TargetASCX;
if (this.PersList.SelectedIndex == -1)//se non è indicata la
personalizzazione da cancellare esci
return;
//utilizzo il campo NewPersName per indicare quale personalizzazione
eliminare
resp.NewPersName = (model.ASCXPersList[this.PersList.SelectedIndex]
as Personalizzazione).OggCod;
//chiude modale e restituisce la risposta
this.MDContainer.Close(wDialogResult.OK, resp);
}
#endregion
}
Il metodo Close() al suo interno, dopo aver chiuso la popup, lancia l’evento Closed della classe EventHandler<ModalDialogClosedEventArgs> che viene catturato dal delegato definito all’interno della classe DelPersCommand. Tale delegato manda in esecuzione il metodo ModalDialog_DelConf_Closed() che rimuove l’handler associato all’evento di chiusura della popup e chiama il metodo SalvaInBackend() della classe ManagerSave passandogli in ingresso come parametri il comando da eseguire (in tal caso ‘CancellaPersonalizzazione’), i dettagli della personalizzazione da eliminare, e la funzione di callback CallBackDelPersonalization(). La classe ManagerSave invia la richiesta asincrona al server utilizzando l’oggetto HttpHelper e al ritorno della chiamata richiama la funzione di callback. Se la cancellazione della personalizzazione selezionata è andata a buon fine viene effettuato un riallineamento della struttura e viene aggiornata la lista delle personalizzazioni, altrimenti viene visualizzata una schermata di alert. /// Evento asincrono di callback alla cancellazione di una personalizzazione
private void CallBackDelPersonalization(HttpResponseCompleteEventArgs e)
{
if (e.Response == "OK")
{//cancellazione andata a buon fine
//riallineamento
/*BeginInvoke esegue il delegate in maniera asincrona sul thread
associalto
al Dispatcher, che probabilmente è l'UI Thread.*/
currentDispatcher.BeginInvoke(delegate()
{
LoadASCXPersonalization();
//comunque per sicurezza cancello le strutture in locale e
obbligo una rilettura in backend e una ricostruzione
Manager.setCurrentConfig(null);
Manager.setTabsFieldsetsCurrentConfig(null);
Manager.msgBox(ManagerConst.getCaptionByLanguageId(ManagerConst.msgCancellazione
OK));
Manager.WaitingFormOff();
});
}
else
{//se la cancellazione non è andata a buon fine
//riallineamento
currentDispatcher.BeginInvoke(delegate()
{
LoadASCXPersonalization();
//comunque per sicurezza cancello le strutture in locale e
obbligo una rilettura in backend e una ricostruzione
Manager.setCurrentConfig(null);
Manager.setTabsFieldsetsCurrentConfig(null);
Manager.msgBox(ManagerConst.getCaptionByLanguageId(ManagerConst.msgCancellazione
KO) + e.Response);
Manager.WaitingFormOff();
});
}
} 6.3.4
SEZIONE DETTAGLI All’interno di tale sezione vengono visualizzati i dettagli riguardanti il parametro relativo ad una gerarchia ascx selezionato all’interno della sezione Parametri Default Gerarchie. I dettagli relativi ad un parametro riguardano precisamente il nome del parametro, il suo tipo (ad esempio una TextBox o una Grid) e l’indicazione della sua natura di default o meno. Osservando il codice xaml relativo alla sezione, si può notare che essa è composta principalmente da un elemento di tipo Grid, che contiene al proprio interno degli elementi di tipo TextBlock per visualizzare i dettagli relativi ad un parametro della gerarchia, e quattro elementi di tipo Button ciascuno con una propria funzionalità ben precisa. Il primo è il pulsante di Full Screen per permettere la visualizzazione a schermo intero dell’interfaccia grafica dell’applicazione, poi c’è il pulsante Load/ReLoad che consente di caricare dal server le gerarchie e di visualizzarle all’interno della sezione ASCX, infine ci sono i pulsanti di Preview e di Test che consentono rispettivamente di mostrare una preview a fronte di una configurazione ascx e di effettuare un test attraverso una chiamata asincrona al web service. <Grid Background="#FFDEDEDE" Grid.Row="0" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<!--Bottoni Carica -->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button x:Name="btnFullScreen" Grid.Column="0" Content="Full
Screen" Click="btnFullScreen_Click" />
<Button x:Name="btnLoad" Grid.Column="1" Content="Load/ReLoad"
Click="btnLoad_Click" />
<Button x:Name="btnPreview" Grid.Column="2" Content="Preview"
Click="btnPreview_Click" />
<Button
x:Name="btnTest"
Grid.Column="3"
Content="Test"
Click="btnTest_Click" />
</Grid>
<TextBlock
Text="Dettagli"
Margin="5"
Grid.Row="1"
Style="{StaticResource TitleBlock}"/>
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Nome: " Margin="5" Grid.Row="0" Grid.Column="0"
Style="{StaticResource TitleBlock}"/>
<TextBlock Text="Tipo: " Margin="5" Grid.Row="1" Grid.Column="0"
Style="{StaticResource TitleBlock}"/>
<TextBlock
Text="Default:
"
Margin="5"
Grid.Row="2"
Grid.Column="0" Style="{StaticResource TitleBlock}"/>
<TextBlock x:Name="txtNome" Text=""
Margin="5" Grid.Row="0"
Grid.Column="1" Style="{StaticResource ParBlock}"/>
<TextBlock x:Name="txtTipo" Text="" Margin="5" Grid.Row="1"
Grid.Column="1" Style="{StaticResource ParBlock}"/>
<TextBlock x:Name="txtDefault" Text="" Margin="5" Grid.Row="2"
Grid.Column="1" Style="{StaticResource ParBlock}"/>
</Grid>
</Grid>
Nella figura sottostante viene mostrato un esempio di visualizzazione dei dettagli a fronte della selezione di un parametro all’interno della sezione Parametri Default Gerarchie. Figura 41 – Esempio di dettagli relativi ad un parametro selezionato Quando un utente seleziona un parametro all’interno della rispettiva sezione, viene scatenato un evento di cambio selezione che ha come sorgente l’oggetto di tipo ListBox interno alla sezione. Una volta lanciato, l’evento viene catturato dal suo gestore il quale, per poterlo gestire, richiama il metodo ParametriDefaultList_SelectionChanged(). Internamente a tale metodo viene creata una lista di oggetti relativi a componenti UI che saranno utilizzati successivamente. Tale lista viene passata come parametro alla creazione dell’evento business della classe DettagliLoadEvent. Successivamente l’evento viene inviato attraverso la funzione dispatch() e catturato dal componente DFController presente all’interno dello strato di Business. Tale componente verifica la presenza dell’evento tra quelli presenti nella sua lista e, se la verifica ha esito positivo, lancia il comando ad esso associato. private
void
ParametriDefaultList_SelectionChanged(object
sender,
SelectionChangedEventArgs e)
{
DFModel.getInstance().ParametriDefaultSelectedIndex
=
this.ParametriDefaultList.SelectedIndex;
List<object> param = new List<object>();
param.Add(this.txtNome);
param.Add(this.txtTipo);
param.Add(this.txtDefault);
DettagliLoadEvent detEvent = new DettagliLoadEvent("DettagliLoad",
param);
detEvent.dispatch();
} Quando viene eseguito il comando, in realtà ad essere eseguito è il metodo execute() derivante dall’interfaccia ICommand che la classe DettagliLoadCommand implementa. Al momento della sua esecuzione, vengono innanzitutto recuperati i componenti UI dalla lista di oggetti che era stata passata come parametro al costruttore della classe relativa all’evento scatenato. Successivamente vengono assegnati agli oggetti TextBlock i valori relativi ai dettagli del parametro di default selezionato. public class DettagliLoadCommand : SilverlightPattern.Business.Command.ICommand,
IResponder
{
private DFModel model = DFModel.getInstance();
TextBlock txtNome, txtTipo, txtDefault;
#region ICommand Membri di
public void execute(GenericEvent genericEvent)
{
if (genericEvent.Params[0] is TextBlock)
txtNome = (genericEvent.Params[0] as TextBlock);
if (genericEvent.Params[1] is TextBlock)
txtTipo = (genericEvent.Params[1] as TextBlock);
if (genericEvent.Params[2] is TextBlock)
txtDefault = (genericEvent.Params[2] as TextBlock);
Parametro par;
if (model.ParametriDefaultSelectedIndex == -1)
par = null;
else
par
model.ParametriDefaultList[model.ParametriDefaultSelectedIndex];
if (par != null)
{
this.txtNome.Text = par.ParCod;
this.txtTipo.Text = par.Vers;
=
this.txtDefault.Text = par.Default;
}
}
#endregion
#region IResponder Membri di
public void onFault(string errorMessage)
{
}
public void onResult(object result)
{
}
} #endregion
Per il momento viene tralasciata la parte riguardante la Preview poiché questa verrà presa in considerazione in maniera approfondita all’interno del prossimo paragrafo. Quando l’utente fa click sul pulante di Test viene richiamato il metodo btnTest_Click(), all’interno del quale un evento appartenente alla classe RunTestEvent viene creato e successivamente lanciato attraverso la funzione dispatch(). L’evento viene catturato dal Controller che richiama il metodo execute() del comando della classe RunTestCommand. All’interno di tale metodo viene a sua volta richiamato il metodo SalvaInBackend() della classe ManagerSave, al quale vengono passati come parametri il comando da eseguire (TestSalva), i dati che in tale caso sono rappresentati da una semplice stringa di testo, e la funzione di callback per la post asincrona al web service. La chiamata asincrona viene effettuata utilizzando l’oggetto HttpHelper che consente di effettuare una comunicazione http. Quando il controllo ritorna al chiamante viene eseguita la funzione di callback CallBackAllineaPars2 che nel caso in cui il test è andato a buon fine mostra a video una finestra con un messaggio di conferma dell’invio dei dati, altrimenti visualizza una finestra di errore. public class RunTestCommand : SilverlightPattern.Business.Command.ICommand,
IResponder
{
Dispatcher
currentDispatcher
=
Application.Current.RootVisual.Dispatcher;
#region ICommand Membri di
public void execute(GenericEvent genericEvent)
{
ManagerSave.SalvaInBackend("TestSalva",
CallBackAllineaPars2);
}
"Dati
inviati",
#endregion
#region IResponder Membri di
public void onFault(string errorMessage)
{
}
public void onResult(object result)
{
}
#endregion
private void CallBackAllineaPars2(HttpResponseCompleteEventArgs e)
{
if (e.Response == "OK")
{//salvataggio andato a buon fine
//riallineamento
currentDispatcher.BeginInvoke(delegate()
{
Manager.msgBox("OK");
});
}
else
{//se il salvataggio non è andato a buon fine visualizzo
//un alert e non riallineo e cancello le strutture in modo tale
che rieffettuando
//una preview mantengo ancora vive le modifiche e si può
ritentare il salvataggio...
currentDispatcher.BeginInvoke(delegate()
{
Manager.msgBox(e.Response);
});
}
}
} 6.3.5
PARAMETRI PERSONALIZZAZIONE ASCX All’interno della sezione viene visualizzata la lista dei parametri relativi alla personalizzazione ascx selezionata all’interno della sezione Personalizzazioni ASCX. Da un punto di vista grafico la sezione in questione è costituita essenzialmente da un oggetto di tipo Grid all’interno del quale sono presenti un oggetto di tipo TextBlock, che specifica il nome della sezione, ed un oggetto ListBox che contiene la lista dei parametri della personalizzazione. La ListBox grazie alla proprietà ItemsSource fa binding su un oggetto ASCXPersParList, che contiene una lista di elementi di tipo Parametro, presente all’interno della classe DFModel; questo dà alla ListBox l’opportunità di visualizzare automaticamente i dati aggiornati al cambiare dell’oggetto contenuto nel Model. <Grid Background="LightGray" Grid.Row="1" Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="titParametri" Text="Parametri/Personalizzazione
ASCX" Margin="5" Grid.Row="0" Style="{StaticResource TitleBlock}"/>
<ListBox
x:Name="ASCXPersParList"
Background="#FFDEDEDE"
Grid.Row="1" SelectionChanged="ASCXParPersList_SelectionChanged"
ItemsSource="{Binding
Path=ASCXPersParList}"
SelectedIndex="{Binding Path=ASCXPersParSelectedIndex, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="22"></ColumnDefinition>
<ColumnDefinition
Width="auto"></ColumnDefinition>
<ColumnDefinition
Width="200"></ColumnDefinition>
<ColumnDefinition
Width="100"></ColumnDefinition>
<ColumnDefinition
Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- Lista Parametri x personalizzazione-->
<CheckBox
IsChecked="{Binding
Ereditato}"
IsEnabled="False" Grid.Row="0" Grid.Column="1" ></CheckBox>
<TextBlock
x:Name="txtNomePar"
Text="{Binding
ParCod}"
Margin="5"
Grid.Row="0"
Grid.Column="2"
Style="{StaticResource
ContentBlock}"/>
<TextBlock
Text="{Binding
Vers}"
Margin="5"
Grid.Column="3" Style="{StaticResource ContentBlock}"/>
<TextBlock
x:Name="txtValDef"
Text="{Binding
Default}"
Margin="5"
Grid.Row="0"
Grid.Column="4"
Foreground="red"
Style="{StaticResource ContentBlock}"/>
<Image
Height="20"
Width="20"
Cursor="Hand"
Grid.Row="0"
Grid.Column="0"
Margin="1,1,1,1"
MouseLeftButtonUp="DeletePar_Click"
Source="../FakeControls/Resource/DeletePar.png">
</Image>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid> Grid.Row="0"
Quando viene visualizzata la lista dei parametri della personalizzazione, viene anche offerta all’utente la possibilità di eliminare un parametro facendo click sull’elemento di tipo Image contenuto in ciascuna riga della lista, come mostrato nell’esempio riportato nella figura sottostante. Figura 42 – Parametri relativi alla personalizzazione ASCX selezionata Quando viene selezionata una personalizzazione all’interno della sezione Personalizzazioni ASCX, viene scatenato un evento di cambio selezione che viene catturato e gestito dal rispettivo gestore. Quest’ultimo richiama il metodo ASCXPersList_SelectionChanged() della classe Page; all’interno del metodo viene creata una lista di componenti UI che viene passata al costruttore dell’evento di classe ParamsPersLoadEvent. L’evento di business, dopo essere stato creato, viene inviato e catturato dal Controller che lo ricerca all’interno della sua lista di coppie evento‐comando e, in caso positivo, lancia il metodo execute() appartenente al relativo comando della classe ParamsPersLoadCommand. Internamente al metodo vengono recuperati dalla lista i componenti UI e poi viene creata una istanza della classe Delegate per poter effettuare delle chiamate asincrone ad un web service. Per essere precisi vengono effettuate due chiamate al web service, la prima delle quali serve per ottenere la collezione di tab e fieldset per la personalizzazione corrente, mentre la seconda chiamata è effettuata per ottenere i parametri della personalizzazione. public class ParamsPersLoadDelegate : GenericDelegate
{
private DFModel model = DFModel.getInstance();
public ParamsPersLoadDelegate(IResponder responder)
: base(responder)
{
}
public
void
RefreshTabsFieldsetsByPersonalizzazione(string
gerCod,
string oggCod)
{
//chiamata al listener per ottenere la collezione di tabs e fieldset
per la personalizzazione corrente
string
PostUrl2
=
"http://******/listenerDF.aspx?op=getTabsFieldset&par=" + gerCod + "&par2=" +
oggCod;
// Chiamata asincrona al web service attraverso il listener
WebClient DFService2 = new WebClient();
DFService2.DownloadStringCompleted
+=
new
DownloadStringCompletedEventHandler(DFService2_DownloadParsCompleted);
DFService2.DownloadStringAsync(Manager.getPostAddress(PostUrl2));
}
public void RefreshParsByPersonalizzazione(string gerCod, string oggCod)
{
//chiamata
al
listener
per
ottenere
i
parametri
per
una
personalizzazione
string PostUrl = "http://******/listenerDF.aspx?op=getPars&par=" +
gerCod + "&par2=" + oggCod;
// Chiamata asincrona al web service attraverso il listener
WebClient DFService = new WebClient();
DFService.DownloadStringCompleted
+=
DownloadStringCompletedEventHandler(DFService_DownloadParsCompleted);
DFService.DownloadStringAsync(Manager.getPostAddress(PostUrl));
}
new
//callBack chiamata al carica Tabs e Fieldset per personalizzazione
void
DFService2_DownloadParsCompleted(object
sender,
DownloadStringCompletedEventArgs e)
{
string returnType = "Return getTabsFieldset";
if (null != e.Error)
((ParamsPersLoadCommand)responder).onFault("Exception!
e.Error.Message + ")", returnType);
else
((ParamsPersLoadCommand)responder).onResult(e.Result,
returnType);
}
//callBack chiamata al carica parametri per personalizzazione
void
DFService_DownloadParsCompleted(object
DownloadStringCompletedEventArgs e)
{
string returnType="Return getParams";
if (null != e.Error)
((ParamsPersLoadCommand)responder).onFault("Exception!
e.Error.Message + ")", returnType);
else
((ParamsPersLoadCommand)responder).onResult(e.Result,
returnType);
}
("
+
sender,
("
+
} Quando il web service termina la propria elaborazione, invia la risposta al chiamante. Una volta che il controllo ritorna al chiamante, se non si è verificato alcun errore viene chiamato il metodo onResult() del comando; in caso contrario viene richiamato il metodo onFault(). All’interno del primo metodo avviene l’elaborazione dei risultati provenienti dal web service; tali risultati vengono innanzitutto parserizzati per potervi accedere e successivamente, a partire dall’xml recuperato dal listener, viene ricostruita una struttura di Tab e Fieldset presenti nella personalizzazione corrente, viene caricata la lista dei parametri ascx che viene assegnata come valore all’oggetto ASCXPersParList contenuto nel Model. Fatto ciò viene visualizzata in maniera immediata, all’interno del componente ListBox della sezione, la lista aggiornata dei parametri relativi alla personalizzazione selezionata. public
class
ParamsPersLoadCommand
SilverlightPattern.Business.Command.ICommand, IResponder
{
private DFModel model = DFModel.getInstance();
public Personalizzazione PersonalizzazioneCorrente;
public ConfigurazioneASCX ConfigurazioneCorrente;
public XDocument xmlParsCorrenti;
public XDocument xmlTabsFieldsetCorrenti;
TextBox txtValore;
CheckBox cbxEreditato;
Button btnAggiungiParam, btnEliminaParam, btnSalvaParam;
TextBlock titParametri;
:
#region ICommand Membri di
public void execute(GenericEvent genericEvent)
{
if (genericEvent.Params[0] is TextBox)
txtValore = (genericEvent.Params[0] as TextBox);
if (genericEvent.Params[1] is CheckBox)
cbxEreditato = (genericEvent.Params[1] as CheckBox);
if (genericEvent.Params[2] is Button)
btnAggiungiParam = (genericEvent.Params[2] as Button);
if (genericEvent.Params[3] is Button)
btnEliminaParam = (genericEvent.Params[3] as Button);
if (genericEvent.Params[4] is Button)
btnSalvaParam = (genericEvent.Params[4] as Button);
if (genericEvent.Params[5] is TextBlock)
titParametri = (genericEvent.Params[5] as TextBlock);
Personalizzazione per;
if (model.ASCXPersSelectedIndex == -1)
per = null;
else
per = model.ASCXPersList[model.ASCXPersSelectedIndex];
if (per != null)
{
this.titParametri.Text = "Parametri/Personalizzazione ASCX";
Manager.WaitingFormOn();
PersonalizzazioneCorrente = per;
ParamsPersLoadDelegate
ppDelegate
=
new
ParamsPersLoadDelegate(this);
ppDelegate.RefreshTabsFieldsetsByPersonalizzazione(PersonalizzazioneCorrente.Ger
Cod, PersonalizzazioneCorrente.OggCod);
ppDelegate.RefreshParsByPersonalizzazione(PersonalizzazioneCorrente.GerCod,
PersonalizzazioneCorrente.OggCod);
PulisciParPers();
}
}
#endregion
public void onFault(string errorMessage, string returnType)
{
if (returnType=="Return getParams")
Manager.WaitingFormOff();
}
public void onResult(object result, string returnType)
{
if (returnType == "Return getParams")
{
//this.titParametri.Text = OrigtitoloParametri;
DisplayParsASCX((string)result);
Manager.WaitingFormOff();
}
else
{
SetTabsFieldsetPers((string)result);
}
}
private void DisplayParsASCX(string xmlContent)
{
xmlContent
=
xmlContent.Replace("xmlns=\"http://tempuri.org/StrutturaGerarchieFinale.xsd\"",
"");
xmlParsCorrenti = XDocument.Parse(xmlContent);
ReLoadParsASCXList();
}
private void ReLoadParsASCXList()
{
//svuoto e riassegno la source alla lista ASCXPersParList
model.ASCXPersParList = null;
model.ASCXPersParSelectedIndex = -1;
model.ASCXPersParList = (List<Parametro>)getListaParsASCX();
}
/// Metodo che ricostruisce la lista di oggetti parametro
private System.Collections.IEnumerable getListaParsASCX()
{
Parametro par;
//lista.Clear();
List<Parametro> lista = new List<Parametro>();
var
itemsPar
=
from
ParASCX
in
xmlParsCorrenti.Root.Elements("ParOut")
select ParASCX;
////ricostruzione della classe
foreach (XElement per in itemsPar)
{
par = new Parametro();
par.ParCod = (string)per.Element("ParCod").Value;
par.Vers = (string)per.Element("Vers").Value;
par.Default = (string)per.Element("Valore").Value;
par.Ereditato
=
System.Convert.ToBoolean(per.Element("Ereditato").Value);
if (par.ParCod.EndsWith("_sta"))
par.MasterPar = true;
else
par.MasterPar = false;
lista.Add(par);
}
model.ConfigASCX
=
Manager.GetConfigurazioneFromParametri(lista,
PersonalizzazioneCorrente);
return lista;
}
private void SetTabsFieldsetPers(string xmlContent)
{
xmlContent
=
xmlContent.Replace("xmlns=\"http://tempuri.org/StrutturaGerarchieFinale.xsd\"",
"");
xmlTabsFieldsetCorrenti = XDocument.Parse(xmlContent);
SetTabsFieldsetsCorrenti();
}
/// Metodo che a partire dall'xml recuperato dal listener ricostruisce
/// una struttura di Tabs e Fieldset presenti nella personalizzazione
corrente
private void SetTabsFieldsetsCorrenti()
{
List<Tab> tabs = new List<Tab>();
List<Fieldset> fieldsets = new List<Fieldset>();
Tab tab;
Fieldset fieldset;
var
itemsTab
=
from
Tabs
in
xmlTabsFieldsetCorrenti.Root.Element("Tabs").Elements("Tab")
select Tabs;
////ricostruzione della classe
foreach (XElement tt in itemsTab)
{
tab = new Tab();
tab.Index = System.Convert.ToInt32(tt.Attribute("Index").Value);
tab.newIndex
=
System.Convert.ToInt32(tt.Attribute("Index").Value);
tab.KeyVoc = tt.Attribute("Cap").Value;
tabs.Add(tab);
}
var
itemsFieldset
=
from
Fieldsets
in
xmlTabsFieldsetCorrenti.Root.Element("Fieldsets").Elements("Fieldset")
select Fieldsets;
////ricostruzione della classe
foreach (XElement ff in itemsFieldset)
{
fieldset = new Fieldset();
fieldset.Id = (string)ff.Attribute("id").Value;
fieldset.KeyVoc = (string)ff.Attribute("KeyVoc").Value;
fieldset.Top = System.Convert.ToInt32(ff.Attribute("Top").Value)
- 10; //offset dovuto a caratteristiche del fake control
fieldset.Left
=
System.Convert.ToInt32(ff.Attribute("Left").Value);
fieldset.Height
=
System.Convert.ToInt32(ff.Attribute("Height").Value) + 10; //offset dovuto a
caratteristiche del fake control
fieldset.Width
=
System.Convert.ToInt32(ff.Attribute("Width").Value);
fieldset.TabContainerIndex
=
System.Convert.ToInt32(ff.Attribute("TabContainerIndex").Value);
fieldset.BgColor = (string)ff.Attribute("Color").Value;
fieldsets.Add(fieldset);
}
Manager.setTabsFieldsetsCurrentConfig(Manager.GetTabsFieldsetPersonalizzazioneCo
rrente(tabs, fieldsets, PersonalizzazioneCorrente));
}
private void PulisciParPers()
{
this.txtValore.Text = "";
this.txtValore.IsReadOnly = false;
this.cbxEreditato.IsChecked = false;
this.btnAggiungiParam.IsEnabled = false;
this.btnEliminaParam.IsEnabled = false;
this.btnSalvaParam.IsEnabled = false;
}
} 6.3.6
GESTIONE PARAMETRI Internamente alla sezione vengono visualizzati alcuni dettagli relativi al parametro selezionato all’interno della sezione trattata in precedenza; in particolare viene specificato se si tratta di un parametro ereditato o meno e, nel caso in cui non si tratti di un parametro ereditato, viene visualizzato il suo valore. Per quel che riguarda l’aspetto grafico, la sezione è costituita principalmente da una griglia di tipo Grid all’interno della quale sono presenti una TexBlock che contiene il nome della sezione, un componente CheckBox per specificare se si tratta di un parametro ereditato, e un componente di tipo TextBox per visualizzare il valore corrente del parametro. Infine sono stati aggiunti dei pulsanti di salvataggio, aggiunta, e cancellazione che per ora non vengono utilizzati. <Grid Background="#FFDEDEDE" Grid.Row="1" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock
Text="Gestione
Parametro"
Margin="5"
Grid.Row="0"
Style="{StaticResource TitleBlock}"/>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Valore" Margin="5" Grid.Row="0" Grid.Column="0"
Style="{StaticResource TitleBlock}"/>
<TextBlock
Text="Ereditato"
Margin="5"
HorizontalAlignment="Right" Grid.Row="0" Grid.Column="1" Style="{StaticResource
TitleBlock}"/>
<CheckBox
HorizontalAlignment="Left"
Grid.Column="2" ></CheckBox>
x:Name="cbxEreditato"
VerticalAlignment="Center"
IsEnabled="False"
Grid.Row="0"
<TextBox x:Name="txtValore" Text=""
Height="20" Width="auto"
FontSize="12" TextAlignment="Center" Grid.Row="1" Grid.ColumnSpan="3"></TextBox>
</Grid>
<!--pulsanti salvataggio aggiunta cancellazione -->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button x:Name="btnSalvaParam"
Grid.Column="0" Content="Salva"
Click="btnSalvaParam_Click" />
<Button
x:Name="btnEliminaParam"
Grid.Column="1"
Content="Elimina" Click="btnEliminaParam_Click" />
<Button
x:Name="btnAggiungiParam"
Grid.Column="2"
Content="Aggiungi" Click="btnAggiungiParam_Click" />
</Grid>
</Grid> Viene di seguito mostrato un esempio riguardante la visualizzazione della sezione Gestione Parametro quando viene selezionato un parametro nella sezione Parametri Personalizzazione ASCX. Figura 43 – Esempio dettagli relativi ad un parametro di una personalizzazione Quando viene selezionato un parametro di una personalizzazione ascx, viene generato un evento di cambio selezione che viene gestito attraverso l’esecuzione del metodo ASCXParPersList_SelectionChanged() della classe Page. Internamente a tale metodo viene creata la lista dei componenti UI a cui accedere, viene creato un evento della classe GestPersParamLoadEvent passandogli nel costruttore la lista, e successivamente viene inviato l’evento attraverso la funzione dispatch(). Dopo che l’evento è stato inviato, viene catturato dalla classe DFController che richiama il metodo execute() appartenente al comando GestPersParamLoadCommand associato all’evento. All’interno del metodo vengono recuperati dalla lista di oggetti i componenti UI e poi, recuperando il parametro selezionato dall’oggetto ASCXPersParList contenuto nel DFModel, viene assegnato alla proprietà Text della TextBox il valore del parametro, viene specificato se il parametro è ereditato nel componente CheckBox, e infine viene visualizzata sulla base di ciò la visibilità dei pulsanti (Button) di aggiunta, cancellazione e salvataggio dei dettagli del parametro. In particolare, se il parametro selezionato è ereditato allora si rendono visibili solo i pulsanti Salva ed Elimina, altrimenti l’unico pulsante visibile è quello di aggiunta. public
class
GestPersParamLoadCommand
SilverlightPattern.Business.Command.ICommand, IResponder
{
private DFModel model = DFModel.getInstance();
TextBox txtValore;
CheckBox cbxEreditato;
Button btnAggiungiParam, btnEliminaParam, btnSalvaParam;
#region ICommand Membri di
public void execute(GenericEvent genericEvent)
{
if (genericEvent.Params[0] is TextBox)
txtValore = (genericEvent.Params[0] as TextBox);
if (genericEvent.Params[1] is CheckBox)
cbxEreditato = (genericEvent.Params[1] as CheckBox);
if (genericEvent.Params[2] is Button)
btnAggiungiParam = (genericEvent.Params[2] as Button);
if (genericEvent.Params[3] is Button)
btnEliminaParam = (genericEvent.Params[3] as Button);
if (genericEvent.Params[4] is Button)
btnSalvaParam = (genericEvent.Params[4] as Button);
Parametro par;
if (model.ASCXPersParSelectedIndex == -1)
par = null;
else
par = model.ASCXPersParList[model.ASCXPersParSelectedIndex];
if (par != null)
:
{
this.txtValore.Text = par.Default;
this.txtValore.IsReadOnly = par.Ereditato;
this.cbxEreditato.IsChecked = par.Ereditato;
switchPulsanti(par.Ereditato);
}
}
#endregion
private void switchPulsanti(bool p)
{
if (p)
{
this.btnAggiungiParam.IsEnabled = true;
this.btnEliminaParam.IsEnabled = false;
this.btnSalvaParam.IsEnabled = false;
}
else
{
this.btnAggiungiParam.IsEnabled = false;
this.btnEliminaParam.IsEnabled = true;
this.btnSalvaParam.IsEnabled = true;
}
}
}
6.3.7
TEST STRUTTURA Quando si vuole effettuare dei test sull’applicazione, invece di caricare la Master Form (cioè la form della classe Page) come form principale dell’applicazione, si carica la Test Form Dinamica, attraverso la creazione di una struttura di tipo DynamicForm e l’istanzazione della classe TestStruttura. La scelta della form principale da caricare avviene impostando la variabile startForm all’interno della classe Manager con i valori di tipo string ‘MasterForm’ o ‘Struttura’, mentre la sua istanzazione, come già detto in precedenza, avviene nella classe PageSwitcher che ha il compito di caricare la struttura corrispondente da un listener e settare la targetPage. Anche la classe TestStruttura, come la classe Page, è uno user control, quindi eredita dalla classe UserControl. All’interno della pagina di test vengono visualizzate le varie sezioni ASCX caricate dal server e per ciascuna sezione è possibile visualizzarne la preview. Per consentire il binding dei componenti UI della classe sugli oggetti del Model, quando viene caricata la pagina viene chiamato il metodo UserControl_Loaded() che istanzia la classe DFModel e assegna tale istanza come valore alla proprietà DataContext presente nella Grid principale chiamata LayoutRoot. In tal modo tutti i componenti contenuti all’interno della Grid in questione ereditano da questa il contesto dai dati. Figura 44 – Test Form Dinamica Per essere istanziata, la classe TestStruttura prende come parametro d’ingresso nel metodo costruttore un oggetto di tipo DynamicForm ottenuto dalla chiamata al listener presente sul server. public class DynamicForm
{
public DynamicForm()
{
//Ctor
//setto i defaults
this.Sezioni = new List<Section>();
this.TitoloForeColor = "#000000";//nero
this.TitoloBackColor = "#FFFFFF";//bianco
this.KeyVocabolario = "IPPOLOPPOLO";//generica caption Sezione
this.ManagerModel = "DynamicFormEngine";
this.KeyVocabolario = "DF_1";
}
public
public
public
public
public
public
public
public
string Id { get; set; }
string ManagerModel { get; set; }
string CssForm { get; set; }
string CssTitolo { get; set; }
string TitoloForeColor { get; set; }
string TitoloBackColor { get; set; }
string KeyVocabolario { get; set; }
List<Section> Sezioni { get; set; }
public string StatoForm { get; set; } //stato collassato o espanso della
form
} All’interno del costruttore della classe TestStruttura, dopo aver inizializzato i componenti dell’UI tramite il metodo InitializeComponent(), viene creata un’istanza della classe DragDropManager, passandogli come parametro l’oggetto Canvas dell’UI. L’istanza creata consente di gestire le operazioni di drag and drop sugli elementi contenuti all’interno del Canvas. Poi l’evento di cambio della posizione di una sezione nel pannello viene agganciato con un handler che, al verificarsi dell’evento, richiama il metodo dragDropManager_SectionsPositionChanged(). Dopo aver fatto ciò, viene effettuato il rendering delle sezioni caricate mandando in esecuzione il metodo RenderControls(). public partial class TestStruttura : UserControl, ISaveCancelBtn
{
UIElement[] draggableElements;
Panel[] panels;
public TestStruttura(DynamicForm
idStruttura
{
InitializeComponent();
DF)
//List<Section>
Sezioni,
string
//Imposto come titolo della form l'id della struttura
this.lblTitle.Text
Manager.RisolviKey_inVocabolarioDF(DF.KeyVocabolario);
=
DragDropManager dragDropManager = new DragDropManager(topCanvas);
//aggancio evento SectionsPositionChanged
dragDropManager.SectionsPositionChanged
EventHandler(dragDropManager_SectionsPositionChanged);
+=
new
this.ConfStructure = DF;
//mio metodo esteso per ordinare una lista su una proprietà
this.ConfStructure.Sezioni.WSSort("Order asc");
DF = this.ConfStructure;
panels = new Panel[] { Container };
draggableElements = new UIElement[this.ConfStructure.Sezioni.Count];
RenderControls();
for(int j=0; j< this.Container.Children.Count;j++)
draggableElements[j] = this.Container.Children[j];
foreach (var element in draggableElements)
{
//Registers the element with the DragDropManager. This allows
the elements to be dragged.
dragDropManager.RegisterDraggable(element);
}
foreach (var panel in panels)
{
//Registers the panel as a drop target. This means that elements
can be dragged into this panel.
dragDropManager.RegisterDropTarget(panel);
DFModel.getInstance().Griglia = this.LayoutRoot;
}
}
/// <summary>
/// Evento CambioPosizioneSezione
/// Scatta ogni volta si verifica uno spostamento di una sezione
/// Si ricalcolano gli Order delle sezioni
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void dragDropManager_SectionsPositionChanged(object sender, EventArgs e)
{
int currentOrder=0;
foreach (UserControl sec in this.Container.Children)
{
if((sec
as
ManagerParametriDF.Structure.SectionControl).ItemSezione.Order!=currentOrder)
{
(sec
as
ManagerParametriDF.Structure.SectionControl).ItemSezione.Order = currentOrder;
(sec
as
ManagerParametriDF.Structure.SectionControl).ItemSezione.Isalterated = true;
}
currentOrder++;
}
}
/// <summary>
/// routine che ritorna la lista di sezioni ordinata per la proprietà
Order
/// </summary>
/// <param name="Sezioni"></param>
/// <returns></returns>
private List<Section> getInOrderSections(List<Section> Sezioni)
{
List<Section> newLst = new List<Section>();
Section curSec;
int nIterazioni = Sezioni.Count;
for (int i = 0; i < nIterazioni; i++)
{
curSec = getMinorOrderSection(Sezioni);
//aggiungo nella nuova lista
newLst.Add(curSec);
//rimuovo dalla lista originale
Sezioni.Remove(curSec);
}
return newLst;
}
private Section getMinorOrderSection(List<Section> Sezioni)
{
Section lowerBound=null;
int CurrentIndex = Sezioni[0].Order;
foreach (Section sec in Sezioni)
{
if (sec.Order <= CurrentIndex)
{
lowerBound = sec;
CurrentIndex = sec.Order;
}
}
return lowerBound;
}
private void RenderControls()
{
foreach (Section sec in this.ConfStructure.Sezioni)
{
AddSection(sec);
}
}
private void AddSection(Section sec)
{
ManagerParametriDF.Structure.SectionControl
ctrlSec
=
new
ManagerParametriDF.Structure.SectionControl(sec, this.preview1, this.popup);
ctrlSec.MinWidth = this.Width;
this.Container.Children.Add(ctrlSec);
}
public DynamicForm ConfStructure { get; set; }
/// <summary>
/// Chiusura form senza salvataggio
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CloseBtnSta_Click(object sender, RoutedEventArgs e)
{
if (this.MDContainer != null)
MDContainer.Close();
}
/// <summary>
/// ModalContainer (quando il controllo è aperto su form modale sarebbe
un riferimento alla form stessa)
/// </summary>
public ModalDialog MDContainer { get; set; }
} private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
DFModel model = DFModel.getInstance();
//bind model to view
LayoutRoot.DataContext = model;
}
6.4 DYNAMIC FORM PREVIEW In realtà la vera parte core dell’applicativo ManagerParametriDF è il componente che gestisce la visualizzazione in preview di una form dinamica a fronte di una configurazione ascx passatagli in ingresso. Al momento della creazione di tale componente è stato necessario scegliere se implementare il componente come un Custom Control oppure come uno UserControl. Per poter effettuare tale scelta è stato necessario analizzare le differenze tra questi due tipi di componenti. [33] Per quel che riguarda i Custom Controls, essi sono dei controlli riusabili, a cui è possibile applicare temi e skin, che possono essere usati in maniera semplice caricando l’assembly che li contiene in un qualunque progetto. Gli User Controls sono, invece, dei controlli che possono essere riusati, ma ad essi non è possibile applicare temi o skin. Da un punto di vista tecnico le due tipologie di controlli differiscono in quanto i Custom Controls ereditano dalla classe System.Windows.Controls, mentre gli User Controls ereditano dalla classe System.Windows.Controls.UserControl. Tutti i controlli usati in Silverlight (come Button, TextBlock, ecc…) e gli User Controls sono anche oggetti di classe Controls. I Custom Controls hanno il vantaggio rispetto agli altri controlli che quando vengono riusati non si devono aggiungere attributi Style a tutte le istanze create all’interno dell’applicazione o a controlli composti. Un’altra differenza, spesso essenziale nella scelta, tra i due componenti riguarda la semplicità di codifica, infatti codificare uno User Control risulta essere molto più semplice. Poiché si erano già creati in precedenza molti User Controls e non era stato creato nessun Custom Control, la scelta è ricaduta su quest’ultima categoria. Per creare il controllo la prima cosa da fare è creare una classe C# che verrà chiamata Preview.cs, all’interno della quale verrà inserito il codice per poter costruire dinamicamente la preview di una form dinamica. Successivamente è necessario creare una cartella chiamata ‘themes’ (il nome è importante, altrimenti il suo contenuto non è preso in considerazione nella versione RC1 di Silverlight) all’interno della quale va inserito il file Xaml che conterrà gli stili di default per il controllo di preview che si sta creando e per qualunque altro custom control che si desidera aggiungere all’applicazione. Aggiungiamo, dunque, al progetto un file di tipo Text chiamato generic.xaml (il nome è molto importante), che è l’ossatura di base nella costruzione di qualunque custom control. Figura 45 – Creazione file generic.xaml Inseriamo tale file all’interno della cartella ‘themes’ che è stata aggiunta in precedenza al progetto. E’ molto importante per un corretto funzionamento, nel caso in cui il controllo venga creato in un’assembly diverso da quello del progetto in cui è utilizzato, ricordarsi di impostare la proprietà Build Action del generic.xaml a ‘Resource’, questo per assicurare che il template sia inserito nello stesso assembly del controllo altrimenti il template non sarà disponibile. Figura 46 – Impostazione della proprietà Build Action di generic.xaml Per prima cosa, aggiungiamo al file xaml il contenuto di default e una referenza al namespace xml del progetto. <ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:DraggableWindow;assembly=ManagerParametriDF"
xmlns:local="clr-namespace:ManagerParametriDF;assembly=ManagerParametriDF">
</ResourceDictionary> E’ da notare la definizione del prefisso local del namespace xml che sarà utilizzata all’interno dello stile per referenziare i tipi del controllo. Aggiungiamo ora al generic.xaml il tag Style per definire la posizione dove sarà inserito lo stile del controllo creato. Dentro la proprietà TargetType del tag Style viene specificato il controllo a cui lo stile va applicato (in tal caso il controllo Preview). Il controllo di destinazione va specificato anche nella proprietà TargetType del tag ControlTemplate; inseriamo dunque in tale tag il template del controllo. Il template del controllo Preview si compone principalmente di una griglia (Grid) all’interno della quale viene inserito un pannello (StackPanel) che conterrà tutti i tabs appartenenti alla form dinamica di cui si vuole visualizzare la preview. E’ presente inoltre, all’interno di un componente ScrollViewer, un canvas (Canvas) all’interno del quale saranno disegnati tutti i componenti della form dinamica. In aggiunta sono presenti dei pulsanti (Button) e delle immagini (Image) per le operazioni di chiusura e di salvataggio dati della Preview. <Style TargetType="local:Preview">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Preview">
<Grid x:Name="rootElement" Background="Transparent">
<Rectangle
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Opacity="0.765" Fill="Transparent" />
<Border
CornerRadius="30"
Background="#FF5C7590"
Width="1024" Height="768">
<Grid x:Name="Container" Width="984" Height="738"
Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="20"></ColumnDefinition>
<ColumnDefinition
Width="20"></ColumnDefinition>
<ColumnDefinition
Width="*"></ColumnDefinition>
<ColumnDefinition
Width="60"></ColumnDefinition>
</Grid.ColumnDefinitions>
<local:FieldsetImageControl
HorizontalAlignment="Center"
Grid.Row="0"></local:FieldsetImageControl>
<local:TabImageControl
Margin="1,1,1,1"
HorizontalAlignment="Center"
Grid.Row="0"></local:TabImageControl>
x:Name="FieldImg"
Grid.Column="0"
x:Name="TabImg"
Grid.Column="1"
<StackPanel
x:Name="TabContainer"
Height="25"
Orientation="Horizontal" Grid.Row="0" Grid.Column="2"></StackPanel>
<Button
x:Name="btnClose"
Cursor="Hand"
Grid.Row="0" Grid.Column="3" HorizontalAlignment="Right" VerticalAlignment="Top"
Content="Close" Style="{StaticResource DFCloseButton}"/>
<local:SaveImageControl
x:Name="SaveImg"
HorizontalAlignment="Left"
Grid.Column="3"
Grid.Row="0"></local:SaveImageControl>
<ScrollViewer Grid.Row="1" Grid.ColumnSpan="4"
VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible">
<Canvas
x:Name="MasterPanel"
Grid.ColumnSpan="4"
Grid.Row="1"
Width="1024"
Height="1768"
Background="LightGray">
</Canvas>
</ScrollViewer>
</Grid>
</Border>
<TextBlock
x:Name="Personalizzazione"
Text="wwwwww"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Foreground="White"
FontSize="14"></TextBlock>
<TextBlock x:Name="Status" Text="
... ..."
HorizontalAlignment="Left"
VerticalAlignment="Top"
Foreground="White"
Margin="2,1" FontSize="12"></TextBlock>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style> Inseriamo ora la logica di base del controllo all’interno del file C# creato. La classe Preview, definita all’interno del file Preview.cs, essendo un Custom Control deve ereditare dalla classe System.Windows.Controls. All’interno del costruttore della classe, dopo aver richiamato il costruttore della classe da cui eredita (Controls), va impostata la chiave che fa riferimento allo stile predefinito del controllo. public Preview()
: base()
{
this.DefaultStyleKey = typeof(Preview);
} Quello che il controllo deve fare è costruire dinamicamente la form dinamica con tutti i suoi componenti a fronte di una configurazione ASCX che gli viene passata in ingresso. Per questo motivo è necessario definire una proprietà in grado di fare binding su un oggetto del Model che contiene la configurazione desiderata. Qualunque proprietà che consenta il binding che deve essere aggiunta al controllo deve essere una Dependency Property, perciò creiamo ed esponiamo tale proprietà di dipendenza. [34] In Silverlight le proprietà di dipendenza sono esposte tipicamente come proprietà CLR. A livello base, è possibile interagire direttamente con tali proprietà senza essere a conoscenza del fatto che esse sono proprietà di dipendenza. Lo scopo di una proprietà di dipendenza è quello di fornire un modo per calcolare il valore di una proprietà basandosi sul valore di altri input. Questi altri input possono includere proprietà esterne come preferenze dell’utente, meccanismi di determinazione delle proprietà just‐in‐time come il data binding, template multi uso come risorse e stili, o valori determinati attraverso relazioni padre‐figlio con altri elementi nell’albero degli oggetti. In aggiunta, una proprietà di dipendenza può essere implementata per fornire dei callbacks che monitorano i cambiamenti dei valori della proprietà. Se una proprietà è di sola lettura non è necessario che sia una proprietà di dipendenza. Le proprietà di dipendenza estendono la funzionalità di una proprietà CLR fornendo un tipo che ritorna una proprietà, come un’implementazione alternativa del pattern standard che ritorna la proprietà con un campo privato. public
static
readonly
DependencyProperty
DependencyProperty.Register("Configurazione",
typeof(Preview),
new
PropertyChangedCallback(OnConfigurazioneChanged)));
ConfigurazioneProperty
=
typeof(ConfigurazioneASCX),
PropertyMetadata(new
public ConfigurazioneASCX Configurazione
{
get
{
(ConfigurazioneASCX)this.GetValue(ConfigurazioneProperty); }
set
{
base.SetValue(ConfigurazioneProperty, value);
}
} return
Nel codice di sopra viene riportata l’implementazione di una istanza della proprietà di dipendenza, il cui identificatore è ConfigurazioneProperty, che possiede un tipo di ritorno che è una DependencyProperty. L’istanza viene esposta come un membro pubblico, statico, e di sola lettura. Nel caso di una proprietà di dipendenza custom, tale membro è ottenuto come valore di ritorno quando si registra la proprietà. L’identificatore ConfigurazioneProperty è usato come parametro in API come GetValue e SetValue che espongono in Silvelight le proprietà di sistema fondamentali. Per registrare una proprietà di dipendenza occorre chiamare il metodo Register() della classe DependencyProperty passandogli come parametri un nome univoco per la proprietà (es. Configurazione), il tipo di valori che la proprietà accetta in ingresso (es. ConfigurazioneASCX), la classe del componente che possiede tale proprietà (es. Preview), e se necessario i metadati che consentono di specificare un valore di default che viene assegnato alla proprietà e/o un metodo di callback statico che è invocato quando il valore della proprietà cambia. Per ottenere un valore o per assegnare un valore alla proprietà creata deve essere creato un wrapper (es. Configurazione) che deve chiamare il metodo GetValue() nell’implementazione dell’accessor get e il metodo SetValue() nell’implementazione dell’accessor set. Oltre alla proprietà di dipendenza ConfigurazioneProperty deve essere creata una proprietà di dipendenza TestProperty che prende in ingresso un valore di tipo bool che specifica se si sta effettuando un test dell’applicazione o meno. Se è in esecuzione il test occorre impedire il salvataggio delle modifiche effettuate ad una form visualizzata nel controllo Preview. Poiché non è possibile accedere agli oggetti appartenenti al template del controllo da dentro il costruttore della classe, viene fatto l’override del metodo OnApplyTemplate(). L’unico modo per accedere in modo sicuro agli oggetti del template e catturare gli eventi qualora specificati si ha quando viene applicato il template. In questa occasione si può ottenere un oggetto dal template utilizzando il metodo GetTemplateChild(). Ciò in realtà non basta perché è necessario specificare anche attraverso l’attributo TemplatePart i nomi e i tipi degli oggetti a cui si desidera accedere. In questo modo, dopo aver recuperato gli oggetti presenti nel template del controllo definito nel generic.xaml, vengono assegnati i gestori all’evento di chiusura del pannello e agli eventi di rilascio del pulsante sinistro del mouse sulle immagini utilizzate per salvare i dati, e aggiungere tabs e fieldsets. L’ultima operazione da effettuare all’interno del metodo OnApplyTemplate() è la chiamata al metodo Render() che disegna sul pannello i tab, i fieldset e i controlli della form dinamica previsti nella configurazione ASCX assegnata alla proprietà di dipendenza del custom control creato. Quando avviene il render dei fieldset e dei controlli sul pannello, viene assegnato loro il gestore per le operazioni di drag and drop. [TemplatePart(Name
[TemplatePart(Name
[TemplatePart(Name
[TemplatePart(Name
=
=
=
=
"rootElement", Type = typeof(FrameworkElement))]
"Container", Type = typeof(FrameworkElement))]
"TabContainer", Type = typeof(StackPanel))]
"btnClose", Type = typeof(Button))]
[TemplatePart(Name
[TemplatePart(Name
[TemplatePart(Name
[TemplatePart(Name
[TemplatePart(Name
[TemplatePart(Name
=
=
=
=
=
=
"MasterPanel", Type = typeof(Canvas))]
"Personalizzazione", Type = typeof(TextBlock))]
"Status", Type = typeof(TextBlock))]
"SaveImg", Type = typeof(SaveImageControl))]
"TabImg", Type = typeof(TabImageControl))]
"FieldImg", Type = typeof(FieldsetImageControl))]
public class Preview : Control, ISaveCancelBtn
{
FrameworkElement rootElement;
FrameworkElement container;
public StackPanel TabContainer;
public Canvas MasterPanel;
TextBlock Personalizzazione;
public TextBlock Status;
SaveImageControl SaveImg;
TabImageControl TabImg;
FieldsetImageControl FieldImg;
Image imgSave;
Image btnNF;
Image btnNT;
Button btnClose;
//Variabili per la gestione del drag & drop
public bool isMouseCaptured;
public double mouseVerticalPosition;
public double mouseHorizontalPosition;
public Preview()
: base()
{
this.DefaultStyleKey = typeof(Preview);
}
/// ModalContainer (quando il controllo è aperto su form modale sarebbe
un riferimento alla form stessa)
private ModalDialog _MDContainer;
public ModalDialog MDContainer
{
get { return _MDContainer; }
set {
_MDContainer = value;
if(this.MasterPanel!=null)
Render();
}
}
public Popup MDPopup { get; set; }
public static readonly DependencyProperty ConfigurazioneProperty =
DependencyProperty.Register("Configurazione",
typeof(ConfigurazioneASCX),
typeof(Preview),
new
PropertyMetadata(new
PropertyChangedCallback(OnConfigurazioneChanged)));
public ConfigurazioneASCX Configurazione
{
get
{
(ConfigurazioneASCX)this.GetValue(ConfigurazioneProperty); }
set
{
base.SetValue(ConfigurazioneProperty, value);
}
}
return
public
static
readonly
DependencyProperty
TestProperty
DependencyProperty.Register("Test", typeof(bool), typeof(Preview), null);
=
public bool Test
{
get { return (bool)this.GetValue(TestProperty); }
set
{
base.SetValue(TestProperty, value);
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.rootElement
=
this.GetTemplateChild("rootElement")
as
FrameworkElement;
this.container
=
this.GetTemplateChild("Container")
as
FrameworkElement;
this.TabContainer
=
this.GetTemplateChild("TabContainer")
as
StackPanel;
this.Personalizzazione = this.GetTemplateChild("Personalizzazione")
as TextBlock;
this.Status = this.GetTemplateChild("Status") as TextBlock;
this.SaveImg = this.GetTemplateChild("SaveImg") as SaveImageControl;
this.imgSave = this.SaveImg.imgSave;
this.TabImg = this.GetTemplateChild("TabImg") as TabImageControl;
this.btnNT = this.TabImg.btnNT;
this.FieldImg
=
this.GetTemplateChild("FieldImg")
as
FieldsetImageControl;
this.btnNF = this.FieldImg.btnNF;
if (Test)
this.imgSave.Visibility = Visibility.Collapsed;
this.MasterPanel = this.GetTemplateChild("MasterPanel") as Canvas;
this.btnClose = this.GetTemplateChild("btnClose") as Button;
Personalizzazione.Text
=
Configurazione.ASCX
+
"
Configurazione.Personalizzazione;
btnClose.Click += new RoutedEventHandler(btnClose_Click);
imgSave.MouseLeftButtonUp
+=
MouseButtonEventHandler(imgSave_MouseLeftButtonUp);
btnNF.MouseLeftButtonUp
+=
MouseButtonEventHandler(btnNF_MouseLeftButtonUp);
btnNT.MouseLeftButtonUp
+=
MouseButtonEventHandler(btnNT_MouseLeftButtonUp);
Render();
}
-
"
+
new
new
new
void btnNT_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
List<object> param = new List<object>();
param.Add(this);
AddNewTabEvent atEvent = new AddNewTabEvent("AddNewTab", param);
atEvent.dispatch();
}
void btnNF_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
List<object> param = new List<object>();
param.Add(this);
AddNewFieldsetEvent
afEvent
=
AddNewFieldsetEvent("AddNewFieldset", param);
afEvent.dispatch();
}
new
void imgSave_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
List<object> param = new List<object>();
param.Add(this);
SavePPConfigEvent scEvent = new SavePPConfigEvent("SavePPConfig",
param);
scEvent.dispatch();
}
void btnClose_Click(object sender, RoutedEventArgs e)
{
List<object> param = new List<object>();
param.Add(this);
ClosePanelPreviewEvent
cwEvent
ClosePanelPreviewEvent("ClosePanelPreview", param);
cwEvent.dispatch();
}
=
new
public void Render()
{
Manager.setPanelContainer(this.MasterPanel);
Manager.setTabContainer(this.TabContainer);
double defaultX = 1;
double defaultY = 1;
int nTabs = 0;
if (Manager.getTabsFieldsetsCurrentConfig().Tabs.Count > 0)
nTabs = Manager.getTabsFieldsetsCurrentConfig().Tabs.Count - 1;
//creazione dei canvas corrispondenti ai tabs e dei tabs
for (int i = 0; i <= nTabs; i++)
{
ServiceControls.NETACanvas
nc
=
ManagerParametriDF.ServiceControls.NETACanvas();
new
nc.KeyIndex = i;
if (Manager.getTabsFieldsetsCurrentConfig().Tabs.Count > 0)
nc.KeyMembership
Manager.getTabsFieldsetsCurrentConfig().Tabs[i].Index;
else
nc.KeyMembership = 0;
if (i == 0)
nc.Visibility = Visibility.Visible;
else
nc.Visibility = Visibility.Collapsed;
this.MasterPanel.Children.Add(nc);
if (Manager.getTabsFieldsetsCurrentConfig().Tabs.Count > 0)
AddTab(Manager.getTabsFieldsetsCurrentConfig().Tabs[i],
this.TabContainer);
}
//disegno i fieldsets
foreach
(Fieldset
fs
Manager.getTabsFieldsetsCurrentConfig().Fieldsets)
{
DisegnaFieldset(fs);
}
//disegno i controlli
foreach (ParametriControllo par in Configurazione.Controlli)
{
par.pathASCX = Configurazione.PathASCX;
DisegnaControllo(par,
this.MasterPanel,
ref
defaultX,
defaultY);
}
}
=
i,
in
ref
private void AddTab(Tab tab, int index, StackPanel container)
{
//inizializzo newIndex e oldIndex ad index (le variabili per gestire
il cambio ordine dei tabs assumono lo stesso valore
//dell'indice di lettura cioè l'ordine con il quale vengono letti i
tabs)
tab.newIndex = index;
tab.oldIndex = index;
ManagerParametriDF.ServiceControls.TabItem
btn
=
new
ManagerParametriDF.ServiceControls.TabItem(tab, index, container);
btn.MouseLeftButtonUp
+=
new
MouseButtonEventHandler(btn_MouseLeftButtonUp);
container.Children.Add(btn);
}
/// Evento selezione tabs
void btn_MouseLeftButtonUp(object sender, RoutedEventArgs e)
{
List<object> param = new List<object>();
param.Add(this.TabContainer);
param.Add(sender);
param.Add(this.MasterPanel);
SelectTabEvent stEvent = new SelectTabEvent("SelectTab", param);
stEvent.dispatch();
}
/// Metodo per inserire i fieldset previsti in configurazione
public void DisegnaFieldset(Fieldset fs)
{
ControlsDesigner.NETAFieldset
nfs
=
ManagerParametriDF.ControlsDesigner.NETAFieldset(fs);
AddHandlerDragDrop(nfs, null);
}
new
/// Metodo per disegnare i controlli previsti
private
void
DisegnaControllo(ParametriControllo
par,
Canvas
PannelloTarget, ref double defaultX, ref double defaultY)
{
switch (par.TipoControllo)
{
case "LabText":
AddHandlerDragDrop(new
ManagerParametriDF.ControlsDesigner.LabTextBox(par, ref defaultX, ref defaultY,
PannelloTarget), PannelloTarget);
break;
case "Button":
AddHandlerDragDrop(new
ManagerParametriDF.ControlsDesigner.NETAButton(par, ref defaultX, ref defaultY,
PannelloTarget), PannelloTarget);
break;
case "LabMatcher":
AddHandlerDragDrop(new
ManagerParametriDF.ControlsDesigner.LabMatcher(par, ref defaultX, ref defaultY,
PannelloTarget), PannelloTarget);
break;
case "TabPane":
AddHandlerDragDrop(new
ManagerParametriDF.ControlsDesigner.NETACollapsiblePanel(par, ref defaultX, ref
defaultY, PannelloTarget), PannelloTarget);
break;
case "Grid":
AddHandlerDragDrop(new
ManagerParametriDF.ControlsDesigner.NETAGrid(par,
PannelloTarget), PannelloTarget);
break;
ref
defaultX,
ref
defaultY,
case "TextBox":
AddHandlerDragDrop(new
ManagerParametriDF.ControlsDesigner.NETATextBox(par, ref defaultX, ref defaultY,
PannelloTarget), PannelloTarget);
break;
case "CheckBox":
AddHandlerDragDrop(new
ManagerParametriDF.ControlsDesigner.NETACheckBox(par,
defaultY, PannelloTarget), PannelloTarget);
break;
ref
defaultX,
ref
case "Label":
AddHandlerDragDrop(new
ManagerParametriDF.ControlsDesigner.NETALabel(par, ref defaultX, ref defaultY,
PannelloTarget), PannelloTarget);
break;
case "Combo":
AddHandlerDragDrop(new
ManagerParametriDF.ControlsDesigner.NETACombo(par, ref defaultX, ref defaultY,
PannelloTarget), PannelloTarget);
break;
case "LabCombo":
AddHandlerDragDrop(new
ManagerParametriDF.ControlsDesigner.LabCombo(par,
PannelloTarget), PannelloTarget);
break;
ref
case "DateTime":
AddHandlerDragDrop(new
ManagerParametriDF.ControlsDesigner.NETACalendar(par,
defaultY, PannelloTarget), PannelloTarget);
break;
defaultX,
ref
ref
defaultY,
defaultX,
ref
case "LabCalendar":
AddHandlerDragDrop(new
ManagerParametriDF.ControlsDesigner.LabCalendar(par, ref defaultX, ref defaultY,
PannelloTarget), PannelloTarget);
break;
case "Domiciliazione":
AddHandlerDragDrop(new
ManagerParametriDF.ControlsDesigner.NETADomiciliazione(par,
defaultY, PannelloTarget), PannelloTarget);
ref
defaultX,
ref
break;
default:
break;
}
}
/// Metodo per aggiungere al controllo il behaviour del drag & drop
private void AddHandlerDragDrop(UserControl ctrl, Canvas pannelloTarget)
{
ctrl.MouseLeftButtonDown
+=
new
MouseButtonEventHandler(FakeControl_LeftMouseButtonDownEventHandler);
ctrl.MouseLeftButtonUp
+=
new
MouseButtonEventHandler(FakeControl_LeftMouseButtonUpEventHandler);
ctrl.MouseMove
+=
new
MouseEventHandler(FakeControl_MouseMoveEventHandler);
//aggancio
evento
al
ridimensionamento
di
un
subcontrollo
del
controllo
(ctrl
as
IFakeControl).SubControlSizeChanged
EventHandler(PanelPreview_SubControlSizeChanged);
}
+=
void PanelPreview_SubControlSizeChanged(object sender, EventArgs e)
{
List<object> param = new List<object>();
param.Add(sender);
param.Add(this.Status);
SubControlResizeEvent
scEvent
=
SubControlResizeEvent("SubControlResize", param);
scEvent.dispatch();
new
new
}
//Gestori eventi per il drag & drop
void FakeControl_MouseMoveEventHandler(object sender, MouseEventArgs e)
{
List<object> param = new List<object>();
param.Add(this);
param.Add(sender);
param.Add(e);
MouseMoveEvent mmEvent = new MouseMoveEvent("MouseMove", param);
mmEvent.dispatch();
}
void
FakeControl_LeftMouseButtonUpEventHandler(object
MouseButtonEventArgs e)
{
List<object> param = new List<object>();
param.Add(this);
param.Add(sender);
MouseButtonUpEvent
mbuEvent
=
MouseButtonUpEvent("MouseButtonUp", param);
mbuEvent.dispatch();
}
sender,
void
FakeControl_LeftMouseButtonDownEventHandler(object
MouseButtonEventArgs e)
{
List<object> param = new List<object>();
param.Add(this);
param.Add(sender);
param.Add(e);
MouseButtonDownEvent
mbdEvent
=
MouseButtonDownEvent("MouseButtonDown", param);
mbdEvent.dispatch();
}
sender,
new
new
} Dopo aver creato il custom control Preview, poichè questo deve essere visualizzato all’interno di una finestra di popup, inseriamo un componente di tipo Popup all’interno del codice xaml della classe Page e all’interno del codice xaml della classe TestStruttura. Inizialmente la proprietà IsOpen del componente Popup viene impostata a ‘false’ perché, fino a quando non viene richiesta la preview della form, esso deve rimanere chiuso. All’interno del Popup inseriamo il controllo Preview creato, assegnando alla sua proprietà Visibility il valore ‘Collapsed’ per non visualizzare l’elemento finchè non viene richiesto. Inoltre, la proprietà di dipendenza Configurazione viene legata in binding all’oggetto ConfigASCX, contenuto nella classe DFModel, che contiene la configurazione ascx a partire dalla quale il controllo fa il rendering della form. <Popup x:Name="popup" IsOpen="False">
<ContentControl>
<myCustom:Preview
x:Name="preview1"
Visibility="Collapsed"
Test="False" Configurazione="{Binding ConfigASCX, Mode=TwoWay}" Grid.Row="2"/>
</ContentControl>
</Popup> Quando all’interno dell’interfaccia di presentazione dell’applicazione viene premuto il pulsante Preview viene scatenato un evento click che viene catturato dal suo gestore, il quale richiama il metodo btnPreview_Click(). All’interno di tale metodo viene creata una lista di oggetti contenente il controllo Popup e il controllo Preview. Tale lista viene passata come parametro al costruttore dell’evento di tipo PersPreviewEvent. Successivamente, l’evento creato viene inviato tramite l’operazione di dispatch() e catturato dal componente DFController. Quest’ultimo verifica se per l’evento catturato è già stato registrato un comando e, in tal caso, richiama il metodo execute() del comando che, nel caso in questione, è un’istanza della classe PersPreviewCommand. Internamente al metodo, innanzitutto viene fatto un controllo per verificare se è stata selezionata una personalizzazione per l’ascx corrente all’interno della sezione Personalizzazioni ASCX, in caso contrario non esegue alcuna istruzione, e se è presente una configurazione ascx valida, in caso contrario viene ricaricata la configurazione corrente dei controlli, dei tab e dei fieldset. Per ricaricare tali configurazioni viene istanziato un Delegate appartenente alla classe PersPreviewDelegate e vengono utilizzati i suoi metodi per poter comunicare con un listener in ascolto sul web server. Per fare ciò il delegate utilizza un oggetto WebClient che consente di effettuare chiamate asincrone. public class PersPreviewDelegate : GenericDelegate
{
public PersPreviewDelegate(IResponder responder)
: base(responder)
{
}
public void RefreshTabsFieldsetsByPersonalizzazione(string gerCod,
string oggCod)
{
//chiamata al listner per ottenere la collezione di tabs e fieldset
per la personalizzazione corrente
string PostUrl2 =
"http://******/listnerDF.aspx?op=getTabsFieldset&par=" + gerCod + "&par2=" +
oggCod;
// Chiamata asincrona al web service attraverso il listner
WebClient DFService2 = new WebClient();
DFService2.DownloadStringCompleted += new
DownloadStringCompletedEventHandler(DFService2_DownloadParsCompleted);
DFService2.DownloadStringAsync(Manager.getPostAddress(PostUrl2));
}
public void RefreshParsByPersonalizzazione(string gerCod, string oggCod)
{
//chiamata al listner per ottenere i parametri per una
personalizzazione
string PostUrl = "http://******/listnerDF.aspx?op=getPars&par=" +
gerCod + "&par2=" + oggCod;
// Chiamata asincrona al web service attraverso il listner
WebClient DFService = new WebClient();
DFService.DownloadStringCompleted += new
DownloadStringCompletedEventHandler(DFService_DownloadParsCompleted);
DFService.DownloadStringAsync(Manager.getPostAddress(PostUrl));
}
//callBack chiamata al carica Tabs e Fieldset per personalizzazione
void DFService2_DownloadParsCompleted(object sender,
DownloadStringCompletedEventArgs e)
{
string returnType = "Return getTabsFieldset";
if (null != e.Error)
((PersPreviewCommand)responder).onFault("Exception! (" +
e.Error.Message + ")", returnType);
else
((PersPreviewCommand)responder).onResult(e.Result, returnType);
}
//callBack chiamata al carica parametri per personalizzazione
void DFService_DownloadParsCompleted(object sender,
DownloadStringCompletedEventArgs e)
{
string returnType = "Return getParams";
if (null != e.Error)
((PersPreviewCommand)responder).onFault("Exception! (" +
e.Error.Message + ")", returnType);
else
((PersPreviewCommand)responder).onResult(e.Result, returnType);
}
} Quando il web service ha terminato le elaborazioni, ritorna i risultati al chiamante il quale, se non si sono verificati errori, manda in esecuzione il metodo onResult() del comando per elaborare ed utilizzare i dati prodotti dal web service. Dopo la verifica delle configurazioni, all’interno del metodo execute() vengono recuperati dalla lista di oggetti passata all’evento i componenti Popup e Preview. Infine, viene richiamato il metodo OpenPreview() per l’apertura della preview all’interno della finestra popup. Prima di richiamare il metodo show() della classe ModalDialog che consente l’apertura della modale di preview, viene impostata a ‘Visible’ la proprietà Visibility del controllo Preview. public class PersPreviewCommand : SilverlightPattern.Business.Command.ICommand,
IResponder
{
private DFModel model = DFModel.getInstance();
private ServiceControls.ModalDialog md;
public
EventHandler<ManagerParametriDF.ServiceControls.ModalDialogClosedEventArgs>
delegatoChiusuraPopUp;
private Dispatcher currentDispatcher =
Application.Current.RootVisual.Dispatcher;
private PersPreviewDelegate ppDelegate;
private XDocument xmlParsCorrenti;
private XDocument xmlTabsFieldsetCorrenti;
private Personalizzazione PersonalizzazioneCorrente;
private GenericEvent genEvent;
#region ICommand Membri di
public void execute(GenericEvent genericEvent)
{
SavedTP = false;
this.genEvent = genericEvent;
if (model.ASCXPersSelectedIndex == -1)
return;
//Passaggio lista parametri correnti
if (Manager.getCurrentConfig() == null)
{//ricarico la configurazione corrente dei controlli
//ReLoadParsASCXList();
ppDelegate = new PersPreviewDelegate(this);
ppDelegate.RefreshParsByPersonalizzazione(model.ASCXPersList[model.ASCXPersSelec
tedIndex].GerCod, model.ASCXPersList[model.ASCXPersSelectedIndex].OggCod);
ParsRefreshed = false;
}
if (Manager.getTabsFieldsetsCurrentConfig() == null)
{//ricarico la configurazione corrente dei TabsFieldsets
//SetTabsFieldsetsCorrenti();
ppDelegate = new PersPreviewDelegate(this);
ppDelegate.RefreshTabsFieldsetsByPersonalizzazione(model.ASCXPersList[model.ASCX
PersSelectedIndex].GerCod,
model.ASCXPersList[model.ASCXPersSelectedIndex].OggCod);
TabsFieldRefreshed = false;
}
if (TabsFieldRefreshed == true && ParsRefreshed == true)
{
PersonalizzazioneCorrente =
model.ASCXPersList[model.ASCXPersSelectedIndex];
Preview preview = null;
Popup popup = null;
foreach (object par in genericEvent.Params)
{
if (par is Preview)
preview = (par as Preview);
else
popup = (par as Popup);
}
OpenPreview(preview, popup);
}
}
#endregion
/// Apertura Preview
private void OpenPreview(Preview preview, Popup popup)
{
SolidColorBrush bb = new SolidColorBrush();
bb.Color = Colors.LightGray;
//apertura popup
md = new ManagerParametriDF.ServiceControls.ModalDialog();
preview.Visibility = Visibility.Visible;
md.Show(bb, 0.5, preview, popup);
//aggancio evento di callback alla chiusura della popup
delegatoChiusuraPopUp = new
EventHandler<ManagerParametriDF.ServiceControls.ModalDialogClosedEventArgs>(Moda
lDialog_Closed);
md.Closed += delegatoChiusuraPopUp;
}
public void onResult(object result, string returnType)
{
if (returnType == "Return getParams")
{
//this.titParametri.Text = OrigtitoloParametri;
DisplayParsASCX((string)result);
Manager.WaitingFormOff();
ParsRefreshed = true;
OpenedPreview = true;
}
else
{
SetTabsFieldsetPers((string)result);
TabsFieldRefreshed = true;
OpenedPreview = true;
}
if (ParsRefreshed == true && TabsFieldRefreshed == true &&
OpenedPreview == true
&& SavedOk == false && SavedTP == false)
{
OpenedPreview = false;
PersonalizzazioneCorrente =
model.ASCXPersList[model.ASCXPersSelectedIndex];
Preview preview = null;
Popup popup = null;
foreach (object par in this.genEvent.Params)
{
if (par is Preview)
preview = (par as Preview);
else
popup = (par as Popup);
}
OpenPreview(preview, popup);
}
SavedOk = false;
}
private void DisplayParsASCX(string xmlContent)
{
xmlContent =
xmlContent.Replace("xmlns=\"http://tempuri.org/StrutturaGerarchieFinale.xsd\"",
"");
xmlParsCorrenti = XDocument.Parse(xmlContent);
ReLoadParsASCXList();
}
private void ReLoadParsASCXList()
{
//svuoto e riassegno la source alla lista ASCXPersParList
model.ASCXPersParList = null;
model.ASCXPersParSelectedIndex = -1;
model.ASCXPersParList = (List<Parametro>)getListaParsASCX();
}
/// Metodo che ricostruisce la lista di oggetti parametro
private System.Collections.IEnumerable getListaParsASCX()
{
Parametro par;
//lista.Clear();
List<Parametro> lista = new List<Parametro>();
var itemsPar = from ParASCX in
xmlParsCorrenti.Root.Elements("ParOut")
select ParASCX;
////ricostruzione della classe
foreach (XElement per in itemsPar)
{
par = new Parametro();
par.ParCod = (string)per.Element("ParCod").Value;
par.Vers = (string)per.Element("Vers").Value;
par.Default = (string)per.Element("Valore").Value;
par.Ereditato =
System.Convert.ToBoolean(per.Element("Ereditato").Value);
if (par.ParCod.EndsWith("_sta"))
par.MasterPar = true;
else
par.MasterPar = false;
lista.Add(par);
}
model.ConfigASCX = Manager.GetConfigurazioneFromParametri(lista,
PersonalizzazioneCorrente);
return lista;
}
private void SetTabsFieldsetPers(string xmlContent)
{
xmlContent =
xmlContent.Replace("xmlns=\"http://tempuri.org/StrutturaGerarchieFinale.xsd\"",
"");
xmlTabsFieldsetCorrenti = XDocument.Parse(xmlContent);
SetTabsFieldsetsCorrenti();
}
/// Metodo che a partire dall'xml recuperato dal listner ricostruisce
/// una struttura di Tabs e Fieldset presenti nella personalizzazione
corrente
private void SetTabsFieldsetsCorrenti()
{
List<Tab> tabs = new List<Tab>();
List<Fieldset> fieldsets = new List<Fieldset>();
Tab tab;
Fieldset fieldset;
var itemsTab = from Tabs in
xmlTabsFieldsetCorrenti.Root.Element("Tabs").Elements("Tab")
select Tabs;
////ricostruzione della classe
foreach (XElement tt in itemsTab)
{
tab = new Tab();
tab.Index = System.Convert.ToInt32(tt.Attribute("Index").Value);
tab.newIndex =
System.Convert.ToInt32(tt.Attribute("Index").Value);
tab.KeyVoc = tt.Attribute("Cap").Value;
tabs.Add(tab);
}
var itemsFieldset = from Fieldsets in
xmlTabsFieldsetCorrenti.Root.Element("Fieldsets").Elements("Fieldset")
select Fieldsets;
////ricostruzione della classe
foreach (XElement ff in itemsFieldset)
{
fieldset = new Fieldset();
fieldset.Id = (string)ff.Attribute("id").Value;
fieldset.KeyVoc = (string)ff.Attribute("KeyVoc").Value;
fieldset.Top = System.Convert.ToInt32(ff.Attribute("Top").Value)
- 10; //offset dovuto a caratteristiche del fake control
fieldset.Left =
System.Convert.ToInt32(ff.Attribute("Left").Value);
fieldset.Height =
System.Convert.ToInt32(ff.Attribute("Height").Value) + 10; //offset dovuto a
caratteristiche del fake control
fieldset.Width =
System.Convert.ToInt32(ff.Attribute("Width").Value);
fieldset.TabContainerIndex =
System.Convert.ToInt32(ff.Attribute("TabContainerIndex").Value);
fieldset.BgColor = (string)ff.Attribute("Color").Value;
fieldsets.Add(fieldset);
}
Manager.setTabsFieldsetsCurrentConfig(Manager.GetTabsFieldsetPersonalizzazioneCo
rrente(tabs, fieldsets, PersonalizzazioneCorrente));
}
} Nella figura sottostante viene mostrato un esempio di visualizzazione, all’interno di una finestra popup, di una form dinamica relativa ad una personalizzazione creata per la gerarchia Griglia.ascx. All’interno della Preview è possibile distinguere i tabs ‘Test Form Dinamica’ e ‘Anagrafica’, i fieldsets che sono quelli colorati con etichetta ‘Personalizzazione?’, i vari controlli (soprattutto TextBox) contenuti all’interno dei fieldsets, e i vari pulsanti per il salvataggio dei dati, per la chiusura della finestra popup, e per l’aggiunta di fieldsets e tabs. Figura 47 – Preview di una form dinamica relativa ad una configurazione ascx Per tutti i componenti presenti all’interno della Preview è possibile visualizzare i dettagli facendo click sul pulsante a forma di stellina rossa presente su ciascuno di essi. Supponiamo di voler quindi visualizzare i dettagli di un componente di tipo fieldset. Nel momento in cui viene premuto il pulsante per la visualizzazione dei dettagli, viene generato un evento e il gestore ad esso associato richiama il metodo open_details() della classe NETAFieldset. Internamente al metodo viene creata una lista di oggetti all’interno della quale stavolta viene passata l’istanza stessa della classe in questione. Poi avviene la creazione dell’evento di business di tipo OpenFieldDetailEvent , passando come parametro al suo costruttore la lista creata precedentemente; infine viene inviato l’evento creato. Dopo che l’evento è stato inviato, viene catturato dal Controller, il quale verifica se per l’evento è già stato registrato un comando e, se ciò accade, lancia il metodo execute() del relativo comando, appartenente in tal caso alla classe OpenFieldDetailCommand. private void open_details(object sender, MouseButtonEventArgs e)
{
List<object> param = new List<object>();
param.Add(this);
OpenFieldDetailEvent ctEvent = new
OpenFieldDetailEvent("OpenFieldDetail", param);
ctEvent.dispatch();
} All’interno del metodo execute(), innanzitutto viene recuperato l’oggetto di tipo fieldset dalla lista di oggetti, poi viene creata una istanza della classe ManagerFieldset, che è uno user control implementato per la gestione dei dettagli del fieldset selezionato. Tale controllo viene, dunque, aperto in modale all’interno di una finestra popup richiamando il metodo Show() della classe ModalDialog. public class OpenFieldDetailCommand :
SilverlightPattern.Business.Command.ICommand, IResponder
{
public
EventHandler<ManagerParametriDF.ServiceControls.ModalDialogClosedEventArgs>
delegatoChiusuraPopUp;
ServiceControls.ModalDialog md;
NETAFieldset fieldset;
#region ICommand Membri di
public void execute(GenericEvent genericEvent)
{
foreach (object param in genericEvent.Params)
{
if (param is NETAFieldset)
fieldset = (param as NETAFieldset);
}
//istanza classe ManagerTabs
ServiceControls.ManagerFieldset dp = new
ManagerParametriDF.ServiceControls.ManagerFieldset(fieldset.ItemFieldset);
SolidColorBrush bb = new SolidColorBrush();
bb.Color = Colors.LightGray;
//apertura popup
md = new ManagerParametriDF.ServiceControls.ModalDialog();
md.Show(bb, 0.5, dp);
//aggancio evento di callback alla chiusura della popup
delegatoChiusuraPopUp = new
EventHandler<ManagerParametriDF.ServiceControls.ModalDialogClosedEventArgs>(Moda
lDialog_Closed);
md.Closed += delegatoChiusuraPopUp;
}
#endregion
#region IResponder Membri di
public void onFault(string errorMessage)
{
}
public void onResult(object result)
{
}
#endregion
/// CallBack alla chiusura di DetailsPan con salvataggio
void ModalDialog_Closed(object sender,
ManagerParametriDF.ServiceControls.ModalDialogClosedEventArgs e)
{
//rimozione handler evento di chiusura della popup
md.Closed -= delegatoChiusuraPopUp;
delegatoChiusuraPopUp = null;
if (e.Data == null)//chiudo ManagerFieldset senza ridisegnare il
controllo
return;
////prima controllo se il tab deve essere cancellato e se sì lo
cancello e esco
if ((e.Data as Fieldset).Stato == "del")
{
Manager.getTabCanvas(Manager.getPanelContainer(),
fieldset.currentMembership).TabContainer.Children.Remove(fieldset);
return;
}
//Riporto eventuali modifiche
////La caption la riassegna sempre
fieldset.HeightControl = fieldset.ItemFieldset.Height;
fieldset.WidthControl = fieldset.ItemFieldset.Width;
Canvas.SetTop(fieldset, fieldset.ItemFieldset.Top);
Canvas.SetLeft(fieldset, fieldset.ItemFieldset.Left);
fieldset.ctrlFieldset.BordoFieldset.Background =
(Brush)Manager.getBrushFromHexString(fieldset.ItemFieldset.BgColor);
fieldset.ctrlFieldset.CaptionFieldset =
Manager.RisolviKey_inVocabolarioDF(fieldset.ItemFieldset.KeyVoc);
if (fieldset.ItemFieldset.TabContainerIndex ==
fieldset.currentMembership)
return;
//cambio del tab....
Manager.getTabCanvas(Manager.getPanelContainer(),
fieldset.currentMembership).TabContainer.Children.Remove(fieldset);
fieldset.currentMembership =
fieldset.ItemFieldset.TabContainerIndex;
Manager.getTabCanvas(Manager.getPanelContainer(),
fieldset.currentMembership).TabContainer.Children.Add(fieldset);
Manager.getTabCanvas(Manager.getPanelContainer(),
fieldset.currentMembership).TabContainer.InvalidateArrange();
}
} Figura 49 – Finestra di gestione dei parametri dei fieldset Come è possibile notare dalla figura, nella finestra di visualizzazione dei dettagli (o parametri) di un componente fieldset è possibile assegnare ad esso un’etichetta, scegliendola tra quelle presenti all’interno del vocabolario di profilazione caricato all’avvio dell’applicazione, è possibile poi gestire le sue dimensioni e la sua posizione all’interno del pannello, è possibile selezionare l’indice del tab all’interno del quale deve essere collocato e il suo colore di riempimento. Non è visibile nella figura, ma è presente inoltre un pulsante ‘Elimina’ per l’eliminazione del fieldset dal pannello; le modifiche apportate ai vari parametri del componente possono poi essere salvate utilizzando il relativo pulsante di salvataggio dati. Quando all’interno della finestra di gestione dei parametri di un fieldset si fa click sul pulsante di aggiunta di un etichetta, viene richiamato dal gestore dell’evento scatenato il metodo Open_VocDF() presente all’interno della classe relativa alla sezione Par_Cap. All’interno del metodo viene creata una lista con i componenti UI necessari per l’elaborazione e viene passata come parametro al costruttore dell’evento di business di classe OpenVocabEvent. Successivamente l’evento creato viene inviato tramite il metodo dispatch(), ereditato dalla classe GenericEvent, e catturato dal componente Controller. Tale componente verifica se l’evento è già registrato all’interno della lista che esso possiede e, in tal caso, esegue il metodo execute() del comando di classe OpenVocabCommand ad esso associato. private void Open_VocDF(object sender, MouseButtonEventArgs e)
{
List<object> param = new List<object>();
param.Add(this.txtKey);
param.Add(this.txtValore);
OpenVocabEvent ovEvent = new OpenVocabEvent("OpenVocab", param);
ovEvent.dispatch();
}
All’interno di tale metodo, dopo aver recuperato i componenti UI (in tal caso le TextBox relative alla chiave e al valore dell’etichetta da assegnare) dalla lista di oggetti, viene creata un’istanza della classe ComboVocabolario, che è uno user control utilizzato per la selezione di una coppia chiave‐valore tra quelle fornite nella lista caricata all’avvio. Tale controllo offre anche la possibilità di effettuare su tutte le coppie una ricerca per valore, consentendo così di visualizzare solo gli elementi i cui valori iniziano con le lettere inserite nella TextBox di ricerca. Il controllo viene poi aperto all’interno di una finestra popup richiamando il metodo Show() della classe ModalDialog. Infine viene agganciato l’evento di callback alla chiusura della popup. public class OpenVocabCommand : SilverlightPattern.Business.Command.ICommand,
IResponder
{
public
EventHandler<ManagerParametriDF.ServiceControls.ModalDialogClosedEventArgs>
delegatoChiusuraPopUp;
ServiceControls.ModalDialog md;
TextBox txtKey, txtValore;
DFModel model = DFModel.getInstance();
#region ICommand Membri di
public void execute(GenericEvent genericEvent)
{
txtKey = (genericEvent.Params[0] as TextBox);
txtValore = (genericEvent.Params[1] as TextBox);
Popup popup = new Popup();
model.Griglia.Children.Add(popup);
//istanza classe DetailsPan
ServiceControls.ComboVocabolario managerVoc = new
ManagerParametriDF.ServiceControls.ComboVocabolario();
SolidColorBrush bb = new SolidColorBrush();
bb.Color = Colors.LightGray;
//apertura popup
md = new ManagerParametriDF.ServiceControls.ModalDialog();
md.Show(bb, 0.5, popup, managerVoc);
//aggancio evento di callback alla chiusura della popup
delegatoChiusuraPopUp = new
EventHandler<ManagerParametriDF.ServiceControls.ModalDialogClosedEventArgs>(Moda
lDialogVocabolario_Closed);
md.Closed += delegatoChiusuraPopUp;
}
#endregion
/// CallBack alla chiusura di ModalDialog vocabolario
void ModalDialogVocabolario_Closed(object sender,
ManagerParametriDF.ServiceControls.ModalDialogClosedEventArgs e)
{
//rimozione handler evento di chiusura della popup
md.Closed -= delegatoChiusuraPopUp;
delegatoChiusuraPopUp = null;
if (e.Data == null)//chiudo DetailsPan senza ridisegnare il
controllo
return;
//riporto sul frontend i valori selezionati
txtKey.Text = (e.Data as ItemVocabolarioDFProf).Key;
txtValore.Text = (e.Data as ItemVocabolarioDFProf).Content;
}
} Figura 50 – Finestra di selezione di una chiave da un vocabolario di profilazione Abbiamo già detto in precedenza che all’interno della Preview è possibile aggiungere un nuovo fieldset al pannello di componenti; in tal caso viene istanziato uno user control di classe ManagerNewFieldset che è quasi identico a quello della classe ManagerFieldset precedentemente descritto. L’unica differenza tra i due controlli sta nel fatto che il controllo che viene istanziato ora non offre all’utente la possibilità di eliminare un fieldset, poiché il suo obiettivo è creare un nuovo fieldset con tutte le sue caratteristiche parametriche. Figura 51 – Finestra per l’aggiunta di un nuovo fieldset al pannello Un’altra funzionalità fornita dal componente di Preview è la possibilità di aggiungere al pannello un nuovo tab. In particolare quando si fa click sul pulsante di aggiunta tab, l’evento click viene inviato e catturato dal suo gestore che richiama il metodo btnNT_MouseLeftButtonUp() della classe Preview. In tale metodo l’istanza della classe viene inserita nella lista di oggetti che viene passata al costruttore dell’evento di classe AddNewTabEvent. Poi l’evento viene inviato e catturato dall’istanza della classe DFController creata all’avvio dell’applicazione; questa controlla se l’evento è già stato registrato con il relativo comando e, in caso positivo, manda in esecuzione il comando AddNewTabCommand richiamando il suo metodo execute(). void btnNT_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
List<object> param = new List<object>();
param.Add(this);
AddNewTabEvent atEvent = new AddNewTabEvent("AddNewTab", param);
atEvent.dispatch();
} Internamente al metodo execute(), per prima cosa viene recuperato dalla lista l’oggetto preview necessario per le elaborazioni, poi viene creata un’istanza della classe ManagerNewTab che viene visualizzata in modale all’interno di una finestra popup creata e dimensionata all’interno del metodo Show() appartenente alla classe ModalDialog. Infine viene aggiunto un gestore con associata una funzione di callback per l’evento di chiusura della popup. E’ proprio all’interno di tale funzione di callback che avviene l’effettiva aggiunta al pannello del tab, le cui caratteristiche vengono definite all’interno della classe ManagerNewTab. public class AddNewTabCommand : SilverlightPattern.Business.Command.ICommand,
IResponder
{
public ConfigurazioneASCX configurazioneAttuale;
public
EventHandler<ManagerParametriDF.ServiceControls.ModalDialogClosedEventArgs>
delegatoChiusuraPopUp;
ServiceControls.ModalDialog md;
private Preview preview = null;
#region ICommand Membri di
public void execute(GenericEvent genericEvent)
{
foreach (object param in genericEvent.Params)
{
if (param is Preview)
preview = (param as Preview);
}
if (preview.Configurazione.Personalizzazione == "__Default")
{//se sto facendo la preview di un default non posso aggiungere tabs
o fieldset
//quindi sgancio gli eventi
return;
}
//istanza classe ManagerNewTab
ServiceControls.ManagerNewTab dp = new
ManagerParametriDF.ServiceControls.ManagerNewTab(preview.TabContainer);
SolidColorBrush bb = new SolidColorBrush();
bb.Color = Colors.LightGray;
//apertura popup
md = new ManagerParametriDF.ServiceControls.ModalDialog();
md.Show(bb, 0.5, dp);
//aggancio evento di callback alla chiusura della popup
delegatoChiusuraPopUp = new
EventHandler<ManagerParametriDF.ServiceControls.ModalDialogClosedEventArgs>(Moda
lDialog_Closed);
md.Closed += delegatoChiusuraPopUp;
}
#endregion
/// CallBack alla chiusura di ManagerNewTab
void ModalDialog_Closed(object sender,
ManagerParametriDF.ServiceControls.ModalDialogClosedEventArgs e)
{
//rimozione handler evento di chiusura della popup
md.Closed -= delegatoChiusuraPopUp;
delegatoChiusuraPopUp = null;
if (e.Data == null)//chiudo ManagerNewTab senza aggiungere il nuovo
tab
return;
//aggiunta di un nuovo tab
//se il tab aggiunto è il primo allora aggiungo solo un pulsante
altrimenti aggiungo anche un canvas corrispondente
if (preview.TabContainer.Children.Count == 0)
{
//devo aggiungere il nuovo tab anche nella struttura
TabsFieldsets della classe statica
Manager.getTabsFieldsetsCurrentConfig().Tabs.Add((e.Data) as
Tab);
AddTab(Manager.getTabsFieldsetsCurrentConfig().Tabs[0], 0,
preview.TabContainer);
}
else
{//se NewIndex==oldIndex allora il pulsante va aggiunto in ultima
posizione altrimenti all'indice specificato da newIndex
//prima lo inserisco in ultima posizione, poi se
newIndex!=oldIndex lo sposto
//inserimento del canvas corrispondente al tab
ServiceControls.NETACanvas nc = new
ManagerParametriDF.ServiceControls.NETACanvas();
nc.KeyIndex = ((e.Data) as Tab).oldIndex;
nc.KeyMembership = ((e.Data) as Tab).Index;
nc.Visibility = Visibility.Collapsed;
preview.MasterPanel.Children.Add(nc);
//inserimento del pulsante
Manager.getTabsFieldsetsCurrentConfig().Tabs.Add(e.Data as Tab);
AddTab(((e.Data) as Tab), ((e.Data) as Tab).oldIndex,
preview.TabContainer);
if (((e.Data) as Tab).newIndex != ((e.Data) as Tab).oldIndex)
{//il nuovo tab non è in ultima posizione e và spostato
spostaTab(((e.Data) as Tab).oldIndex, ((e.Data) as
Tab).newIndex);
}
//rende selezionato il tab aggiunto cioè quello in newIndex
position.
Manager.setActiveTab(((e.Data) as Tab).newIndex);
}
}
/// Metodo che aggiunge i pulsanti per i tabs
private void AddTab(Tab tab, int index, StackPanel container)
{
//inizializzo newIndex e oldIndex ad index (le variabili per gestire
il cambio ordine dei tabs assumono lo stesso valore
//dell'indice di lettura cioè l'ordine con il quale vengono letti i tabs
tab.newIndex = index;
tab.oldIndex = index;
ManagerParametriDF.ServiceControls.TabItem btn = new
ManagerParametriDF.ServiceControls.TabItem(tab, index, container);
btn.MouseLeftButtonUp += new
MouseButtonEventHandler(btn_MouseLeftButtonUp);
container.Children.Add(btn);
} All’interno della finestra di aggiunta di un nuovo tab, come quella mostrata in figura, è possibile scegliere dal vocabolario di profilazione l’etichetta che deve essere visualizzata sul pulsante del tab, e selezionare l’indice corrispondente alla posizione in cui il tab deve essere inserito all’interno del set di tabs già esistenti. Figura 52 – Finestra per l’aggiunta di un nuovo tab sulla preview Il custom control Preview creato consente anche il ridimensionamento e le operazioni di drag and drop sui componenti di cui viene fatto il rendering. Dopo aver effettuato le modifiche desiderate su tali componenti, se si è scelto di salvare tali cambiamenti, alla chiusura della finestra popup dentro cui la Preview è visualizzata è necessario controllare cosa è stato modificato e riportare tale modifiche in backend. Tale controllo viene eseguito, all’interno della classe PersPreviewCommand, nel metodo di callback associato al gestore dell’evento di chiusura modale di preview. In particolare si controlla se sono stati alterati solo i tabs, o solo i parametri, oppure se sono stati modificati sia i tabs che i parametri; poi vengono chiamati i rispettivi metodi per riportare in backend le modifiche effettuate. Nel codice sottostante sono riportati i metodi utilizzati per l’allineamento con il backend e i rispettivi callback nel caso in cui sono state effettuate delle modifiche sia ai tabs che ai parametri. private void allineaTabsPars()
{
ManagerSave.SalvaInBackend("salvaTabsPars",
XMLHelper<AlterazioneTabs>.Serialize(getAlteratedTabs()),
XMLHelper<AlterazioneImpianto>.Serialize(getAlteratedConfig()),
CallBackAllineaTabsPars);
}
/// Metodo che ritorna una struttura di tipo AlterazioneTabs per
l'allineamento delle modifiche
/// fatte ai tabs tramite designer in backend
private AlterazioneTabs getAlteratedTabs()
{
AlterazioneTabs risTabs = new AlterazioneTabs();
ConfigurazioneASCX CurrConf = Manager.getCurrentConfig();
risTabs.ASCX = CurrConf.ASCX;
risTabs.Personalizzazione = CurrConf.Personalizzazione;
foreach (Tab tab in Manager.getNewTabsConfig())
{
risTabs.Tabs.Add(tab);
}
foreach (Fieldset fieldset in Manager.getNewFieldsetsConfig())
{
//Correzioni su top ed height dovute al fake control fieldset
fieldset.Height = fieldset.Height - 10;
fieldset.Top = fieldset.Top + 10;
risTabs.FieldSets.Add(fieldset);
}
return risTabs;
}
/// Metodo che ritorna una struttura di tipo ConfigurazioneASCX con la
sola lista dei parametri che sono stati
/// alterati tramite il designer
private AlterazioneImpianto getAlteratedConfig()
{
AlterazioneImpianto risConf = new AlterazioneImpianto();
ConfigurazioneASCX CurrentConf = Manager.getCurrentConfig();
risConf.ASCX = CurrentConf.ASCX;
risConf.Personalizzazione = CurrentConf.Personalizzazione;
foreach (ParametriControllo parCont in CurrentConf.Controlli)
{
foreach (Parametro par in parCont.Parametri)
{
if (par.IsAlterated)
risConf.Parametri.Add(par);
}
}
return risConf;
}
/// Calback di allineamento di tabs e pars
private void CallBackAllineaTabsPars(HttpResponseCompleteEventArgs e)
{
if (e.Response == "OK")
{//salvataggio andato a buon fine
//riallineamento
currentDispatcher.BeginInvoke(delegate()
{
ppDelegate = new PersPreviewDelegate(this);
ppDelegate.RefreshTabsFieldsetsByPersonalizzazione(PersonalizzazioneCorrente.Ger
Cod, PersonalizzazioneCorrente.OggCod);
ppDelegate.RefreshParsByPersonalizzazione(PersonalizzazioneCorrente.GerCod,
PersonalizzazioneCorrente.OggCod);
//comunque per sicurezza cancello le strutture in locale e
obbligo una rilettura in backend e una ricostruzione
Manager.setCurrentConfig(null);
Manager.setTabsFieldsetsCurrentConfig(null);
Manager.msgBox(ManagerConst.getCaptionByLanguageId(ManagerConst.msgSalvataggioOK
));
Manager.WaitingFormOff();
SavedTP = true;
//SavedOK = true;
});
}
else
{//se il salvataggio non è andato a buon fine visualizzo
//un alert e non riallineo e cancello le strutture in modo tale
che rieffettuando
//una preview mantengo ancora vive le modifiche e si può
ritentare il salvataggio
currentDispatcher.BeginInvoke(delegate()
{
Manager.msgBox(ManagerConst.getCaptionByLanguageId(ManagerConst.msgSalvataggioKO
) + e.Response);
Manager.WaitingFormOff();
});
}
} Oltre a visualizzare la preview di una form dinamica relativamente ad una determinata personalizzazione per un ascx, è possibile visualizzare anche quella relativa alla configurazione standard di un ascx facendo click sul pulante ‘Preview Base ASCX’ dell’interfaccia principale appartenente alla classe Page. In tal caso non è consentito all’utente aggiungere nuovi tabs o fieldsets alla configurazione di base. Figura 53 – Pulsante per visualizzare la configurazione di base di un ascx Anche nel caso in cui si è in modalità Test è possibile visualizzare la preview relativa ad una certa sezione ASCX, facendo click sul relativo pulsante, nella figura sottostante ne è riportato un esempio. Figura 54 – Esempio di preview relativa ad una sezione ascx in fase di Test 6.5 MIGRAZIONE ALLA RTW DI SILVERLIGHT Mentre si stava realizzando l’applicazione Silverlight utilizzando la sua versione Beta 2, è stata rilasciata dalla Microsoft la RTW (Release to Web) definitiva di Silverlight 2. Poiché tale versione presenta alcuni cambiamenti rispetto a quella precedente, è stato necessario effettuare un porting dell’applicazione. Dopo aver disinstallato la vecchia release di Silverlight, è stato necessario scaricare ed installare i nuovi tool forniti dalla Microsoft per poter lavorare su progetti Silverlight in Visual Studio 2008 con la nuova versione del plugin. L’installazione di questi tool ha richiesto l’aggiornamento del Service Pack 1 di Visual Studio. Fatto ciò, per prima cosa è stato modificato all’interno del progetto ManagerParametriDF_Web il file html che può ospitare l’applicazione; in particolare è stato cambiato il MIME Type di Silverlight da application/x‐silverlight‐2‐b2 ad application/x‐silverlight‐2. Poiché nella nuova versione del plugin l’assembly System.Windows.Controls.Extended.dll è stato rinominato in System.Windows.Controls.dll, sono state cambiate le referenze di tutti i controlli extended presenti nell’applicazione (come il Calendar, il DatePicker, e il GridSplitter). Nella versione Beta 2 tutte le immagini che dovevano essere visualizzate come contenuto di una finestra popup dovevano essere inserite all’interno di uno user control (è il caso delle immagini di salvataggio, aggiunta tab e fieldset presenti nel custom control Preview creato), altrimenti veniva lanciata un’eccezione. Con la release definitiva tale problema è stato finalmente risolto, così le immagini possono essere direttamente inserite nel contenuto di una finestra popup ed essere visualizzate senza errori. Eseguendo l’applicazione con il nuovo plugin si è notato che le finestre popup non venivano visualizzate, per risolvere tale problema è stato necessario aggiungere l’oggetto popup del controllo Preview alla lista di figli della griglia che lo contiene, utilizzando l’istruzione LayoutRoot.Children.Add(myPopup). Siccome la nuova release Silverlight 2 va a ricercare il file generic.xaml all’interno della cartella ‘themes’, tale file non può più essere inserito nella directory principale del progetto, ma deve essere necessariamente collocato nella cartella in questione. Quando all’interno dell’applicazione viene eseguito il ridimensionamento di un controllo, si deve evitare di effettuare contemporaneamente il drag del controllo, poiché l’operazione di ridimensionamento ha la precedenza sull’operazione di drag. Con la versione Beta 2, quando si ridimensionava un controllo, all’interno della classe MyUserControl veniva bloccato il bubble degli eventi in modo da non far scattare il mouse_move del controllo contenitore dove sono attaccati gli eventi per il drag and drop. Per bloccare il bubble degli eventi bastava settare a ‘true’ la proprietà Handled della classe MouseEventArgs. Con la nuova release tale proprietà è stata spostata dalla classe MouseEventArgs alla classe MouseButtonEventArgs, creando così non pochi problemi. E’ stato dunque indispensabile riformulare in maniera diversa il codice per le operazioni di ridimensionamento, facendo uso dove opportuno di variabili flag. CONCLUSIONI Gli obiettivi principali di questa tesi erano mostrare le motivazioni e le fasi che mi hanno portato alla realizzazione di una particolare implementazione del pattern MVC per la realizzazione di applicazioni Silverlight e presentare un suo caso di utilizzo. In particolare si voleva realizzare una web application utilizzata in ambito aziendale che consentisse la gestione di form dinamiche. Per raggiungere questi obiettivi è stato necessario partire dall’analisi di ciò che la web application doveva consentire all’utente di effettuare e del contesto applicativo in cui veniva a trovarsi. A tal proposito sono state presentate l’architettura software, quella fisica, e la piattaforma tecnologica del sistema di cui l’applicativo fa parte e con cui esso comunica per poter ottenere i dati di cui esso necessita. L’applicativo era già stato realizzato precedentemente utilizzando il framework ASP.NET e dunque presentava numerosi problemi tipici delle applicazioni ‘thin’ client, dove quasi tutta la processazione avviene lato server. Per risolvere tali problemi, spostando parte della processazione lato client, e poiché l’applicazione doveva consentire di effettuare operazioni interattive con una certa velocità di esecuzione, essa doveva essere realizzata come una Rich Internet Application. E’ stata fatta così una panoramica sulle applicazioni RIA, analizzandone pregi e difetti e confrontando alcune delle principali tecnologie per il loro sviluppo. L’analisi di tali tecnologie ha portato ha scegliere la piattaforma Silverlight 2 come strumento più adeguato per la realizzazione dell’applicazione di gestione delle form dinamiche. Scelto lo strumento di sviluppo è sorta la necessità di individuare delle linee guida teoriche e pratiche per la realizzazione di applicazioni Silverlight. Poiché tale piattaforma è giunta alla sua release definitiva solo di recente e non sono state ancora rilasciate delle direttive di riferimento da utilizzare per lo sviluppo delle sue applicazioni, è stato necessario andare alla ricerca di un pattern che consentisse di scrivere del codice leggibile da chiunque e riusabile. Per fare ciò è stato indispensabile capire cosa fosse realmente un pattern, quale fosse stata la sua evoluzione, e conoscere i suoi molteplici ambiti di utilizzo. Sono state, dunque, presentate e analizzate differenti tipologie di pattern, ponendo particolare attenzione ai design pattern e ai pattern di processo, essenziali per una corretta organizzazione dei processi all’interno di un’azienda. Tra i vari pattern analizzati è stato scelto il noto design pattern MVC, del quale è stata descritta la struttura delle sue classi e sono stati mostrati i vantaggi che esso introduce nello sviluppo di applicazioni web. Del pattern in questione è stata così realizzata una particolare implementazione, creando una libreria di classi Silverlight scritte in linguaggio C# che può essere facilmente importata all’interno di una qualunque applicazione Silverlight. Di tale libreria sono stati descritti accuratamente tutti i suoi principali componenti e descritti i benefici derivanti dal suo utilizzo. Per mostrare un uso pratico, l’implementazione realizzata è stata poi utilizzata nella stesura del codice dell’applicazione di gestione delle form dinamiche. A tal proposito è stata mostrata in dettaglio la parte dell’applicazione realizzata, ponendo una particolare attenzione sul controllo di Preview che consente di visualizzare una form a fronte di una certa configurazione ascx passatagli in ingresso dal sistema di backend situato sul server. Sulla form visualizzata, poiché si tratta di una form dinamica che può avere un aspetto diverso in ogni fase dell’esecuzione, sono poi effettuabili operazioni di drag and drop, ridimensionamento dei controlli, aggiunta di campi e modifiche sui vari parametri. Tali operazioni sono rese possibili dalle grandi potenzialità possedute dal plugin Silverlight utilizzato. La realizzazione di questa tesi mi ha consentito di acquisire una certa familiarità con programmi e linguaggi utilizzati in ambito aziendale e difficilmente utilizzati in ambito universitario come Visual Studio 2008 e C#, di venire in possesso di ulteriori informazioni riguardanti il mondo delle web application, in particolare delle applicazioni RIA e di strumenti per la loro realizzazione come Silverlight, e di acquisire nuove conoscenze sui pattern e sulle loro varie tipologie. Spero che l’applicativo creato possa essere utile all’azienda per la quale è stato realizzato e che l’implementazione del pattern MVC possa servire in futuro a coloro che realizzano applicazioni utilizzando la piattaforma Silverlight. BIBLIOGRAFIA •
•
•
•
•
•
•
•
•
•
•
•
•
•
•
[1]
Christian Wenz (2008), Essential Silverlight 2 Up‐to‐Date, 1st edition, O’Reilly [2] Pietro Brambati, Introduzione a Silverlight2, http://aspnet.html.it/articoli/leggi/2713/introduzione‐a‐silverlight‐2/1/, 16 Luglio, 2008 [3]
Purushottam Rathore, Silverlight 2.0 Beta 2 version, http://www.dotnetheaven.com/UploadFile/prathore/silverlight08082008033
959AM/silverlight.aspx, September 08, 2008 [4]
Jesse Ezell, Silverlight vs. Flash: The Developer Story, http://weblogs.asp.net/jezell/archive/2007/05/03/silverlight‐vs‐flash‐the‐
developer‐story.aspx, May 03, 2007 [5]
Nikhil Kothari, Ajax vs. Silverlight and .NET, http://www.nikhilk.net/Ajax‐vs‐
Silverlight‐and‐NET.aspx, April 08, 2008 [6] Michele Nasi, Rich Internet applications: Sun scende in campo con JavaFX, http://www.ilsoftware.it/articoli.asp?id=4872, 11 Dicembre, 2008 [7] Alexey Gavrilov, Crash Course in Next‐Gen RIA: AIR, Silverlight, and JavaFX, http://www.devx.com/RichInternetApps/Article/35208/0/page/5, 2008 [8]
MSDN Library, Silverlight Architecture, http://msdn.microsoft.com/en‐
us/library/bb404713(VS.95).aspx , 2008 [9]
MSDN Library, Common Language Runtime, http://msdn.microsoft.com/en‐
us/library/cc265151(VS.95).aspx, 2008 [10]
Suprotim Agarwal, Isolated Storage in Silverlight 2 Beta 2, http://www.dotnetcurry.com/(X(1)S(l2wlpc2imovimf45kuvupeij))/ShowArticle.
aspx?ID=168&AspxAutoDetectCookieSupport=1, July 01, 2008 [11]
Emilio Simonetti, Pattern, http://www.urp.it/cpusabile/index495d.html [12]
Doug Lea, Christopher Alexander: An Introduction for Object‐Oriented Designers, http://g.oswego.edu/dl/ca/ca/ca.html , January 02, 1997 [13] Martin Fowler (1997), Analysis Patterns: Reusable Object Models, Addison‐
Wesley [14] Martin Fowler (2003), Patterns of Enterprise Application Architecture, Addison‐Wesley [15] Riccardo Golia, Introduzione ai design pattern, MSDN Italia, Novembre 2006 •
•
•
•
•
•
•
•
•
•
•
•
•
[16] Erich Gamma et al. (1995) , Design Patterns: Elements of Reusable Object‐
Oriented Software, Addison‐Wesley [17] Riccardo Golia, Design Pattern per esempi: i GoF creazionali , MSDN Italia, Dicembre 2006 [18] Riccardo Golia, Design Pattern per esempi: i GoF strutturali , MSDN Italia, Gennaio 2007 [19] Riccardo Golia, Design Pattern per esempi: i GoF comportamentali, MSDN Italia, Gennaio 2007 [20] Ambler, S. W. (1998), Process Patterns: Building Large‐Scale Systems Using Object Technology, New York, SIGS Books/Cambridge University Press [21] Harrison, N.B. (1996), Organizational Patterns for Teams, Pattern Languages of Program Design 2, Addison‐Wesley Publishing Company., pp. 345‐352 [22] Coplien, J.O. (1995), A Generative Development‐Process Pattern Language, Pattern Languages of Program Design, Addison Wesley Longman, Inc., pp. 183‐237 [23] Weir, C. (1998). Patterns for Designing in Teams, Pattern Languages of Program Design 3, eds. Martin, R.C., Riehle, D., and Buschmann, F., Addison Wesley Longman, Inc., pp. 487‐501 [24] Ambler, S. W. (1998), Building Object Applications That Work – Your Step‐
by‐Step Handbook for Developing Robust Systems With Object Technology, New York, SIGS Books/Cambridge University Press [25] Ambler, S. W. (1998), More Process Patterns: Delivering Large‐Scale Systems Using Object Technology, New York, SIGS Books/Cambridge University Press [26] Software Engineering Institute (1995), The Capability Maturity Model: Guidelines for Improving the Software Process, Reading MA, Addison‐Wesley Publishing Company [27] Emam, K. E., Drouin J., and Melo, W. (1998). SPICE: The Theory and Practice of Software Process Improvement and Capability Determination, Los Alamitos (CA), IEEE Computer Society Press [28] Martin Fowler (2003), Patterns of Enterprise Application Architecture, Addison‐Wesley •
•
•
•
•
•
[29] Steve Burbeck (1992), Application Programming in Smalltalk‐80: How to use Model‐View‐Controller (MVC), University of Illinois in Urbana‐Champaign (UIUC), Smalltalk Archive [30] Cristobal Baray, The Model‐View‐Controller (MVC) design pattern, http://cristobal.baray.com/indiana/projects/mvc2.html , March, 1999 [31] Danilo Spada, I Patterns, Roma, 07 Dicembre, 2005 [32] MSDN Library, Application Structure, http://msdn.microsoft.com/en‐
us/library/cc838120(VS.95).aspx, 2008 [33] Imran Shaik, Developing Custom Controls in Silverlight 2, http://geekswithblogs.net/Silverlight2/archive/2008/10/31/developing‐
custom‐controls‐in‐silverlight‐2.aspx, 31 October, 2008 [34] MSDN Library, Dependency Properties Overview, http://msdn.microsoft.com/en‐us/library/cc221408(VS.95).aspx, 2008 RINGRAZIAMENTI Voglio innanzitutto dedicare questa tesi ai miei genitori e a mio fratello, ringraziandoli per l’affetto e il sostegno che mi hanno sempre dato durante questi meravigliosi anni, permettendomi così di raggiungere questo traguardo con la massima serenità. Un altro ringraziamento va alle mie zie, a mia nonna, e ai nonni che purtroppo non ci sono più, perché anche grazie al loro affetto mi è stato possibile ottenere ciò che desideravo. Ringrazio poi tutti gli amici che mi hanno accompagnato durante questi sei piacevolissimi anni universitari, condividendo con me le cene, le partite di calcetto e quelle a Pes, le uscite serali e ogni altro fantastico momento. Un doveroso ringraziamento va infine al mio relatore e al mio tutor aziendale che mi hanno sempre dato la loro massima disponibilità durante questa attività di tesi.