MokaByte 62 - Aprile 2002 
foto dell'autore non disponibile
Swing JTable
Un potente widget in grado di soddisfare molte esigenze
di
Nicola Colonna
La JTable è un componente swing, veramente potente, che consente, se usato in maniera proficua, di ottenere tabelle e visualizzazioni veramente complesse e funzionali. Come tutte le classi quali ad esempio JList, JTree o altro, è possibile farne un utilizzo semplice e veloce, e con poche righe di codice arrivare ad avere una buona visualizzazione dei nostri dati. D'altro canto, con un po' di complessità in più, è possibile creare GUI esattamente come le abbiamo pensate nella nostra testa, o come il nostro grafico le ha disegnate.

Un primo esempio di JTable
La base di dati da cui partiremo per creare la nostra prima tabella è composta da:

  • Una matrice di Object:
    Object[][] cells = {{"0,0","0,1","0,2"}, {"1,0","1,1","1,2"}};

  • Un'array di String:
    String[] columnNames = {"Colonna1", "Colonna2", "Colonna3"};

La matrice contiene naturalmente i dati che vogliamo rappresentati, ed il fatto che sia composta di Object aumenta di poco la complessità della sua struttura ma ne amplifica esponenzialmente le possibilità. Occorre fare attenzione ed avere sempre presente che classi di oggetti sono rappresentati nelle varie celle dalla nostra tabella.
L'array di stringhe contiene gli headers (le intestazioni) delle colonne della tabella.
Vediamo subito il codice per costruire la nostra prima JTable:

JScrollPane scrollPane = new JScrollPane();
JTable table;
String[] columnNames = {"Colonna1", "Colonna2", "Colonna3"};
Object[][] cells = {{"0,0","0,1","0,2"}, {"1,0","1,1","1,2"}};


table = new JTable(cells, columnNames);
scrollPane.getViewport().add(table);
contentPane.add(scrollPane);

Questa piccola porzione di codice è abbastanza semplice, e come avevamo detto si è usato un array per contenere le intestazioni delle colonne (columnNames), ed una matrice di Object per contenere i dati che saranno visualizzati (cells).
Semplicemente aggiungendo, ad esempio, lo scrollPane al contentPane di un JFrame, si
ottiene il seguente risultato:


Figura 1

 

