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 = -1944258978183994752L;
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 = 3928008548751894521L;
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 = -5127020452000139717L;
@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 = 7842444473467746594L;
@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;
}
}










