Affinché un IDE possa definirsi completo è necessario che disponga di uno strumento visuale per la creazione di interfacce grafiche (GUI, Graphical User Interface). Poter effettuare il drag & drop di un componente visuale da una “palette” su una form o generare in modo automatico il codice sorgente per la gestione di un evento sono tra le azioni basilari di un tale strumento. Andiamo quindi a scoprire Visual Editor, il tool visuale di Eclipse.
Introduzione: problemi di installazione e soluzioni
In questo articolo vedremo come installare e utilizzare in modo efficace l’editor visuale di Eclipse.
Con Visual Editor saremo in grado di creare e modificare le parti grafiche della nostra applicazione Rich Client (Finestre standard, finestre di dialogo, pannelli e tutti i componenti visuali in essi contenuti).
Tale strumento non è compreso nelle distribuzioni di Eclipse correnti e deve quindi essere aggiunto in un secondo tempo. Inoltre, al momento, il progetto Visual Editor (VEP) non è ancora compatibile con l’ultima versione stabile di Eclipse (la versione 3.3, nota anche col nome di “Europa”, giugno 2007), ma solo con la versione 3.2 (“Callisto”, giugno 2006) [1]. D’altra parte il “Data Binding” è stato introdotto solo nella versione 3.3, quindi come possiamo avere contemporaneamente il data binding e Visual Editor? Impossibile? Certo che no.
In attesa della versione di Visual Editor compatibile con Eclipse 3.3, esiste una soluzione [2] che consiste nei seguenti due passi:
- installare il plug-in WTP (Web Tools Platform), che serve normalmente per la gestione di Applicazioni Web, ma nel nostro caso ha il solo scopo di portare all’interno di Eclipse un insieme di altri plug-in utilizzati anche da Visual Editor
- installare una versione modificata di Visual Editor
Troveremo il primo (WTP) sul sito ufficiale di Update per Eclipse (“Eclipse Europa Discovery Update Site”), mentre il secondo è disponibile all’interno del progetto EasyEclipse, presso il famoso incubatore di progetti Open Source “Sourceforge”.
Avremo così modo di vedere due tra i differenti modi di aggiungere un plug-in. Il primo sfrutta un meccanismo automatico di update e di risoluzione delle dipendenze simile a quello utilizzato per l’aggiornamento dei sistemi operativi. Il secondo invece prevede operazioni manuali di download e copia di files.
In ogni caso tenete sempre presente che, dopo aver integrato e personalizzato il vostro IDE con i più svariati plug-ins, potrete semplicemente copiare la cartella di Eclipse sia per ottenere un backup che per riportare lo stesso ambiente o su altre macchine di sviluppo.
Ma andiamo con ordine: istalliamo il WTP.
Una piccola considerazione però è necessaria per chi si trova in una rete con proxy. In questo caso occorre impostare il proxy e la porta andando alla voce di menù Window -> Preferences… e nella struttura gerarchica mostrata sulla sinistra (Figura 1) selezionare General -> Network Connections. Selezionare a questo punto l’opzione “Manual proxy configuration” e impostare i campi “HTTP proxy” e “Port”. Inoltre, se la vostra configurazione di rete lo richiede, potreste dover impostare anche i campi “Username” e “Password” nella sezione “Enable proxy authentication”.
Figura 1 – Impostazione del proxy
Connettiamoci ora all’Update site di Eclipse selezionando Help -> Software Updates -> Find and Install…, selezionare l’opzione “Search for new features to install” e premere Next.
Allo step “Update sites to visit” (Figura 2) selezionare il sito “Europa Discovery Site” e l’opzione “Automatically select mirrors” (questo vi risparmierà di dover selezionare ogni volta il mirror site), poi premere Finish.
Figura 2 – Connessione all’Update site principale di Eclipse 3.3 (Europa)
Avrà così inizio la ricerca degli aggiornamenti ad opera dell’Update Manager che terminerà con la finestra di selezione dei vari plug-ins disponibili (Figura 3)
Figura 3 – Installazione del WTP
A questo punto occorre selezionare l’opzione “Filter features included in other features on the list” (per ridurre le ridondanze tra i pacchetti mostrati), espandere il nodo “Eclipse Discovery Site” e selezionare il plug-in “Web and JEE Development”. Verrà segnalato un problema di dipendenze in quanto il plug-in selezionato necessita di altri plug-ins. Per risolvere le dipendenze in modo automatico basta premere il bottone “Select Required”. Al termine premere Next, accettare il License Agreement e proseguire (Next) e di nuovo Next poi Finish e attendere le fasi di download e installazione. Al termine confermate l’installazione dei vari plug-ins premendo Install All.
Dopo l’installazione confermate la richiesta al riavvio di Eclipse.
Ora, per l’installazione di Visual Editor è necessario effettuare il download del file org.eclipse.visualeditor-1.3.0.200709121813.zip dall’indirizzo
http://sourceforge.net/project/showfiles.php?group_id=131065&package_id=154855
Il file ottenuto va quindi scompattato in una cartella temporanea e il contenuto della cartella “org.eclipse.visualeditor-1.3.0.200709121813”, costituito dalle sottocartelle “features”, “plugins” e da alcuni altri files, copiato nella cartella di installazione di Eclipse (p.e.: C:eclipse), confermando la sovrascrittura delle cartelle omonime. A questo punto, dopo aver riavviato Eclipse, Visual Editor sarà finalmente disponibile (Figura 4).
Figura 4 – Verifica installazione Visual Editor
E dopo l’installazione, cominciamo a utilizzare il Visual Editor.
Creazione di una View
Nel precedente articolo, al momento di creare la view, abbiamo riportato le linee di codice già pronte rimandando ulteriori spiegazioni a un articolo successivo. Ora siamo finalmente in grado di vedere come si crea una view in maniera visuale. Creeremo pertanto la stessa view utilizzata nel primo articolo, ma stavolta utilizzando il Visual Editor (approfitteremo inoltre dell’occasione per darle un nome e una collocazione più adeguata).
Creiamo un nuovo package it.mokabyte.rcp.test1.application.view e al suo interno una view premendo tasto destro, New -> Other -> Java -> RCP -> View Visual Class quindi Next. Nel campo “Name” digitiamo LibroView e premiamo Finish. A questo punto si aprirà una finestra di Visual Editor con la view appena creata (Figura 5)
Figura 5 – Una view vuota aperta con Visual Editor
Espandiamo quindi la “Palette” di componenti visuali (come indicato dal cursore in Figura 5) e selezioniamo il componente “Label” con un click. Poi portiamo il cursore nell’angolo in alto a sinistra della view e clicchiamo di nuovo (Figura 6).
Figura 6 – Drop di un componente sulla view
Ci verrà chiesto il nome da attribuire all’oggetto; chiamiamolo “lblCodice”. Per modificare il contenuto della label eseguiamo un clic su di essa e digitiamo “Codice”. Ora, allo stesso modo, posizioniamo un componente di tipo “Text” alla destra della label, chiamandolo “txtCodice” e sotto di essi disponiamo poi la label “lblTitolo” e il campo di testo “txtTitolo”.
Per fare in modo che quest’ultimo sia più lungo del campo Codice possiamo modificarne le proprietà attraverso l’apposita vista (Properties, Figura 7); in particolare possiamo imporre una larghezza di riferimento espressa in pixel grazie alla proprietà “layoutData -> widthHint”.
Figura 7 – Vista Properties
Infine disponiamo un componente “Button” al di sotto delle label chiamandolo “btnSalva” e impostandone il testo (“Salva”) con un click su di esso.
Per creare inoltre il codice per la gestione dell’evento di click su tale bottone è sufficiente selezionarlo e, attraverso il tasto destro del mouse, fare click su Events -> widgetSelected; verrà generato il metodo widgetSelected, che dovrà essere completato per gestire l’evento scelto. Fate riferimento all’articolo precedente per il contenuto di tale metodo.
Manca ancora però una parte importante, cioè come aggiungere questa nuova vista alla nostra applicazione e come fare in modo che sia la vista di default della nostra unica Perspective.
Il primo punto si risolve attraverso la vista di configurazione del plug-in, aggiungendo una nuova view (Figura 8) e attribuendo alle proprietà “id” e “class” il nome completo della classe LibroView (it.mokabyte.rcp.test1.application.view.LibroView) e un nome di riferimento alla proprietà “Name”, ad esempio “Vista Libro” (Figura 9).
Figura 8 – Aggiunta di una view
Figura 9 – Impostazione degli attributi della nuova view
Non resta ora che modificare la classe Perspective.java per lanciare la view Libro come vista di default.
layout.addStandaloneView(LibroView.ID, false, IPageLayout.LEFT, 1.0f, editorArea);
Layout in SWT
In questo paragrafo intendiamo fare una panoramica dei componenti di base per il disegno di un’interfaccia utente (da qui UI) con SWT. Per chi ha già lavorato con le interfacce grafiche in Java la prossima parte dell’articolo sarà una ripetizione di alcuni concetti già visti.
Un’interfaccia grafica è composta da istanze delle classi che ereditano dalla classe astratta org.eclipse.swt.widgets.Control. Il primo concetto di cui parliamo è il Composite, che costituisce il contenitore per tutti i controlli necessari per la costruzione della UI.
Un Composite, che è un istanza della classe org.eclipse.swt.widgets.Composite, è caratterizzato da uno stile ed è legato a un altro Composite (Parent) che lo contiene. Infatti ogni UI è costituita da un albero di contenitori che ha come radice la Shell principale (che rappresenta la finestra attiva); ogni nodo dell’albero è un Composite (o un’istanza di una classe figlia) che contiene i controlli che rappresentano i widgets (controlli grafici) per l’interazione con l’utente.
Figura 10 – Un Composite dentro a un altro
La figura 10 riporta un esempio di come è possibile utilizzare un Composite dentro ad un altro: quello più esterno infatti contiene un TabFolder (org.eclipse.swt.widgets.TabFolder)che estende la classe Composite. Il TabFolder ha una parte superiore con le etichette (nell’esempio General, Custom e System) e una parte inferiore, chiamata clientArea, all’interno della quale sono state inserite due TextArea.
Ora che abbiamo un idea di cosa sia un Composite, abbiamo a disposizione tutti gli elementi per comprendere come si costruisce una UI, ma non abbiamo ancora parlato di come possiamo darle l’aspetto che desideriamo.
Quando si sviluppa un’applicazione con le librerie SWT, dobbiamo tener presente che stiamo interagendo con uno strato al di sotto del quale si trova direttamente il Sistema Operativo. Per dare alla UI l’aspetto che desideriamo, è necessario l’uso dei Layouts che ci permettono di guidare la disposizione di tutti i controlli all’interno dei Composite.
Per comprendere a fondo la necessità dell’uso dei layout, che a prima vista può sembrare un po’ complicato, è necessario pensare la finestra che vogliamo costruire come un insieme di controlli che sono liberi di muoversi al variare delle sue dimensioni. Per questo motivo è necessario l’uso di un manager che stabilisca le regole con cui i widgets si possono muovere all’interno del contenitore. Questo è il compito del layout.
Il posizionamento e il dimensionamento di un controllo non sono caratteristiche del componente singolo, ma dipendono dal contenitore e dalla presenza dei controlli circostanti. Questo permette di decidere la posizione iniziale e come si comporteranno i controlli a fronte di eventuali ridimensionamenti del contenitore.
La classe org.eclipse.swt.widgets.Layout è la classe astratta da implementare se si vuole creare un layout custom; tuttavia la libreria SWT fornisce alcuni tipi di Layout standard:
- FillLayout permette di disporre widgets di uguale dimensione su una riga o una colonna;
- RowLayout permette di disporre i widgets su una o più righe, con le opzioni fill, spacing e wrap (vedi più avanti);
- GridLayout permette di disporre i widgets in una griglia;
- FormLayout permette di disporre i widgets nel container, utilizzando i FormAttachment (vedi più avanti).
Per assegnare un layout a un Composite (o a qualsiasi altro contenitore) si deve utilizzare il metodo setLayout(Layout). Per completezza va fatto notare che è anche possibile fare a meno di un layout, impostandolo al valore “null”; in questo caso i widgets potranno essere disposti e dimensionati in modo assoluto (con le coordinate x, y e le dimensioni width e height espresse in pixel) perdendo però ogni possibilità di adattamento automatico al variare delle dimensioni della finestra.
Ad ogni classe Layout, deve corrispondere una classe LayoutData, che contiene i dati dal layout del controllo all’interno del Composite. Per assegnare un LayoutData a un widget è necessario utilizzare il metodo setLayoutData(LayoutData).
Per fare un esempio, nel codice sotto riportato si crea un composite e gli si assegna un RowLayout; per ogni widget che viene creato, è quindi necessario assegnare un layoutData di tipo RowData.
//Assegno il Layout al Container Composite container = new Composite(parent,SWT.NONE); container.setLayout(new RowLayout()); //Assegno il LayoutData al controllo Button button = new Button(container, SWT.PUSH); button.setLayoutData(new RowData(50, 40));
Il codice sopra riportato è solo a titolo di esempio. Dato che abbiamo a disposizione Visual Editor, non dovremo più scrivere a mano il codice per la manipolazione dei Layout. Riprendiamo la classe LibroView che abbiamo creato nel paragrafo precedente.
Usare Visual Editor per la manipolazione dei Layout
Ora che abbiamo Visual Editor, riprendiamo la costruzione della View del primo articolo, per sottolineare la velocità con cui è possibile realizzare una UI. Apriamo la View con VisualEditor. Cliccando con il tasto destro del mouse nella visualizzazione grafica del composite si aprirà il menù contestuale, dal quale è possibile assegnare un Layout predefinito come mostrato in figura 11. Il GridLayout è assegnato di default.
Figura 11 – Assegnare un Layout predefinito
Cliccando invece sulla voce Customize Layout… si apre il popup per la gestione delle proprietà di layout. Nella figura 12 sono riportate due schermate di proprietà. Quella a sinistra riguarda il contenitore (Layout), mentre l’altra un singolo componente testuale (LayoutData). Per visualizzarle, è sufficiente cliccare su un elemento della View (nel primo caso il composite, nel secondo un widget) con il popup delle proprietà aperto.
Layout predefiniti in SWT
Il FillLayout è il più semplice tra tutti i manager messi a disposizione da SWT. Aggiungendo dei widgets al container, il FillLayout li dispone in riga o in colonna, in base ad un parametro che si può passare al costruttore, tutti con la stessa dimensione in modo che sia riempito tutto lo spazio a disposizione. Questo tipo di layout è molto utile se si deve per esempio visualizzare una lista di labels o di bottoni tutti della stessa dimensione, come mostrato un figura 13: se la finestra viene ridimensionata, i widgets saranno ridimensionati in modo che continuino a occupare tutto lo spazio.
Figura 13 – Esempi di FillLayout
Il RowLayout dispone i widget in una riga (o in una colonna). A differenza del FillLayout, il RowLayout mette a disposizione alcuni attributi la cui valorizzazione modifica il comportamento del Layout. Per esempio assegnando true all’attributo pack, il RowLayout rispetterà la dimensione naturale di ogni widget, ed essi saranno posizionati il più possibile a sinistra (o in alto). Nella Figura 14 mostriamo l’esempio precedente a cui abbiamo applicato un RowLayout con l’attributo pack a true.
Figura 14 – Esempio di RowLayout
Un’altra caratteristica che contraddistingue il RowLayout è l’attributo wrap, che permette al Layout di dividere una riga troppo lunga per la finestra.
Nella figura 15 è riportato un altro esempio di RowLayout che modifica la posizione dei widgets in base ai ridimensionamenti della finestra.
Figura 15 – Altri esempi di RowLayout
Il GridLayout offre molta più flessibilità del RowLayout o del FillLayout, ma questa flessibilità ha un costo in termini di complessità. Il GridLayout ha molti attributi che devono essere manipolati per ottenere l’aspetto voluto; questo vale anche per il GridData che è necessario assegnare ad ogni widget per sfruttare a pieno le potenzialità del GridLayout.
I due valori più importanti da assegnare al GridLayout sono il numColumn e il makeColumnsEqualWidth. Il primo descrive il numero di colonne che dovranno essere presenti, indipendentemente dai ridimensionamenti. Il secondo invece specifica se le colonne devono essere tutte uguali.
Differentemente dal RowLayout, dove il RowData è opzionale, il GridData è assolutamente necessario per il GridLayout. Il suo più importante aspetto è che, quando la finestra viene ridimensionata, permette di controllare l’aspetto dei singoli widgets in modo che solo alcuni si ridimensionino mentre altri mantengano le dimensioni originali.
Nella figura 16 presentiamo due viste della stessa finestra. Sulla sinistra la finestra ha le dimensioni di partenza, mentre nella finestra a destra che è stata ridimensionata si può vedere che il campo di testo corrispondente al titolo è stato ridimensionato in larghezza mentre la label con la scritta Titolo è rimasta invariata. Questo succede perchè al campo di testo è stato assegnato un GridLayout con gli attributi fillHorizontal e grabExcessHorizontalSpace entrambi a true. Tale proprietà sono state modificate dal popup di gestione dei layout di cui abbiamo già parlato e che riportiamo per semplicità in figura 17.
In questa figura si possono vedere tutte le possibili opzioni per personalizzare il LayoutData. L’attributo fill indica che il campo deve occupare tutto lo spazio all’interno della cella in cui si trova e si può specificare sia per l’orizzontale che per il verticale. È inoltre possibile specificare l’allineamento ed un eventuale indentazione. I bottoni Grab Excess invece specificano che la cella della griglia deve prendere tutto lo spazio possibile; questa opzione può essere assegnata ad una sola cella della riga o della colonna.
Infine è possibile specificare una dimensione (Hints) sia per l’altezza che per la larghezza del campo, ma questi valori sono un suggerimento in quanto saranno rispettati solo se le dimensioni globali della finestra lo permetteranno.
Figura 17 – Modificare le impostazione di un GridData
L’ultimo di cui parliamo è il FormLayout, che è il più nuovo tra i manager di layout anche se è dubbio il fatto che sia più utile del GridLayout. Il FormLayout mette a disposizione una enorme flessibilità, a scapito della semplicità dell’implementazione.
La prima differenza con gli altri tipi di Layout è che l’ordine in cui vengono definiti i widgets non è importante, dato che attraverso l’uso dei FormData devono essere specificamente valorizzate per determinarne la posizione: se non viene loro assegnato, tutti i widgets avranno la stessa posizione e saranno sovrapposti.
Ogni widget ha un suo FormLayout che gli viene assegnato grazie ai FormAttachments. L’idea che sta alla base dei FormAttachments è che i bordi di ogni widget possono essere posizionati ovunque.
Infatti possiamo assegnare la posizione per il bordo inferiore, superiore, destro e sinistro per ogni widget: questi valori vengono espressi in percentuale o in distanze rispetto agli altri widgets.
Le due facce del data-binding
Al momento il meccanismo di data binding è stato affrontato solo a metà! Infatti nel primo articolo abbiamo visto come i valori impostati nei campi della view vadano ad aggiornare gli attributi del modello in modo automatico (View-to-Model databinding). Potete facilmente verificare che non è vero il contrario, cioè se il modello viene modificato i campi della view non rifletteranno tali modifiche.
Per completare il meccanismo introducendo il lato “Model-to-View” del databinding, occorre fare qualche modifica al modello, rendendolo conforme alle specifiche JavaBean [4].
In pratica occorre aggiungere al nostro modello (Libro.java) il codice necessario affinchè esso possa notificare le proprie modifiche agli eventuali componenti interessati (Listeners). Le parti da aggiungere sono costituite da un attributo di tipo PropertyChangeSupport e da due metodi che permetteranno agli oggetti listeners di registrarsi o farsi rimuovere dalla lista degli interessati agli eventi di questo oggetto.
private PropertyChangeSupport changeSupport = new PropertyChangeSupport(this); public void addPropertyChangeListener( String propertyName, PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(propertyName, listener); } public void removePropertyChangeListener( String propertyName, PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(propertyName, listener); }
Inoltre ogni metodo setter dovrà utilizzare l’attributo changeSupport per notificare i cambiamenti.
public void setCodice(String codice) { String oldCodice = this.codice; this.codice = codice; changeSupport.firePropertyChange("codice", oldCodice, codice); } public void setTitolo(String titolo) { String oldTitolo = this.titolo; this.titolo = titolo; changeSupport.firePropertyChange("titolo", oldTitolo, titolo); }
Le import necessarie (sempre ottenibili posizionandosi sulla finestra di editor dei sorgenti ed eseguendo tasto destro, Source -> Organize Imports) sono le seguenti:
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport;
Eccoci quindi pronti a utilizzare questo aspetto del databinding per un obiettivo ben preciso, cioè quello di aggiornare i campi della view quando si esce dal campo codice con un valore presente sul database. Dovremo quindi, al verificarsi dell’evento “focusLost” del campo codice procedere alla lettura dal database e all’aggiornamento del modello, mentre il Model-to-View databinding provvederà a portare i valori letti sulla view.
Selezioniamo quindi il campo codice sulla view ed eseguiamo tasto destro, Events -> Add Events…, selezioniamo la categoria “focus” e quindi l’evento “focusLost”. Inseriamo quindi le seguenti istruzioni per la gestione dell’evento:
public void focusLost(org.eclipse.swt.events.FocusEvent e) { Libro libroDB = LibroBusiness.load(txtCodice.getText()); if (libroDB==null) { libroDB = new Libro(); } libro.setTitolo(libroDB.getTitolo()); }
anche se una gestione più interessante potrebbe essere quella di copiare in un’unica operazione tutti gli attributi utilizzando la classe BeanUtils della libreria “commons-beanutils.jar”, contenuta nel pacchetto commons-beanutils-1.7.0.zip, scaricabile dal link
http://commons.apache.org/downloads/download_beanutils.cgi
public void focusLost(org.eclipse.swt.events.FocusEvent e) { Libro libroDB = LibroBusiness.load(txtCodice.getText()); if (libroDB == null) { libroDB = new Libro(); libroDB.setCodice(txtCodice.getText()); } try { BeanUtils.copyProperties(libro, libroDB); } catch (Exception e1) { e1.printStackTrace(); } }
Per poter utilizzare tale libreria fate riferimento al precedente articolo, dove vengono fornite le indicazioni dettagliate su come aggiungere ed utilizzare nuove librerie.
Aggiungiamo infine il metodo load alla classe LibroBusiness:
public static Libro load(String codice) { EntityManager em = emf.createEntityManager(); return em.find(Libro.class, codice); }
Eseguendo ora l’applicazione possiamo verificare facilmente come inserendo un codice già registrato sul database ed uscendo dal campo, si ottenga l’aggiornamento del campo titolo sulla view.
Conclusioni
L’utilizzo di Visual Editor e l’analisi delle possibili scelte durante la costruzione del layout grafico di una applicazione meriterebbe da solo ben più di un articolo. In questa occasione abbiamo tuttavia voluto fornire le indicazioni necessarie alla sua installazione e al suo utilizzo in una forma abbastanza essenziale.
Abbiamo inoltre completato il discorso relativo al data binding applicandolo alla operazione di caricamento dei dati dal database all’interfaccia grafica.
Riferimenti
[1] http://www.eclipse.org/vep/WebContent/main.php
[2] http://wiki.eclipse.org/VE/Installing
[3] http://www.eclipse.org/articles/Article-Understanding-Layouts/Understanding-Layouts.htm
[4] http://java.sun.com/developer/onlineTraining/Beans/JBeansAPI/shortcourse.html