Afficher un tableau avec un Table Model Swing en 5 minutes

Thierry

Ce miniarticle montre (par l'exemple) comment ajouter un tableau dans une fenêtre Swing, à l'aide d'un table model, en quelques minutes. 11 commentaires Donner une note à l'article (5)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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
http://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"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)
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
Sélectionnez
			
public class Eleve {

	private String nom;
	private String prenom;
	private Integer annee;
	private Sexe sexe;
	
	// plus getters et setters
	...
Bean NoteEleve
Sélectionnez
			
public class NoteEleve {

	private Eleve eleve;
	private Double note;
	
	// plus getters et setters
	...
Enum Sexe
Sélectionnez
			
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
Sélectionnez

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
Sélectionnez

	/**
	 * 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
Sélectionnez

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
Sélectionnez

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
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
Sélectionnez

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
Sélectionnez

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
Sélectionnez

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
Sélectionnez

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
Sélectionnez

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
Sélectionnez

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
Sélectionnez

public class NotesJFrame extends JFrame {

	private NotesModele modele;
	
	public NotesJFrame() {
		...

		modele = new NotesModele();
		
		pack();
	}

Puis on ajoute le tableau.

Tableau dans l'IHM
Sélectionnez

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
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
Sélectionnez

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)
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
Sélectionnez

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
Sélectionnez

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
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
Sélectionnez
							
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
Sélectionnez

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
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.code final

Retrouvez les tutoriels de la série "en 5 minutes" sur developpez.com à l'adresse http://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

Thierry

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
http://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
http://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
http://baptiste-wicht.developpez.com/tutoriels/java/swing/jtable

VI-B. Les classes importantes en entier

NotesModele
Sélectionnez
				
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
Sélectionnez
	
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
Sélectionnez
	
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
Sélectionnez

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

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

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.