I. Introduction▲
Dans ce document, nous allons voir comment créer un « table model » simple puis s'en servir pour afficher et alimenter un tableau dans une IHM Swing.
I-A. À propos▲
Découvrir une technologie n'est pas chose facile. En aborder plusieurs d'un coup l'est encore moins. Partant de ce constat, cet article a été écrit pour aller à l'essentiel. Les points importants sont présentés dans le corps de l'article et les éléments secondaires sont expliqués en annexes.
I-B. Avant de commencer▲
Dans ce tutoriel, j'utilise Maven et Eclipse à titre d'illustration. Ils n'ont absolument rien à voir avec les « table model ». Néanmoins il me fallait un support de travail.
Pour écrire ce tutoriel, j'ai utilisé les éléments suivants :
- Java JDK 1.6.0_24-b07 ;
- Eclipse Indigo 3.7 JEE 64b ;
- Maven 3.0.3.
Avant de commencer, je conseille aux débutants de lire le tutoriel « Importer un projet Maven dans Eclipse en 5 minutes » disponible à l'adresse
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/importer-projet-maven-dans-eclipse-5-minImporter un projet Maven dans Eclipse en 5 minutes.
I-C. Mise à jour▲
25/01/2012 : Ajout d'un lien vers la série d'articles « en 5 minutes ».
II. Découverte du projet d'exemple▲
II-A. Télécharger, installer et importer le projet d'exemple▲
Pour commencer, je vous propose de télécharger le fichier Zip « notes1.zip », contenant un projet Java-Maven d'exemple.
Compilez le projet d'exemple et importez-le dans Eclipse (comme expliqué dans le tutoriel « Importer un projet Maven dans Eclipse en 5 minutes ») ou dans l'IDE de votre choix.
Pour suivre ce tutoriel, vous pouvez vous contenter de lire les codes proposés ci-dessous (codes complets en annexes) et de faire confiance aux captures d'écran.
II-B. Les classes déjà présentes dans le projet d'exemple▲
Le projet contient des classes qui ne sont liées ni aux tableaux ni aux « table model ». Ces classes vont servir de support pour la suite.
D'abord, le projet utilise deux Java beans et un « enum » pour décrire le métier : Eleve.java, NoteEleve.java et Sexe.java. Ce sont des classes relativement simples avec des attributs et des accesseurs.
public
class
Eleve {
private
String nom;
private
String prenom;
private
Integer annee;
private
Sexe sexe;
// plus getters et setters
...
public
class
NoteEleve {
private
Eleve eleve;
private
Double note;
// plus getters et setters
...
public
enum
Sexe {
HOMME
(
"Garçon"
), FEMME
(
"Fille"
);
private
final
String label;
Sexe
(
String label) {
this
.label =
label;
}
public
String getLabel
(
) {
return
label;
}
}
Le projet dispose d'un service (singleton) qui renvoie la liste des notes des élèves au dernier examen.
public
class
NoteService {
private
static
NoteService instance;
private
List<
NoteEleve>
notes;
private
NoteService
(
) {
... }
public
static
synchronized
NoteService getInstance
(
) {
if
(
instance ==
null
) {
instance =
new
NoteService
(
);
}
return
instance;
}
public
synchronized
List<
NoteEleve>
findLastNotes
(
) {
chargerDepuisBaseDeDonnees
(
);
return
notes;
}
Pour simplifier ce tutoriel, les notes sont « codées en dur » directement dans le service (on parle de « bouchon ») et ne sont donc pas chargées depuis la base de données.
/**
* Bouchon.
*
* Dans un vrai programme, ces donnees seraient chargees depuis la base.
*/
private
void
chargerDepuisBaseDeDonnees
(
) {
if
(
notes !=
null
) {
return
;
}
notes =
new
ArrayList<
NoteEleve>(
);
// Filles (nom, prenom, annee, sexe, note)
addEleveEtNote
(
"Durand"
, "Marie"
, 3
, FEMME, 5
);
addEleveEtNote
(
"Alesi"
, "Julie"
, 3
, FEMME, 8
);
...
// Garcons (nom, prenom, annee, sexe, note)
addEleveEtNote
(
"Michelet"
, "Jean"
, 3
, HOMME, 0
);
addEleveEtNote
(
"Dupond"
, "Pierre"
, 3
, HOMME, 2
);
addEleveEtNote
(
"Timberot"
, "Martin"
, 3
, HOMME, 4
);
addEleveEtNote
(
"Gravatas"
, "Paul"
, 3
, HOMME, 5
);
...
}
private
void
addEleveEtNote
(
String nom, String prenom, Integer annee, Sexe sexe, int
note) {
addEleveEtNote
(
nom, prenom, annee, sexe, new
Double
(
note));
}
private
void
addEleveEtNote
(
String nom, String prenom, Integer annee, Sexe sexe, Double note) {
final
Eleve eleve =
new
Eleve
(
nom, prenom, annee, sexe);
final
NoteEleve noteEleve =
new
NoteEleve
(
eleve, note);
notes.add
(
noteEleve);
}
Une classe très simple, héritant de JFrame, sert d'interface graphique (IHM) pour le programme.
public
class
NotesJFrame extends
JFrame {
public
NotesJFrame
(
) {
super
(
);
setTitle
(
"Notes des élèves"
);
setPreferredSize
(
new
Dimension
(
500
, 400
));
setDefaultCloseOperation
(
JFrame.EXIT_ON_CLOSE);
pack
(
);
}
}
Enfin, une dernière classe, possédant une méthode « main() » permet de lancer l'application d'exemple.
public
class
Launcher {
public
static
void
main
(
String[] args) {
NotesJFrame notesJFrame =
new
NotesJFrame
(
);
notesJFrame.setVisible
(
true
);
}
}
Pour le moment, l'IHM est donc relativement vide. Dans le chapitre suivant, nous verrons comment ajouter le tableau des notes, ce qui est l'objectif de ce tutoriel.
III. Action▲
III-A. Table model▲
Durée estimée : 2 minutes.
Pour commencer, écrivons un « table model » avec juste ce qu'il faut pour que cela compile. Eclipse nous aide dans cette tâche en créant directement les méthodes à implémenter.
public
class
NotesModele extends
AbstractTableModel {
@Override
public
int
getColumnCount
(
) {
// TODO Auto-generated method stub
return
0
;
}
@Override
public
int
getRowCount
(
) {
// TODO Auto-generated method stub
return
0
;
}
@Override
public
Object getValueAt
(
int
arg0, int
arg1) {
// TODO Auto-generated method stub
return
null
;
}
}
Suite à la lecture de ce code, on comprend bien que le « table model » est lié au tableau de données que l'on souhaite afficher. Les deux premières méthodes indiquent le nombre de colonnes et de lignes tandis que la troisième méthode permet d'avoir le contenu d'une cellule.
On souhaite aussi que les colonnes du tableau aient un entête.
public
class
NotesModele extends
AbstractTableModel {
private
final
String[] entetes =
{
"Nom"
, "Prénom"
, "Année"
, "Sexe"
, "Note"
}
;
@Override
public
int
getColumnCount
(
) {
return
entetes.length;
}
@Override
public
String getColumnName
(
int
columnIndex) {
return
entetes[columnIndex];
}
...
En procédant comme ça, on va clairement choisir les données qui nous intéressent parmi toutes celles qui seront renvoyées par le service.
On va maintenant connecter le « table model » au service.
public
class
NotesModele extends
AbstractTableModel {
private
NoteService noteService;
public
NotesModele
(
) {
noteService =
NoteService.getInstance
(
);
}
...
Dans la foulée, on va en profiter pour aller chercher les notes du dernier examen directement depuis le constructeur, ce qui sera largement assez bien dans le cadre de ce tutoriel.
public
class
NotesModele extends
AbstractTableModel {
private
NoteService noteService;
private
List<
NoteEleve>
notes;
public
NotesModele
(
) {
noteService =
NoteService.getInstance
(
);
notes =
noteService.findLastNotes
(
);
}
public
List<
NoteEleve>
getNotes
(
) {
return
notes;
}
...
Maintenant qu'on a une liste, on peut compléter les méthodes restées vides.
public
class
NotesModele extends
AbstractTableModel {
@Override
public
int
getRowCount
(
) {
return
notes.size
(
);
}
@Override
public
Object getValueAt
(
int
rowIndex, int
columnIndex) {
switch
(
columnIndex) {
case
0
:
// Nom
return
notes.get
(
rowIndex).getEleve
(
).getNom
(
);
case
1
:
// Prenom
return
notes.get
(
rowIndex).getEleve
(
).getPrenom
(
);
case
2
:
// Annee
return
notes.get
(
rowIndex).getEleve
(
).getAnnee
(
);
case
3
:
// Sexe
return
notes.get
(
rowIndex).getEleve
(
).getSexe
(
);
case
4
:
// Note au controle
return
notes.get
(
rowIndex).getNote
(
);
default
:
throw
new
IllegalArgumentException
(
);
}
}
...
Petite subtilité, on va aider le « table model » à distinguer les types de données associés à chaque cellule. Cela prendra du sens plus tard.
public
class
NotesModele extends
AbstractTableModel {
@Override
public
Class<
?>
getColumnClass
(
int
columnIndex) {
switch
(
columnIndex) {
case
0
:
case
1
:
return
String.class
;
case
3
:
return
Sexe.class
;
case
2
:
return
Integer.class
;
case
4
:
return
Double.class
;
default
:
return
Object.class
;
}
}
...
À ce stade, on a un « table model » relativement simple, mais suffisant.
III-B. Tableau▲
Durée estimée : 1 minute.
On va maintenant ajouter un tableau à l'IHM en se servant du « table model » qu'on vient d'écrire. On commence par ajouter une référence au « table model ».
public
class
NotesJFrame extends
JFrame {
private
NotesModele modele;
public
NotesJFrame
(
) {
...
modele =
new
NotesModele
(
);
pack
(
);
}
Puis on ajoute le tableau.
public
class
NotesJFrame extends
JFrame {
private
NotesModele modele;
private
JTable table;
public
NotesJFrame
(
) {
...
modele =
new
NotesModele
(
);
table =
new
JTable
(
modele);
getContentPane
(
).add
(
new
JScrollPane
(
table), CENTER);
pack
(
);
}
Nous avons presque fini et nous pouvons déjà admirer le résultat en lançant l'application.
III-C. Un tableau plus beau▲
Durée estimée : 2 minutes.
On va maintenant essayer de rendre l'affichage un peu plus sexy. Pour commencer, on va rendre triables les colonnes.
public
class
NotesJFrame extends
JFrame {
private
NotesModele modele;
private
JTable table;
public
NotesJFrame
(
) {
...
table =
new
JTable
(
modele);
table.setAutoCreateRowSorter
(
true
); // Java 6
...
On s'intéresse ensuite à l'apparence des cellules. Pour cela il faut créer des objets « TableCellRenderer ». On commence par le sexe. On ne veut pas afficher le nom de l'enum, mais son label. En outre, on veut écrire en bleu pour les garçons et en rose pour les filles.
public
class
SexeCellRenderer extends
DefaultTableCellRenderer {
@Override
public
Component getTableCellRendererComponent
(
JTable table, Object value, boolean
isSelected, boolean
hasFocus, int
row, int
column) {
super
.getTableCellRendererComponent
(
table, value, isSelected, hasFocus, row, column);
Sexe sexe =
(
Sexe) value;
setText
(
sexe.getLabel
(
));
if
(
sexe ==
FEMME) {
setForeground
(
PINK);
}
else
{
setForeground
(
BLUE);
}
return
this
;
}
}
On peut simplement utiliser ce « renderer » sur le tableau pour toutes les cellules dont le contenu est de type « Sexe ».
public
class
NotesJFrame extends
JFrame {
public
NotesJFrame
(
) {
...
modele =
new
NotesModele
(
);
table =
new
JTable
(
modele);
table.setAutoCreateRowSorter
(
true
);
table.setDefaultRenderer
(
Sexe.class
, new
SexeCellRenderer
(
));
...
On veut ensuite mettre en évidence toutes les notes en dessous de la moyenne (pour donner un travail supplémentaire aux élèves concernés).
public
class
NoteCellRenderer extends
DefaultTableCellRenderer {
@Override
public
Component getTableCellRendererComponent
(
JTable table, Object value, boolean
isSelected, boolean
hasFocus, int
row, int
column) {
super
.getTableCellRendererComponent
(
table, value, isSelected, hasFocus, row, column);
Double note =
(
Double) value;
setText
(
note.toString
(
));
if
(
note <
10
) {
setBackground
(
Color.RED);
}
else
{
setBackground
(
Color.GREEN);
}
return
this
;
}
}
Cette fois, on va indiquer à quelle colonne s'applique ce renderer. En effet, on peut penser qu'il n'y aura qu'une seule colonne de type « Sexe », mais plusieurs de type « Double ».
public
class
NotesJFrame extends
JFrame {
public
NotesJFrame
(
) {
...
modele =
new
NotesModele
(
);
table =
new
JTable
(
modele);
table.setAutoCreateRowSorter
(
true
);
table.setDefaultRenderer
(
Sexe.class
, new
SexeCellRenderer
(
));
table.getColumnModel
(
).getColumn
(
4
).setCellRenderer
(
new
NoteCellRenderer
(
));
...
Nous allons en rester là dans le cadre de ce tutoriel, mais on pourrait encore s'amuser longtemps avec les tableaux et les « table model ».
IV. Conclusions▲
Globalement, il est donc assez simple d'utiliser un « table model » pour afficher un tableau en Swing. Bien qu'on ne le voie pas dans ce petit article, les « table model » deviennent vraiment intéressants lorsqu'on veut modifier ou mettre à jour les données (pour s'en rendre compte, je conseille vivement la lecture des tutoriels que j'ai indiqués en annexes). Dans le cadre d'une approche MVC avec Swing, les « table model » deviennent même incontournables.
Le code final de ce tutoriel est disponible dans le fichier ZIP notes2.zip.code final
Retrouvez les tutoriels de la série « en 5 minutes » sur developpez.com à l'adresse https://thierry-leriche-dessirier.developpez.com/#page_articlesEn 5 minutes
V. Remerciements▲
Je tiens à remercier, en tant qu'auteur de tutoriel rapide, toutes les personnes qui m'ont aidé et soutenu. Je pense tout d'abord à mes collègues qui subissent mes questions au quotidien, mais aussi à mes contacts et amis du web, dans le domaine de l'informatique ou non, qui m'ont fait part de leurs remarques et critiques. Bien entendu, je n'oublie pas l'équipe de developpez.com qui m'a guidé dans la rédaction de cet article et m'a aidé à le corriger et le faire évoluer, principalement sur le forum.
Plus particulièrement j'adresse mes remerciements, par ordre alphabétique, à jacques_jean, keulkeul et le yam's
VI. Annexes▲
VI-A. Liens▲
Pour suivre cet article, il faut lire le tutoriel « Importer un projet Maven dans Eclipse en 5 minutes » disponible à l'adresse
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/importer-projet-maven-dans-eclipse-5-minImporter un projet Maven dans Eclipse en 5 minutes
Pour aller plus loin avec les tableaux, je conseille la lecture des tutoriels suivants :
« JTables - Un autre regard » de Nicolas Zozol sur developpez.com à l'adresse
https://nicolas-zozol.developpez.com/tutoriel/java/jtable
et « Création interface graphique avec Swing : les tableaux (JTable) » de Baptiste Wicht sur developpez.com à l'adresse
https://baptiste-wicht.developpez.com/tutoriels/java/swing/jtable
VI-B. Les classes importantes en entier▲
package
com.thi.notes.ihm;
import
java.util.List;
import
javax.swing.table.AbstractTableModel;
import
com.thi.notes.domain.NoteEleve;
import
com.thi.notes.domain.Sexe;
import
com.thi.notes.service.NoteService;
public
class
NotesModele extends
AbstractTableModel {
/**
* serialVersionUID
*/
private
static
final
long
serialVersionUID =
-
1944258978183994752
L;
private
final
String[] entetes =
{
"Nom"
, "Prénom"
, "Année"
, "Sexe"
, "Note"
}
;
private
NoteService noteService;
private
List<
NoteEleve>
notes;
public
NotesModele
(
) {
super
(
);
noteService =
NoteService.getInstance
(
);
notes =
noteService.findLastNotes
(
);
}
@Override
public
int
getColumnCount
(
) {
return
entetes.length;
}
@Override
public
String getColumnName
(
int
columnIndex) {
return
entetes[columnIndex];
}
@Override
public
int
getRowCount
(
) {
return
notes.size
(
);
}
@Override
public
Object getValueAt
(
int
rowIndex, int
columnIndex) {
switch
(
columnIndex) {
case
0
:
// Nom
return
notes.get
(
rowIndex).getEleve
(
).getNom
(
);
case
1
:
// Prenom
return
notes.get
(
rowIndex).getEleve
(
).getPrenom
(
);
case
2
:
// Annee
return
notes.get
(
rowIndex).getEleve
(
).getAnnee
(
);
case
3
:
// Sexe
return
notes.get
(
rowIndex).getEleve
(
).getSexe
(
);
case
4
:
// Note au controle
return
notes.get
(
rowIndex).getNote
(
);
default
:
throw
new
IllegalArgumentException
(
);
}
}
@Override
public
Class<
?>
getColumnClass
(
int
columnIndex) {
switch
(
columnIndex) {
case
0
:
case
1
:
return
String.class
;
case
3
:
return
Sexe.class
;
case
2
:
return
Integer.class
;
case
4
:
return
Double.class
;
default
:
return
Object.class
;
}
}
public
List<
NoteEleve>
getNotes
(
) {
return
notes;
}
}
package
com.thi.notes.ihm;
import
static
java.awt.BorderLayout.CENTER;
import
static
javax.swing.JFrame.EXIT_ON_CLOSE;
import
java.awt.Dimension;
import
javax.swing.JFrame;
import
javax.swing.JScrollPane;
import
javax.swing.JTable;
import
com.thi.notes.domain.Sexe;
public
class
NotesJFrame extends
JFrame {
/**
* serialVersionUID
*/
private
static
final
long
serialVersionUID =
3928008548751894521
L;
private
NotesModele modele;
private
JTable table;
public
NotesJFrame
(
) {
super
(
);
setTitle
(
"Notes des élèves"
);
setPreferredSize
(
new
Dimension
(
500
, 400
));
setDefaultCloseOperation
(
EXIT_ON_CLOSE);
modele =
new
NotesModele
(
);
table =
new
JTable
(
modele);
table.setAutoCreateRowSorter
(
true
);
table.setDefaultRenderer
(
Sexe.class
, new
SexeCellRenderer
(
));
table.getColumnModel
(
).getColumn
(
4
).setCellRenderer
(
new
NoteCellRenderer
(
));
getContentPane
(
).add
(
new
JScrollPane
(
table), CENTER);
pack
(
);
}
}
package
com.thi.notes.ihm;
import
static
java.awt.Color.BLUE;
import
static
java.awt.Color.PINK;
import
java.awt.Component;
import
javax.swing.JTable;
import
javax.swing.table.DefaultTableCellRenderer;
import
com.thi.notes.domain.Sexe;
public
class
SexeCellRenderer extends
DefaultTableCellRenderer {
/**
* serialVersionUID
*/
private
static
final
long
serialVersionUID =
-
5127020452000139717
L;
@Override
public
Component getTableCellRendererComponent
(
JTable table, Object value, boolean
isSelected, boolean
hasFocus, int
row, int
column) {
super
.getTableCellRendererComponent
(
table, value, isSelected, hasFocus, row, column);
Sexe sexe =
(
Sexe) value;
setText
(
sexe.getLabel
(
));
if
(
sexe ==
Sexe.FEMME) {
setForeground
(
PINK);
}
else
{
setForeground
(
BLUE);
}
return
this
;
}
}
package
com.thi.notes.ihm;
import
static
java.awt.Color.GREEN;
import
static
java.awt.Color.RED;
import
java.awt.Component;
import
javax.swing.JTable;
import
javax.swing.table.DefaultTableCellRenderer;
public
class
NoteCellRenderer extends
DefaultTableCellRenderer {
/**
* serialVersionUID
*/
private
static
final
long
serialVersionUID =
7842444473467746594
L;
@Override
public
Component getTableCellRendererComponent
(
JTable table, Object value, boolean
isSelected, boolean
hasFocus, int
row, int
column) {
super
.getTableCellRendererComponent
(
table, value, isSelected, hasFocus, row, column);
Double note =
(
Double) value;
setText
(
note.toString
(
));
if
(
note <
10
) {
setBackground
(
RED);
}
else
{
setBackground
(
GREEN);
}
return
this
;
}
}