PREMESSA:
Ciao a tutti,
comincio oggi questo viaggio nel mondo di Java un po' per gioco ed un po' per necessità in quanto potrebbe far comodo anche a me avere a portata di mano alcuni riferimenti utili.
Quasi sempre, sviluppando software che si interfaccia a database o che deve comunque analizzare un concreto numero di dati, è necessario mostrare i risultati che interessano all'interno di tabelle. La tecnica utilizzata da Java per concretizzare questo aspetto, può inizialmente apparire alquanto semplice; in realtà, come vedremo, qualora si volesse personalizzare l'aspetto e la tipologia dei dati mostrati in tabella, non accontentandosi della visualizzazione classica, il discorso si farà abbastanza complesso; ho in mente di realizzare più post per approfondire i vari aspetti sul tema che, se affrontato in un unico discorso, potrebbe risultare veramente troppo articolato.
Quasi sempre, sviluppando software che si interfaccia a database o che deve comunque analizzare un concreto numero di dati, è necessario mostrare i risultati che interessano all'interno di tabelle. La tecnica utilizzata da Java per concretizzare questo aspetto, può inizialmente apparire alquanto semplice; in realtà, come vedremo, qualora si volesse personalizzare l'aspetto e la tipologia dei dati mostrati in tabella, non accontentandosi della visualizzazione classica, il discorso si farà abbastanza complesso; ho in mente di realizzare più post per approfondire i vari aspetti sul tema che, se affrontato in un unico discorso, potrebbe risultare veramente troppo articolato.
Questo post è rivolto essenzialmente a coloro che conoscono almeno le basi della programmazione orientata agli oggetti con particolare riferimento alla sintassi Java e che si avvicinano per la prima volta alla necessità di rappresentare i dati in forma tabellare.
In questo primo step, vedremo come visualizzare una tabella, come inseririvi dei dati provenienti da un DB esterno e come differenziarne la visualizzazione a seconda del tipo.TABELLA COME ISTANZA DELLA CLASSE JTable:
Per rappresentare i dati all'interno di tabelle, Java mette a disposizione la classe JTable (vediJavaDoc) che è reperibile all'interno del package javax.swing.
Per questo motivo è opportuno importare, nella classe che dovrà gestire la cartella, il precedente percorso onde evitare di anteporlo ad ogni richiamo della classe:
import javax.swing.JTable;
oppure, scelta migliore in quanto si farà riferimento ad altre classi del medesimo package:
import javax.swing.*;.
Comunque sia, la tabella dovrà essere sempre definita all'interno di un panello di scorrimento JScrollPane (vediJavaDoc) al fine di consentire l'eventuale visualizzazione delle barre verticale ed orizzontale.Ciò premesso, quando personalmente devo utilizzare una tabella all'interno di un frame, preferisco definire una classe che estende JPanel (vediJavaDoc) in modo da sviluppare un pannello con caratteristiche proprie, da inserire poi nel frame principale od in altri pannelli, in questo modo:
Come è possibile rilevare, per prima cosa ho definito una classe pnlTablePers che estende la classe JPanel. Poi ho dichiarato due variabili d'istanza private di tipo JScrollPane (scrTable) e di tipo JTable (tabDati). Nel costruttore della classe, dopo aver istanziato gli oggetti precedentemente dichiarati (per ora senza nessun parametro d'ingresso), ho in primo luogo aggiunto tabDati all'interno di scrTable, poi ho assegnato al pannello scorrevole la vista sulla medesima tabella ed infine ho aggiunto scrTable al pannello contenitore rappresentato dalla classe stessa.
I DATI CONTENUTI NELLA TABELLA:
Cenni sulla definizione di un Modello;
Costruttori di JTable per l'inserimento di dati;
Costruttori di JTable per l'inserimento di dati;
Fino a questo punto abbiamo visto come definire concretamente un oggetto JTable all'interno di un JPanel. Quello che andremo adesso ad impostare, sarà la modalità con cui inserire alcuni dati all'interno della tabella.
A tal proposito JAVA mette a disposizione diversi sistemi facenti riferimento ad un cosidetto "modello" di tabella che sarà possibile utilizzare un varie occasioni.
La gerarchia delle classi e delle interfacce di JAVA definisce l'esistenza di un interfaccia chiamata TableModel (vediJavaDoc) che astrattamente prevede la definizione di alcuni metodi obbligatoriamente presenti nelle classi che la implementano (concetti di ereditariatà e polimorfismo dei linguaggi di programmazione orientati agli oggetti).
In particolare, ci occuperemo della classe DefaultTableModel (vediJavaDoc) che estende la classe astratta AbstractTableModel (vediJavaDoc), che a sua volta implementa TableModel.
In questo modo, un eventuale istanza di DefaultTableModel, potrà accedere a tutti i metodi indicati in TableModel, sviluppati ed arricchiti con quelli presenti in AbstractTableModel.
In questo modo, un eventuale istanza di DefaultTableModel, potrà accedere a tutti i metodi indicati in TableModel, sviluppati ed arricchiti con quelli presenti in AbstractTableModel.
La classe JTable può essere direttamente istanziata definendo un modello di tipo TableModel nel costruttore:
JTable tabDati = new JTable(TableModel modello);
oppure è possibile definirne uno attraverso il metodo setModel su una sua istanza:
tabDati.setModel(TableModel modello);.
Comunque sia, è anche possibile utilizzare direttamente una serie di costruttori di JTable che prevedono l'utilizzo implicito (cioè sotto il cofano della classe JTable) di un istanza di DefaultTableModel.In particolare abbiamo:
- JTable(int numRighe, int numColonne)
che costruisce una tabella vuota con il numero di righe e colonne specificate; - JTable(Object[][] datiRighe, Object[] nomeColonne)
che costruisce una tabella riempita con i dati contenuti nell'array bidimensionale datiRighe e con le intestazioni delle colonne contenute nell'array nomeColonne; - JTable(Vector datiRighe, Vector nomeColonne)
che costruisce una tabella riempita con i dati contenuti vettore di vettori datiRighe e con le intestazioni delle colonne contenute nel vettore nomeColonne.
Nei casi più semplici è assolutamente indicato l'utilizzo di uno dei precedenti costruttori (indifferentemente a seconda della convenienza), anzichè ricorrere ad una personalizzazione del modello della tabella.
DATI DI TIPO String:
Si tratta proprio del caso meno complesso in quanto questo tipo di dati non necessita di una particolare "formattazione" per essere correttamente visualizzato. I dati inseriti nella tabella sono sempre di tipo Object, cioè generici; un dato di tipo String, se trattato come un Object, fornirà una rappresentazione esattamente uguale a quella propria della sua classe. Per questo motivo, dato un ResultSet (Vedi JavaDoc), è sempre possibile estrarre dati di tipo String attraverso un generico comando getObject.
In termini pratici, immaginiamo di eseguire una semplice query di selezione su una tabella PERSONE contenuta in un DB, come la seguente:
In termini pratici, immaginiamo di eseguire una semplice query di selezione su una tabella PERSONE contenuta in un DB, come la seguente:
SELECT PERSONE.COGNOME, PERSONE.NOME, PERSONE.COMUNE, PERSONE.INDIRIZZO
FROM PERSONE;
e che il risultato della predetta query venga contenuto all'interno di un ResultSet rs.
La query, per ogni campo o colonna, fornisce soltanto dati di tipo stringa per cui, sviluppando alcuni semplici concetti sulle API JDBC e sui vettori, avremo:
La query, per ogni campo o colonna, fornisce soltanto dati di tipo stringa per cui, sviluppando alcuni semplici concetti sulle API JDBC e sui vettori, avremo:
ResultSetMetaData rsmd = rs.getMetaData();
int numColonne = rsmd.getColumnCount();
Vector nomeColonne = new Vector();
for (int nr=0; nr<numColonne; nr++)
nomeColonne.add(rsmd.getColumnName(nr+1));
Vector <Vector> datiRighe = new Vector <Vector>();
while (rs.next()){
Vector riga = new Vector();
for (int nr=0; nr<numColonne; nr++) {
riga.add(rs.getObject(nr+1));
}
riga.add(rs.getObject(nr+1));
}
datiRighe.add(riga);
}
che ci permetterà di utilizzare la seguente forma per istanziare un oggetto JTable riempiendolo dei dati provenienti dal ResultSet della query, senza il bisogno di fare alcun riferimento esplicito ad un Modello di tabella:
JTable tabDati = new JTable(datiRighe, nomeColonne);
La visualizzazione del pannello contenente la tabella fornirà un risultato simile a quello mostrato in figura:
che ci permetterà di utilizzare la seguente forma per istanziare un oggetto JTable riempiendolo dei dati provenienti dal ResultSet della query, senza il bisogno di fare alcun riferimento esplicito ad un Modello di tabella:
JTable tabDati = new JTable(datiRighe, nomeColonne);
La visualizzazione del pannello contenente la tabella fornirà un risultato simile a quello mostrato in figura:
Resta inteso che, trattandosi di dati sicuramente di tipo stringa, avrei potuto utilizzare il metodo getString al posto di getObject. La ragione per cui è a mio avviso preferibile utilizzare quest'ultimo metodo è la seguente: se nella tabella fossero stati anche presenti dati da trattare per forza come Object (dati BLOB ed immagini), avrei dovuto differenziare quest'ultimi da quelli di tipo stringa a seconda della posizione della colonna ove saranno rappresentati (come vedremo in seguito) appesantendo inutilmente il codice.
DATI DI TIPO Boolean (boolean):
Per questo genere di dati occorre probabilmente far uso della definizione di un modello di tabella. La semplice istanziazione di un oggetto JTable con i costruttori visti finora, non è infatti ragionevolmente sufficente a ben rappresentare il tipo di dato in questione.
Se infatti volessimo inserire in tabella i dati contenuti in un ResultSet provenienti da una query come la seguente:
SELECT PERSONE.COGNOME, PERSONE.NOME, PERSONE.ASSUNTO
FROM PERSONE;
con ASSUNTO inteso come un campo booleano, non basterebbe variare la forma di riempimento del vettore dati con il seguente codice:
ResultSetMetaData rsmd = rs.getMetaData();
int numColonne = rsmd.getColumnCount();
Vector nomeColonne = new Vector();
int colAssuntoPos = -1;
for (int nr=0; nr<numColonne; nr++) {
String nomeColonna = rsmd.getColumnName(nr+1);
nomeColonne.add(nomeColonna);
if (nomeColonna.equals("ASSUNTO")) colAssuntoPos = nr;
}
Vector <Vector> datiRighe = new Vector <Vector>();
while (rs.next()){
Vector riga = new Vector();
for (int nr=0; nr<numColonne; nr++) {
if (nr==colAssuntoPos) riga.add(rs.getBoolean(nr+1));
else riga.add(rs.getObject(nr+1));
}
datiRighe.add(riga);
}
dove le parti colorate in verde rappresentano il codice variato rispetto a quanto visto per i soli dati di tipo stringa. Nella prima parte, laddove si gestiscono i nomi delle colonne, viene infatti valorizzata una variabile intera colAssuntoPos con la posizione della colonna che conterrà i dati di tipo booleano; nella seconda, ove avviene l'estrazione dei dati, per la sola colonna ASSUNTO viene utilizzato il metodo getBoolean, mentre per le altre è utilizzato il già visto metodo getObject.
Se si andasse ad istanziare un oggetto JTable con il costruttore utilizzato in precedenza, otterremmo un risultato simile a quello riportato in figura:
Se si andasse ad istanziare un oggetto JTable con il costruttore utilizzato in precedenza, otterremmo un risultato simile a quello riportato in figura:
con la colonna ASSUNTO rappresentante dati di tipo booleano, ma in forma testuale, situazione che probabilmente non sarà gradita sia per motivi di aspetto, ma anche e sopratutto per ragioni di poca praticità a variare il dato agendo direttamente sulla tabella.
Il "rendering" e l'"editing" predefiniti di una JTable, prevedono infatti che a rappresentare i dati in questione non sia una JLabel, ma piuttosto una JCheckBox a patto di utilizzare un adeguato modello di tabella.
L'interfaccia TableModel contiene, tra gl'altri, il metodo:
public Class<?> getColumnClass(int indiceColonna)
che, nelle classi che la implementano, dovrà restituire la classe degli oggetti rappresentati in ogni colonna definita dal parametro intero indiceColonna.
L'interfaccia TableModel contiene, tra gl'altri, il metodo:
public Class<?> getColumnClass(int indiceColonna)
che, nelle classi che la implementano, dovrà restituire la classe degli oggetti rappresentati in ogni colonna definita dal parametro intero indiceColonna.
La classe astratta AbstractTableModel, che come detto implementa TableModel, si limita a restituire la classe Object per ogni colonna.
DefaultTableModel, che estende AbstractTableModel, non sovrascrive il predetto metodo: questa è la ragione per cui, utilizzando uno dei costruttori di JTable visti in predenza, poichè gli stessi fanno implicito riferimento ad un istanza di DefaultTableModel, un dato di tipo booleano viene visualizzato in modo testuale.
Occorre, pertanto, fornire all'oggetto JTable, un modello che indichi la giusta classe per ogni colonna.
Ai fini pratici, Il metodo migliore è quello di sfruttare la sintassi della programmazione orientata agli oggetti, utilizzando il costruttore di JTable che permette la definizione del modello della tabella, in questo modo:
Occorre, pertanto, fornire all'oggetto JTable, un modello che indichi la giusta classe per ogni colonna.
Ai fini pratici, Il metodo migliore è quello di sfruttare la sintassi della programmazione orientata agli oggetti, utilizzando il costruttore di JTable che permette la definizione del modello della tabella, in questo modo:
JTable tabDati = new JTable(
new DefaultTableModel(datiRighe, nomeColonne) {
@Override
public Class getColumnClass(int indiceColonna) {
return getValueAt(0, indiceColonna).getClass();
}
});
dove il modello della tabella è definito come nuova istanza anonima della classe DefaultTableModel per la quale è stato usato il costruttore che già permette di indicare il vettore dei dati e quello dei nomi delle colonne. Ricorrendo poi alle classi anonime definite all'interno di un oggetto, è stato sovrascritto il metodo che ci interessa, forzando così l'istanza di DefaultTableModel a restituire la classe dei dati presenti in ogni colonna della prima riga della tabella, attraverso i metodi:
dove il modello della tabella è definito come nuova istanza anonima della classe DefaultTableModel per la quale è stato usato il costruttore che già permette di indicare il vettore dei dati e quello dei nomi delle colonne. Ricorrendo poi alle classi anonime definite all'interno di un oggetto, è stato sovrascritto il metodo che ci interessa, forzando così l'istanza di DefaultTableModel a restituire la classe dei dati presenti in ogni colonna della prima riga della tabella, attraverso i metodi:
getValueAt(int indiceRiga, int indiceColonna), che restituisce un Object;
getClass(), che restituisce la classe di quell'Object.
Utilizzando il predetto costruttore, si otterrà una tabella simile a quella mostrata nella seguente immagine:
dove i dati di tipo booleano sono rappresentati attraverso delle JCheckBox oppurtunamente valorizzate, fornendo un risultato più gradevole e funzionale.
Si segnala infine che il metodo getBoolean, utilizzato in precedenza per estrarre i dati booleani dal ResultSet d'origine, ben funziona anche quando il DB sottostante non prevede l'esistenza di un tipo boolean (come ad esempio SQLite, mySQL, Apache Derby JavaDB e molti altri). In questo caso, in realtà la situazione più comune, il dato di tipo booleano viene in genere sostituito da un tipo intero (o suo sottoinsieme) dove true viene rappresentato da 1 e false da 0: Il metodo getBoolean, in presenza di questi dati, riesce da solo a restituire il corretto dato boolean, senza bisogno di passaggi intermedi.
DATI DI TIPO NUMERICO (Integer, Float, Double)
DATI DI TIPO DATE/TIME (Date, Time, Timestamp):
Se stiamo scorrendo un ResultSet utilizzeremo, per quest'ultima operazione, i metodi getInt, getFloat, getDouble, getDate, getTime o getTimestamp a seconda della situazione.
Il "rendering" e l'"editing" predefiniti prevedono, per rappresentare i dati in questione, l'utilizzo di una JLabel opportunamente formattata secondo il seguente schema:
- Tipo Integer: rappresentati da una JLabel con allineamento a destra;
- Tipo Float e Double: rappresentati da una JLabel in cui la conversione tra dato e testo restituito, viene effettuata attraverso un istanza della classe NumberFormat (vedi JavaDoc) che userà, per definire il formato, le impostazioni locali della macchina ove il codice è in esecuzione;
- Tipo Data/Ora: rappresentati da una JLabel in cui la conversione tra dato e testo restituito, viene effettuata attraverso un istanza della classe DateFormat (vedi JavaDoc) che userà, per definire il formato, una rappresentazione breve del dato in questione.
Ciao e grazie per l'attenzione, ci sentiamo al prossimo post.
Francesco Di Giuseppe
Sei un grande!
RispondiElimina