Il paradigma Document/View (O MVC)
La struttura dati che abbiamo usato è abbastanza semplice e, in generale, non sarà mai tanto più complessa di questa. Però creare una tabella così come è stato fatto, dandole in pasto direttamente la sua struttura dati è un po' come aprire un file XML direttamente con un browser (ad esempio Explorer). Quello che succede è che vengono si mostrati dati, ma senza la possibilità di intervenire sulla loro visualizzazione. Principalmente per lo stesso motivo chi ha creato i componenti swing ha dotato i più complessi di un modello, ossia la possibilità di specificare come gli eventuali dati mostrati dai componenti devono essere visualizzati.
Il vantaggio principale della separazione tra documento e vista è quello di separare il dato dalla sua visualizzazione, secondo il paradigma Document/View. Così come nel web, nel quale, su questo paradigma sono stati definiti standard (nei quali l'XML gioca la parte da leone), anche in ogni buona applicazione Java (e non) è buona norma rispettarlo, svincolando la parte grafica dell'applicazione da tutto quello che è il reperimento, la formattazione o il calcolo dei dati.

 

Il modello della JTable
Un modello, in questo caso modello di tabella, è il punto di riferimento per quanto riguarda i dati della tabella stessa. Ovvero è l'oggetto che gestisce i dati che la popolano. Infatti la JTable, come il JTree, non immagazzina lei stessa i dati che visualizza, ma delega questo, ed altre cose, al suo modello. La figura mostra un esempio di questa interazione.


Figura 2


Nelle poche righe di codice di cui era composto l'esempio precedente non ci si era preoccupati di gestire questa iterazione. In realtà non abbiamo contravvenuto al paradigma Document/View, in quanto, se non specificato, il componente JTable usa un modello base che non fa altro se non quello di passare i dati così come sono alla tabella.
La classe DefaultTableModel implementa questo modello. Nell'esempio sotto riportato una sua istanza viene usata nella creazione della tabella. Il risultato è assolutamente identico all'esempio precedente:

JScrollPane scrollPane = new JScrollPane();
JTable table;
String[] columnNames = {"Colonna1", "Colonna2", "Colonna3"};
Object[][] cells = {{"0,0","0,1","0,2"},
                    {"1,0","1,1","1,2"}
                   };

DefaultTableModel defaultModel = new DefaultTableModel(cells, columnNames);
table = new JTable(defaultModel);
scrollPane.getViewport().add(table);
contentPane.add(scrollPane);

Scrivendo in questo modo si nota come il modello sia creato partendo dai dati da rappresentare ed il componente grafico invece dal suo modello.

 

AbstractTableModel
La classe AbstractTableModel fornisce una prima implementazione dei principali metodi di TableModel. Essa, pertanto, può essere usata come punto di partenza per la definizione di un modello adatta alle nostre esigenze.
Sono solo tre i metodi che dobbiamo ridefinire se estendiamo questo modello:

public int getRowCount();
public int getColumnCount();
public Object getValueAt(int row, int column);

Attraverso questi tre metodi la JTable ottiene risposta alle seguenti domande:

  • Di quante righe è composta la mia base dati
  • Di quante colonne
  • Quale oggetto devo mettere ad una determinata posizione.

Andiamo avanti per gradi. Riprendiamo l'esempio iniziale sostituendo il DefaultTableModel con un nostro modello, che chiameremo SimpleTableModel.
Il codice per creare il SimpleTableModel è il seguente:

class SimpleTableModel extends AbstractTableModel{
  String[] columnNames = {"Colonna1", "Colonna2",                           "Colonna3"};
  String[][] cells = {{"0,0","0,1","0,2"},
                     {"1,0","1,1","1,2"}
                     };

  public int getRowCount(){
    return cells.length;
  }

  public int getColumnCount(){
    return columnNames.length;
  }

  public Object getValueAt(int r, int c){
  return cells[r][c];
  }
}


A questo punto, la dichiarazione della tabella cambia, ma non di molto:

TableModel simpleModel = new SimpleTableModel();
table = new JTable(simpleModel);

Il risultato ottenuto con queste modifiche è il seguente:


Figura 3

Mancano i nomi giusti delle colonne. Quelli che vediamo sono nomi dati di default dal nostro modello, che non ha avuto da noi informazioni su quali questi siano. Per fornirgliele è sufficiente l'overriding del metodo getColumnName, cioè l'aggiunta alla nostra nuova classe del metodo:

public String getColumnName(int c){
  return columnNames[c];
}

A questo punto il risultato è tornato identico a quello dell'esecuzione originaria.
O
ra la gestione dei dati è passata tutta al modello. Vediamo di fargli fare qualche cosa che possa avere più senso della semplice visualizzazione dei dati. Costruiamocene uno che ci consenta di avere due colonne, in cui sono immagazzinati valori interi, ed una in cui viene mostrata, ad esempio, la somma delle prime due.
Per fare questo occorre ridefinire il metodo getVaueAt, in modo che sappia riconoscere il fatto che, nel caso sia richiesto un valore dell'ultima colonna, deve essere restituita la sommatoria dei valori di tutte le altre colonne della stessa riga. Tradotto in codice può diventare:

class SimpleTableModel extends AbstractTableModel{
  String[] columnNames = {"Valore1",
                          "Valore2", "Somma"};
  Integer[][] cells = {{new Integer(4),
                        new Integer(5)},
                        {new Integer(7), new Integer(8)}
                      };

  public int getRowCount(){
    return cells.length;
  }

  public int getColumnCount(){
    return columnNames.length;
  }

  public Object getValueAt(int r, int c){
    if (c < columnNames.length - 1)
      return cells[r][c];
    else{
      int value = 0;
      for (int i = 0; i < columnNames.length-1; i++){
       value += ((Integer)cells[r][i]).intValue();
      }
      return new Integer(value);
    }
  }

  public String getColumnName(int c){
    return columnNames[c];
  }
}


Abbiamo visto come gestire i dati per caricare una tabella. Nelle prossime lezioni vedremo come la JTable li visualizza, per mezzi dei renderer delle singole celle, e useremo, come fonte dati, una tabella di un database relazionale.

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it