Il data binding ci permette di associare proprietà appartenenti ad oggetti differenti, consentendoci ad esempio di sincronizzare le proprietà di un widget di un'interfaccia utente con altri oggetti Java incaricati di mantenere lo stato di una applicazione. Gli oggetti da associare all'interfaccia utente vengono generalmente chiamati data model. La relazione tra data model e interfaccia utente è bidirezionale nel caso in cui le modifiche (l'utente può inserire/aggiornare un dato tramite interfaccia o può essere aggiornato/inserito un valore nel data model) dovranno propagarsi nei due versi. E' monodirezionale se invece le modifiche vengono propagate solo in un verso (ad esempio solo dall'interfaccia al data model). Gli strumenti di data binding hanno il compito di supportare questa sincronizzazione, eventualmente fornendo strumenti di validazione per verificare che i dati inseriti o aggiornati rispettino delle regole.
WindowBuilder ci mette a disposizione gli strumenti per soddisfare le esigenze dettate da scenari reali, fornendo funzionalità di data binding bidirezionale sia per Swing che per SWT/JFace. In questa sezione forniremo un esempio di data binding in Swing.
Data binding in Swing
In linea di massima, nel data binding il data model può basarsi su POJO o su Java Bean. Classi POJO non seguono particolari convenzioni, pertanto tendono a presentare meno restrizioni di utilizzo. In compenso un oggetto Java Bean, dovendo seguire le specifiche Java Bean che prevedono metodi di set
e get
per tutti gli attributi (tranne quelli statici o transienti), permette ai framework di gestire automaticamente la dinamicità degli oggetti e supportare la sincronizzazione bidirezionale tra proprietà degli oggetti del data model e dell'interfaccia utente. Pertanto, WindowBuilder mette a disposizione un set di strumenti di data binding pensato appositamente per lavorare con Java Beans.
Da notare che per utilizzare le funzionalità di Swing data binding occorre aggiungere al classpath la libreria Swing Data Binding (ad esempio beansbinding-1.2.1.jar, scaricabile dal repository Maven download o POM dependency, o già contenuta nel progetto associato alla guida).
Aggiunta la libreria al classpath, diventerà disponibile una nuova scheda, "Bindings", come da immagine seguente (se non viene visualizzato nonostante si sia aggiunta la libreria al classpath, è possibile visualizzare la scheda chiudendo e riaprendo l'editor della classe).
Per usare il Swing data binding si può procedere in diversi modi. Attivando la scheda "Bindings" mostrata nell'immagine precedente, cliccando con il destro su un widget e selezionando il menù "Data Binding" (o utilizzando la corrispondente proprietà nella scheda "Properties"), o utilizzando il wizard per creare un'interfaccia utente a partire da un bean (new/other…/WindowBuilder/Swing Designer/Swing Automatic Databinding
).
Di seguito presenteremo un esempio utilizzando la scheda Bindings.
Nel progetto associato alla guida è possibile trovare il sorgente degli esempi descritti. Sono posizionati nella cartella AltroSorgente
, contenuti nel package binding
che include quattro classi successivamente descritte. Inoltre, nella cartella lib
è possibile trovare la libreria beansbinding-1.2.1
, non aggiunta al classpath. Volendo eseguire gli esempi tramite il progetto occorrerà pertanto portare sotto classpath la libreria presente nella cartella lib
e copiare il sorgente nella cartella src
.
Per prima cosa realizziamo un semplice Java Bean che andrà a costituire il data model, la classe Bicicletta
:
public class Bicicletta implements Serializable {
private static final long serialVersionUID = 1L;
private String modello;
private String marca;
private boolean marce;
public Bicicletta(){}
public String getModello() {
return modello;
}
public void setModello(String modello) {
this.modello = modello;
}
public String getMarca() {
return marca;
}
public void setMarca(String marca) {
this.marca = marca;
}
public boolean isMarce() {
return marce;
}
public void setMarce(boolean marce) {
this.marce = marce;
}
}
Vogliamo sincronizzare la classe con la seguente interfaccia (creata come nei casi precedenti tramite lo Swing Designer) BiciclettaUI
.
Possiamo procedere alla sincronizzazione tra interfaccia e Java Bean. Premendo sulla scheda "Bindings" della classe BiciclettaUI
, si aprirà la seguente scheda:
Nella parte centrale troviamo i tipi divisi in Widget sulla sinistra (sono gli elementi dell'interfaccia grafica verso i quali dobbiamo effettuare il binding) e Model sulla destra (i beans da mappare sull'interfaccia). Nel caso in cui non risultino bean disponibili nel data model, è possibile "suggerire" a WindowBuilder il bean cui siamo interessanti aprendo la scheda della classe BiciclettaUI
in modalità sorgente (o della classe d'interfaccia con la quale si sta lavorando) e aggiungendo la dichiarazione di un'istanza della classe bicicletta
(o del data model desiderato), ad esempio:
private Bicicletta bicicletta;
A questo punto, riattivando la scheda "Bindings", tra i tipi del data model troveremo il bean verso la quale effettuare il data binding.
Nella sezione inferiore della scheda troveremo le "properties" che ci consentono di stabilire cosa mappare e dove, ossia quali properties dei widget dell'interfaccia associare con le properties del data model. In alto troviamo infine l'elenco delle associazioni già realizzate con la relativa strategia di associazione. Possiamo vedere dall'immagine che al momento in cui si è effettuato lo snapshot erano state già effettuate due associazioni, la prima riguarda la marca, basata sulla property text del textField "Marca", mentre la seconda riguarda le marce, basata sulla property selected del radioButton. Da notare che in entrambi i casi viene richiesto di applicare una strategia del tipo READ_WRITE, ossia di sincronizzare in maniera bidirezionale, e che è possibile non limitarsi a sincronizzare stringhe di testo ma anche altri tipi di properties, in questo caso se il radioButton è selezionato o meno. E' possibile modificare queste associazioni.
Per aggiungere una nuova associazione, non resta che selezionare le properties del caso sia per l'interfaccia che per il bean e premere sul pulsante "Create data binding" evidenziato dal cerchietto rosso. Si aprirà un wizard che ci consentirà di scegliere la strategia di binding adeguata, oltre a fornirci la possibilità di selezionare eventuali regole di validazione o di conversione. Queste azioni porteranno alla creazione di un metodo initDataBindings
nell'interfaccia utente con la responsabilità di richiedere la sincronizzazione e di una classe AutomaticDataBinding
incaricata di eseguire la sincronizzazione delle properties.
Di default, la classe bicicletta
non è inizializzata e avviando il main
della classe BiciclettaUI
otterremmo un'interfaccia vuota. Se invece inizializziamo l'istanza della classe Bicicletta
e ne popoliamo i campi prima dell'invocazione run, ad esempio:
//…
private static Bicicletta bicicletta;
//…
public static void main(String[] args) {
bicicletta = new Bicicletta();
bicicletta.setMarca("Gialli");
bicicletta.setModello("Bike Mk01");
bicicletta.setMarce(true);
EventQueue.invokeLater(new Runnable() {
public void run() {
//…
otterremo un'interfaccia inizializzata con i valori correttamente settati, senza la necessità di dover effettuare le assegnazioni dei rispettivi campi dell'interfaccia utente (lettura del bean).
Nel codice corrispondente all'azione del pulsante possiamo limitarci ad inserire una stampa a video delle proprietà dell'oggetto, ad esempio:
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
System.out.println("Modello: "+bicicletta.getModello()
+"\nMarca: "+bicicletta.getMarca()
+"\nMarce: "+bicicletta.isMarce());
}
});
Provando a modificare il valore di uno o più campi dell'interfaccia utente e cliccando sul pulsante "Aggiorna" vedremo dalla stampa a video che l'oggetto è stato automaticamente aggiornato sulla base dei campi dell'interfaccia utente (scrittura del bean), senza nessuna necessità di effettuare dei get
del contenuto dell'interfaccia né set
delle properties del bean.
Volendo infine provare un aggiornamento del data model con propagazione della modifica all'interfaccia utente, possiamo prevedere un nuovo pulsante "Ricarica" come fatto nella classe BiciclettaUIUp
contenuta nel progetto allegato alla guida, popolandolo con il seguente codice:
btnRicarica.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String nuovoModello = "Bike Super Pro";
bicicletta.setModello(nuovoModello);
initDataBindings();
}
});
Il pulsante non fa altro che aggiornare uno dei campi dell'oggetto Bicicletta
e invocare nuovamente il metodo initDataBindings
. Come risultato, l'interfaccia utente verrà automaticamente aggiornata con il nuovo valore del modello, senza necessità di dover settare manualmente i campi modificati o di dover richiedere l'aggiornamento dell'interfaccia per visualizzare le modifiche apportate alle relative properties.
WindowBuilder ci mette quindi in condizione di poter sincronizzare in maniera bidirezionale le modifiche apportate all'interfaccia utente o al sottostante data model con uno sforzo minimo.