Afficher un tableau avec un Table Model Swing en 5 minutes

Thierry Leriche-Dessirier

Ce miniarticle montre (par l'exemple) comment ajouter un tableau dans une fenêtre Swing, à l'aide d'un table model, en quelques minutes. Commentez ♪

- Titre : Afficher un tableau avec un Table Model Swing en 5 minutes

- Auteur : Thierry Leriche-Dessirier

- Parution : 13 janvier 2012

- Mise à jour : 25 janvier 2012

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2011 Thierry Leriche-Dessirier. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. À propos

- I-B. Avant de commencer

- I-C. Mise à jour

- II. Découverte du projet d'exemple

- II-A. Télécharger, installer et importer le projet d'exemple

- II-B. Les classes déjà présentes dans le projet d'exemple

- III. Action

- III-A. Table model

- III-B. Tableau

- III-C. Un tableau plus beau

- IV. Conclusions

- V. Remerciements

- VI. Annexes

- VI-A. Liens

- VI-B. Les classes importantes en entier

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-min.

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.

Le projet dans Eclipse (on voit les classes de départ)

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.

Bean Eleve

public class Eleve {

private String nom;
private String prenom;
private Integer annee;
private Sexe sexe;

// plus getters et setters
...

Bean NoteEleve

public class NoteEleve {

private Eleve eleve;
private Double note;

// plus getters et setters
...

Enum Sexe

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.

Service NoteService

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 le service

/**
* 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.

IHM

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.

Main

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.

Une IHM bien vide

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.

Table model minimal

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.

Entêtes

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.

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.

Notes

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.

Valeurs des cellules

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.

Types

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 ».

Table model dans l'IHM

public class NotesJFrame extends JFrame {

private NotesModele modele;

public NotesJFrame() {
...

modele = new NotesModele();

pack();
}

Puis on ajoute le tableau.

Tableau dans l'IHM

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.

Le tableau est visible, mais laid

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.

Tri sur les colonnes

public class NotesJFrame extends JFrame {

private NotesModele modele;
private JTable table;

public NotesJFrame() {
...

table = new JTable(modele);
table.setAutoCreateRowSorter(true);    // Java 6

...

Colonnes triables (ici avec un tri sur la colonne des prénoms)

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.

SexeCellRenderer

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 ».

Du sexe en tableau

public class NotesJFrame extends JFrame {

public NotesJFrame() {
...

modele = new NotesModele();

table = new JTable(modele);
table.setAutoCreateRowSorter(true);
table.setDefaultRenderer(Sexe.class, new SexeCellRenderer());

...

Mise en forme selon le sexe

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).

NoteCellRenderer

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 ».

Un tableau en couleur

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());

...

Les mauvaises notes en rouge

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.

Retrouvez les tutoriels de la série « en 5 minutes » sur developpez.com à l'adresse https://thierry-leriche-dessirier.developpez.com/#page_articles

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-min

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

NotesModele

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;
}
}

NotesJFrame

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();
}

}

SexeCellRenderer

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;
}

}

NoteCellRenderer

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;
}
}