I. Introduction▲
Durant ce TP, vous allez approfondir les connaissances, déjà abordées en cours de POO, sur les interfaces graphiques avec Swing. En particulier, vous mettrez en place des tableaux. Ce composant pose souvent des problèmes lorsqu'on débute avec Swing. Vous allez donc vous limiter aux notions simples, mais qui couvrent la plupart des besoins. Vous créerez ensuite des graphes à l'aide de la bibliothèque « JFreeChart », ce qui sera relativement amusant.
Ce TP est la suite du TP « tp-chien-dao » dans lequel vous avez découvert (ou redécouvert) Maven, Eclipse, les logs, les tests, les fichiers CSV et quelques bibliothèques utiles. Je vous conseille donc très vivement de le lire avant de poursuivre avec ce nouveau TP.
Des pistes et/ou des solutions sont proposées en annexes. Essayez de résoudre les problèmes vous-mêmes avant de regarder les réponses.
Demandez l'aide du professeur ou de son assistant si vous restez bloqué trop longtemps. La FAQ en annexe du TP « tp-chien-dao » contient les réponses qu'on me pose le plus souvent. Merci de consulter cette FAQ avant d'appeler le professeur.
I-A. À propos▲
Ce document est la retranscription d'un TP de Génie Logiciel que je donne à mes élèves de l'ESIEAESIEA. Je l'offre (gratuitement) à la communauté, en particulier aux autres professeurs qui peuvent le proposer à leurs propres étudiants.
En contrepartie, je vous demande simplement de ne pas modifier ce document. Au contraire, je vous invite à en communiquer directement l'adresse (sur Developpez.com) et de profiter ainsi des futures mises à jour. N'oubliez pas de me le faire savoir, le cas échéant. Par avance, je vous remercie pour votre participation.
I-B. Versions des logiciels et bibliothèques utilisés▲
Pour écrire ce document, j'ai utilisé les versions suivantes :
- Java JDK 1.8 ;
- Eclipse Indigo 3.7 JEE 64b ;
- Maven 3.0.3 ;
- Log4J 1.2.13 ;
- CSV Engine 1.3.5 ;
- JFreeChart 1.0.14.
I-C. Mises à jour▲
1er janvier 2013 : Création.
1er novembre 2016 : Remplacement de tout le chapitre dédié à l'import dans Eclipse et à la préparation Maven.
II. Préparation▲
Avant d'entrer dans le vif du sujet, vous devez suivre quelques étapes simples (mais intéressantes) pour préparer le travail.
II-A. Télécharger le projet d'exemple▲
Durée estimée : 1 minute.
Pour commencer, s'il ne vous a pas été fourni par votre professeur, téléchargez le fichier tp-chien-ihm-01.zipFichier tp-chien-ihm-01.zip, contenant un projet Java-Maven d'exemple.
Décompressez le fichier « tp-chien-ihm-01.zip ».
Vous devez normalement avoir un dossier avec les éléments suivants :
- le dossier « src » ";
- le fichier « pom.xml »;
- les fichiers « build.bat » et « build.sh »;
- les fichiers « license.txt » et « README.txt » (ces deux derniers fichiers ne sont pas importants pour la suite).
II-B. Import dans Eclipse (projet Maven)▲
Durée estimée : 2 minutes.
Importez le projet directement dans Eclipse car cet IDE sait traiter les projets Maven. Pour cela, lancez donc Eclipse si ce n'est pas déjà fait. Utilisez le menu « File/Import ». Dans la popup il faut prendre « Maven/Existing Maven projet ». Dans la seconde popup, utilisez le bouton « Browse » pour aller jusqu'au dossier que vous avez dézippé et où se trouve le fichier « pom.xml ». Enfin cliquez sur le bouton « Finish ». Eclipse devrait mouliner quelques instants.
II-C. Les objets du domaine▲
Durée estimée : 5 minutes.
Les objets que vous allez utiliser sont exactement les mêmes que ceux du TP « tp-chien-dao ».
Pour rappel, le cœur du domaine est constitué par l'interface Chien, qui définit quelques getters simples :
public
interface
Chien extends
Serializable {
String getNom
(
);
String getNomComplet
(
);
Sexe getSexe
(
);
RaceDeChien getRace
(
);
List<
String>
getCouleurs
(
);
Double getPoids
(
);
}
La classe SimpleChien est l'implémentation (par défaut) de Chien.
public
class
SimpleChien implements
Chien {
private
String nom;
private
String nomComplet;
private
Sexe sexe;
private
RaceDeChien race;
private
List<
String>
couleurs;
private
Double poids;
public
SimpleChien
(
) {
// rien...
}
public
SimpleChien
(
final
String nom) {
this
.nom =
nom;
}
public
SimpleChien
(
final
String nom, final
RaceDeChien race, final
Sexe sexe) {
this
(
nom);
this
.race =
race;
this
.sexe =
sexe;
}
@Override
public
String toString
(
) {
return
"SimpleChien [nom="
+
nom +
"]"
;
}
// Getters et setters
...
}
La classe « SimpleChien » (et « Chien ») définit des attributs vers les enums « Sexe » et « RaceDeChien », dont l'utilité ne devrait pas nécessiter d'explication.
public
enum
Sexe {
FEMALE
(
2
),
MALE
(
1
);
private
final
int
code;
Sexe
(
final
int
code) {
this
.code =
code;
}
public
static
Sexe valueOfByCode
(
final
int
code) {
switch
(
code) {
case
2
: return
FEMALE;
case
1
: return
MALE;
default
:
throw
new
IllegalArgumentException
(
"Le sexe demande n'existe pas."
);
}
}
public
boolean
isMale
(
) {
return
this
==
MALE;
}
...
}
public
enum
RaceDeChien {
BASSET_ALPES
(
"Basset des Alpes"
, "basset_alp"
),
BERGER_ALLEMAND
(
"Berger allemand"
, "berger_all"
),
CANICHE
(
"Caniche"
, "caniche"
),
HARRIER
(
"Harrier"
, "harrier"
),
GOLDEN
(
"Golden retriever"
, "golden_ret"
),
...
ROTTWEILER
(
"Rottweiler"
, "rottweiler"
),
WESTIE
(
"Westie"
, "westie"
);
private
final
String label;
private
final
String code;
RaceDeChien
(
final
String label, final
String code) {
this
.label =
label;
this
.code =
code;
}
public
static
RaceDeChien valueOfByCode
(
final
String code) {
...
for
(
RaceDeChien race : values
(
)) {
if
(
race.code.equalsIgnoreCase
(
code)) {
return
race;
}
}
throw
new
IllegalArgumentException
(
"La race de chien demandee n'existe pas."
);
}
...
}
II-D. DAO▲
Le programme sait lire des données depuis des fichiers CSV et/ou une base de données. Ceci était l'objet du précédent TP et ne sera donc pas réexpliqué ici. Dans la suite, considérez que les méthodes définies dans l'interface « CsvChienDao » fonctionnent parfaitement et qu'elles ont déjà été testées.
III. Data model▲
Un JTable est un composant Swing permettant d'afficher un tableau. En plus des lignes de contenu, il a également une ligne d'entête avec un titre pour chaque colonne. Ce composant a donc, d'un côté, des données et, de l'autre, des données d'en-tête. On peut voir les données comme un tableau à deux dimensions dans lequel chaque valeur correspond à une cellule du tableau. Quant aux entêtes, on peut les voir comme un tableau de String.
Le composant JTable utilise différents concepts de Swing :
- un modèle pour stocker les données. Un JTable utilise une classe implémentant TableModel. Nous verrons plus loin comment spécifier un modèle pour un JTable ;
- un renderer pour le rendu des cellules. On peut spécifier un TableCellRenderer pour chaque classe de données. Nous verrons plus loin ce que ça signifie exactement ;
- un éditeur pour l'édition du contenu d'une cellule. On peut spécifier un TableCellEditor pour chaque classe de données.
III-A. Interface épurée▲
Durée estimée : 2 minutes.
Vous allez maintenant créer une IHM simplifiée, pour accueillir le tableau avec la liste des chiens. Pour cela, créez la classe « ChienJFrame1 » dans le package « com.icauda.tp.chien.ihm » en vous inspirant du code suivant :
public
class
ChienJFrame1 extends
JFrame {
public
ChienJFrame1
(
) {
setTitle
(
"Liste des chiens"
);
setPreferredSize
(
new
Dimension
(
500
, 400
));
setDefaultCloseOperation
(
EXIT_ON_CLOSE);
pack
(
);
}
}
}
Complétez ensuite la classe « LauncherIHM » pour qu'elle crée et ouvre l'IHM en vous inspirant du code suivant :
public
class
LauncherIHM {
...
public
static
void
main
(
String[] args) {
final
ChienJFrame1 f =
new
ChienJFrame1
(
);
f.setVisible
(
true
);
...
}
}
}
Lancez l'exécution de « LauncherIHM ». Ça doit vous ouvrir une fenêtre vide, à la bonne dimension et avec juste un titre, comme sur la capture d'écran suivante :
Cette IHM sera suffisante pour ce TD. Maintenant que tout est en place, nous allons pouvoir entrer dans le vif du sujet : les tableaux.
public
class
LauncherIHM {
...
public
static
void
main
(
String[] args) {
final
ChienJFrame1 f =
new
ChienJFrame1
(
);
f.setVisible
(
true
);
...
}
}
}
III-B. Données dans un tableau▲
Durée estimée : 10 minutes.
La façon la plus simple, mais pas forcément la meilleure, d'alimenter un JTable est de lui passer directement un tableau de données.
Complétez le code de la fenêtre en vous inspirant du code suivant :
public
class
ChienJFrame1 extends
JFrame {
...
public
ChienJFrame1
(
) {
...
final
Object[][] donnees =
{
{
"Pluto"
, "Pluto le chien Disney"
, Sexe.MALE, RaceDeChien.GOLDEN, "jaune"
, 24.0
}
,
{
"Lassie"
, "Lassie des alpes"
, Sexe.FEMALE, RaceDeChien.BERGER_ALLEMAND, "blanc,noir"
, 32.3
}
,
{
"Volt"
, "Volt le super chien"
, Sexe.MALE, RaceDeChien.CANICHE, "blanc,noir"
, 14.0
}
,
{
"Medor"
, "Medor"
, Sexe.MALE, RaceDeChien.ROTTWEILER, "gris"
, 32.0
}
,
{
"King"
, "Kiking"
, Sexe.MALE, RaceDeChien.DANOIS, "noir"
, 46.5
}
,
{
"Chocolat"
, "Chocolat mon amour"
, Sexe.MALE, RaceDeChien.GOLDEN, "marron"
, 31.0
}
,
{
"Vanille"
, "Vanille mon amour"
, Sexe.FEMALE, RaceDeChien.GOLDEN, "blanc"
, 37.0
}
}
;
final
String[] entetes =
{
"Nom"
, "Nom complet"
, "Sexe"
, "Race"
, "Couleurs"
, "Poids"
}
;
final
JTable tableau =
new
JTable
(
donnees, entetes);
getContentPane
(
).add
(
tableau.getTableHeader
(
), NORTH);
getContentPane
(
).add
(
tableau, CENTER);
pack
(
);
}
}
On utilise le constructeur « JTable(Object[][] data, Object[] title) » pour gérer les données et les entêtes. Pour ajouter le « JTable » dans un « JPanel », il faut ajouter séparément le header du tableau et le tableau en lui-même.
Vous pouvez ajouter d'autres chiens dans la liste. Dans ce cas, je vous conseille de recopier les chiens déjà présents dans le fichier « chiens.csv ».
Relancez le programme pour voir le tableau.
Demandez à Eclipse de faire les imports statiques des sexes et des races de chien à l'aide du raccourci [Ctrl] + [Maj] + [M] pour rendre le code plus lisible. Profitez-en pour formater le code.
Avec très peu de code, vous avez donc un tableau fonctionnel. Néanmoins, cette première implémentation souffre de plusieurs défauts :
- on ne peut pas afficher plus de lignes qu'il n'y a d'espace disponible sur la fenêtre (réduisez la taille de la fenêtre avec la souris pour le constater et/ou ajoutez des chiens dans le tableau) ;
- les données sont statiques et immuables ;
- on ne peut pas gérer la façon dont seront affichées les données ;
- aucune distinction entre les données et la vue ;
- les colonnes « Sexe » et « Race » ne sont pas très esthétiques.
Le premier est très trivial ; la bonne façon d'ajouter un « JTable » dans un « JPanel » est de passer par un « JScrollPane » qui permettra d'afficher plus de lignes que la fenêtre ne le permet, à l'aide d'un ascenseur si besoin.
Modifiez le code en vous inspirant de la ligne suivante :
...
//getContentPane().add(tableau, CENTER);
getContentPane
(
).add
(
new
JScrollPane
(
tableau), CENTER);
...
}
Vous devriez obtenir la même fenêtre, mais avec un ascenseur.
III-C. Table model▲
Durée estimée : 15 minutes.
Une chose indispensable, lorsqu'on utilise des « JTable », est d'utiliser un modèle de tableau pour stocker les données. Pour cela il faut créer une classe étendant « TableModel ». En pratique, on implémente rarement directement « TableModel », mais on hérite plutôt de « AbstractTableModel » et l'on redéfinit les méthodes nécessaires. Pour commencer, voici les méthodes qu'il faudra redéfinir pour notre modèle de données statiques :
- int getRowCount() : doit retourner le nombre de lignes du tableau ;
- int getColumnCount() : doit retourner le nombre de colonnes du tableau ;
- Object getValueAt(int rowIndex, int columnIndex) : doit retourner la valeur du tableau à la colonne et la ligne spécifiées ;
- String getColumnName(int columnIndex) : doit retourner l'entête de la colonne spécifiée.
Créez un modèle en reprenant les données dans un tableau à deux dimensions et en vous inspirant du code suivant :
public
class
ModeleStatique extends
AbstractTableModel {
private
final
String[] entetes;
private
final
Object[][] donnees;
public
ModeleStatique
(
) {
donnees =
new
Object[][] {
{
"Pluto"
,"Pluto le chien Disney"
,MALE, GOLDEN,"jaune"
, 24.0
}
,
{
"Lassie"
,"Lassie des alpes"
, FEMALE, BERGER_ALLEMAND,"blanc,noir"
, 32.3
}
,
{
"Volt"
,"Volt le super chien"
,MALE, CANICHE,"blanc,noir"
, 14.0
}
,
...
{
"Vanille"
,"Vanille mon amour"
,FEMALE, GOLDEN,"blanc"
,37.0
}
}
;
entetes =
new
String[] {
"Nom"
,"Nom complet"
,"Sexe"
,"Race"
,"Couleurs"
,"Poids"
}
;
;
}
@Override
public
String getColumnName
(
int
columnIndex) {
return
entetes[columnIndex];
}
@Override
public
int
getRowCount
(
) {
// TODO
}
@Override
public
int
getColumnCount
(
) {
// TODO
}
@Override
public
Object getValueAt
(
int
rowIndex, int
columnIndex) {
// TODO
}
}
Remplacez-les « TODO » par du code intelligent…
Bien entendu il faut modifier la « JTable » pour prendre en compte ce modèle.
public
class
ChienJFrame2 extends
JFrame {
public
ChienJFrame2
(
) {
super
(
);
setTitle
(
"Liste des chiens (v2)"
);
setPreferredSize
(
new
Dimension
(
500
, 400
));
setDefaultCloseOperation
(
EXIT_ON_CLOSE);
final
JTable tableau =
new
JTable
(
new
ModeleStatique
(
));
getContentPane
(
).add
(
tableau.getTableHeader
(
), NORTH);
getContentPane
(
).add
(
new
JScrollPane
(
tableau), CENTER);
pack
(
);
}
}
Ici j'ai créé la classe « ChienJFrame2 » pour m'y retrouver plus facilement entre les différentes versions. Il faut penser à utiliser les nouvelles versions dans le launcher.
On a donc créé une classe héritant de « AbstractTableModel » et redéfinissant les méthodes indispensables. Les données sont toujours stockées de la même manière, mais cette solution est plus souple et beaucoup plus propre. Si l'on regarde la « JFrame », on peut voir qu'il n'y a plus aucune donnée dans cette classe, ce qui est donc beaucoup plus propre en termes de découplage. En outre, on est maintenant maître de nos données et de la façon dont elles sont stockées. Cela dit, il n'y a aucune différence visible à l'affichage.
En vrai, il est extrêmement rare de voir des données sous la forme de tableaux à deux dimensions. Java étant un langage orienté objet, on manipule des objets. On va maintenant pouvoir utiliser la classe « Chien » directement.
Pour que ce soit plus « lisible », vous devez ajouter un constructeur dans « SimpleChien » :
public
class
SimpleChien implements
Chien {
...
public
SimpleChien
(
String nom, String nomComplet, Sexe sexe, RaceDeChien race, List<
String>
couleurs, Double poids) {
this
(
nom, race, sexe);
this
.nomComplet =
nomComplet;
this
.couleurs =
couleurs;
this
.poids =
poids;
}
public
SimpleChien
(
String nom, String nomComplet, Sexe sexe, RaceDeChien race, String[] couleurs, Double poids) {
this
(
nom, nomComplet, sexe, race, tabToList
(
couleurs), poids);
}
private
static
List<
String>
tabToList
(
String[] couleurs) {
List<
String>
couleurList =
new
ArrayList<
String>(
);
for
(
String couleur : couleurs) {
couleurList.add
(
couleur);
}
return
couleurList;
}
...
}
Modifiez le modèle statique en vous inspirant du code suivant :
public
class
ModeleStatique2 extends
AbstractTableModel {
...
private
final
String[] entetes;
//private final Object[][] donnees;
private
final
SimpleChien[] chiens;
public
ModeleStatique2
(
) {
super
(
);
chiens =
new
SimpleChien[] {
new
SimpleChien
(
"Pluto"
,"Pluto le chien Disney"
,MALE, GOLDEN, new
String[] {
"jaune"
}
, 24.0
),
new
SimpleChien
(
"Lassie"
,"Lassie des alpes"
, FEMALE, BERGER_ALLEMAND, new
String[] {
"blanc"
,"noir"
}
, 32.3
),
new
SimpleChien
(
"Volt"
,"Volt le super chien"
, MALE, CANICHE, new
String[] {
"blanc"
,"noir"
}
, 14.0
),
new
SimpleChien
(
"Medor"
,"Medor"
, MALE, ROTTWEILER, new
String[] {
"gris"
}
, 32.0
),
new
SimpleChien
(
"King"
,"Kiking"
,MALE, DANOIS, new
String[] {
"noir"
}
, 46.5
),
...
new
SimpleChien
(
"Chocolat"
,"Chocolat mon amour"
,MALE, GOLDEN, new
String[] {
",marron"
}
, 31.0
),
new
SimpleChien
(
"Vanille"
,"Vanille mon amour"
, FEMALE, GOLDEN, new
String[] {
"blanc"
}
, 37.0
)
}
;
entetes =
new
String[] {
"Nom"
,"Nom complet"
,"Sexe"
,"Race"
,"Couleurs"
,"Poids"
}
;
;
}
...
}
Adaptez les autres méthodes de la classe pour que tout compile et renvoie les bons résultats.
Le code devient intéressant. C'est là qu'on commence à comprendre l'utilité d'utiliser un modèle et non pas directement le constructeur de « JTable ». Si maintenant par exemple, on veut inverser deux colonnes et mettre « nom » après « race », il suffit d'inverser les deux colonnes dans la liste des colonnes et d'inverser les deux « return » de la méthode « getValueAt() », alors que ceci aurait été beaucoup plus difficile et contraignant avec un tableau d'objets.
III-D. Un peu de dynamisme▲
Durée estimée : 10 minutes.
Créez un nouveau « table model », nommé « ModeleDynamique », inspiré de « ModeleStatique », mais utilisant une liste à la place d'un tableau. Vous pouvez vous inspirer du code suivant, mais n'oubliez pas de compléter les méthodes manquantes.
public
class
ModeleDynamique extends
AbstractTableModel {
private
final
String[] entetes;
// private final SimpleChien[] chiens;
private
final
List<
SimpleChien>
chiens;
public
ModeleDynamique
(
) {
super
(
);
chiens =
new
ArrayList<
SimpleChien>(
);
chiens.add
(
new
SimpleChien
(
"Pluto"
,"Pluto le chien Disney"
,MALE, GOLDEN, new
String[] {
"jaune"
}
, 24.0
));
chiens.add
(
new
SimpleChien
(
"Lassie"
,"Lassie des alpes"
,FEMALE, BERGER_ALLEMAND, new
String[] {
"blanc"
,"noir"
}
, 32.3
));
chiens.add
(
new
SimpleChien
(
"Volt"
,"Volt le super chien"
,MALE, CANICHE, new
String[] {
"blanc"
,"noir"
}
, 14.0
));
chiens.add
(
new
SimpleChien
(
"Medor"
,"Medor"
,MALE, ROTTWEILER, new
String[] {
"gris"
}
, 32.0
));
chiens.add
(
new
SimpleChien
(
"King"
,"Kiking"
,MALE, DANOIS, new
String[] {
"noir"
}
, 46.5
));
...
chiens.add
(
new
SimpleChien
(
"Chocolat"
,"Chocolat mon amour"
, MALE, GOLDEN, new
String[] {
"marron"
}
, 31.0
));
chiens.add
(
new
SimpleChien
(
"Vanille"
,"Vanille mon amour"
, FEMALE, GOLDEN, new
String[] {
"blanc"
}
, 37.0
));
entetes =
new
String[] {
"Nom"
,"Nom complet"
,"Sexe"
,"Race"
,"Couleurs"
,"Poids"
}
;
}
...
}
N'oubliez pas de modifier la fenêtre pour qu'elle utilise le modèle dynamique :
public
class
ChienJFrame4 extends
JFrame {
...
public
ChienJFrame4
(
) {
...
//final JTable tableau = new JTable(new ModeleStatique2());
final
JTable tableau =
new
JTable
(
new
ModeleDynamique
(
));
...
}
Visuellement on ne voit toujours pas de différence.
III-E. Les vraies données▲
Durée estimée : 10 minutes.
Maintenant que vous avez compris le principe, vous avez très certainement envie de jouer avec des vraies données et plus particulièrement avec celles contenues dans le fichier CSV. Pour cela, vous allez utiliser la classe de service « ChienService » qui se présente comme un « singleton ».
Vous allez ajouter l'utilisation d'une des implémentations du DAO dans ce service, par exemple « EngineCsvChienDao » qui est la dernière que vous avez programmée dans le TP précédent. Pour cela, inspirez-vous du code suivant :
public
class
ChienService {
...
private
CsvChienDao csvChienDao;
...
private
ChienService
(
) {
LOGGER.debug
(
"Constructeur"
);
csvChienDao =
new
EngineCsvChienDao
(
);
}
...
public
List<
Chien>
findAllChiens
(
final
String fileName) {
final
File file =
new
File
(
fileName);
csvChienDao.init
(
file);
return
csvChienDao.findAllChiens
(
);
}
...
}
Vous constatez que la méthode « findAllChiens(..) » relance l'initialisation du DAO à chaque fois. Dans le cadre de ce TP, on dira que ce n'est pas grave. Vous pouvez toutefois trouver une manière plus élégante de le faire.
Vous devez également adapter le modèle pour qu'il prenne en charge ce service, en vous inspirant du code suivant :
public
class
ModeleDynamique2 extends
AbstractTableModel {
...
private
final
String[] entetes;
private
List<
Chien>
chiens;
final
private
static
String CHIENS_FILE_NAME =
"src/main/resources/chiens.csv"
;
private
ChienService chienService =
ChienService.getInstance
(
);
...
public
ModeleDynamique2
(
) {
super
(
);
chiens =
chienService.findAllChiens
(
CHIENS_FILE_NAME);
entetes =
new
String[] {
"Nom"
,"Nom complet"
,"Sexe"
,"Race"
,"Couleurs"
,"Poids"
}
;
}
...
}
Relancez le programme pour voir les données du fichier.
III-F. Renderer▲
Durée estimée : 10 minutes.
Vous allez maintenant passer à la personnalisation de l'affichage des cellules pour que ce soit un peu plus beau. Commencez par spécifier (dans le modèle) à quelle classe correspond chacune colonne (on ne peut configurer des « renderers » que par colonne). À la suite de quoi, vous configurerez les « renderers » pour chaque classe de colonne au niveau de la « JTable ».
La première chose à faire est de redéfinir la méthode getColumnClass() dans le modèle, en vous inspirant du code suivant :
public
class
ModeleDynamique2 extends
AbstractTableModel {
...
@Override
public
Class<
?>
getColumnClass
(
int
columnIndex) {
switch
(
columnIndex) {
case
0
:
case
1
:
return
String.class
;
case
2
:
return
Sexe.class
;
case
3
:
return
RaceDeChien.class
;
default
:
return
Object.class
;
}
}
...
}
Ne relancez pas tout de suite l'IHM, faute de quoi vous aurez une exception.
Un « renderer » est une classe implémentant l'interface « TableCellRenderer », cette dernière ne contenant qu'une seule méthode retournant un composant Swing. En pratique, on hérite généralement de « DefaultCellRenderer » qui représente un « JLabel » comme « renderer ».
Il faut éviter dans la mesure du possible de renvoyer un nouvel objet dans un « renderer » si on a beaucoup d'éléments dans la « JTable ». Cela voudrait dire qu'il faudrait créer un objet pour chaque ligne, et à chaque fois qu'on redessine la « JTable », ce qui peut dégrader très fortement les performances.
Créez un « renderer » pour afficher le label des enum de « Sexe » à la place du nom de l'enum en vous inspirant du code suivant :
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);
final
Sexe sexe =
(
Sexe) value;
if
(
sexe.isMale
(
)) {
setText
(
"M"
);
setBackground
(
Color.BLUE);
}
else
{
setText
(
"F"
);
setBackground
(
Color.PINK);
}
return
this
;
}
...
}
Puis spécifiez que vous souhaitez utiliser ce « renderer » dans le tableau.
public
class
ChienJFrame5 extends
JFrame {
public
ChienJFrame5
(
) {
...
final
JTable tableau =
new
JTable
(
new
ModeleDynamique2
(
));
...
tableau.setDefaultRenderer
(
Sexe.class
, new
SexeCellRenderer
(
));
pack
(
);
}
}
Créez un « renderer » sur le même principe pour la race.
Quand vous relancez le programme, vous devriez constater que la colonne du sexe est maintenant en couleurs et que la race est devenue lisible.
La colonne des couleurs n'est toujours pas très jolie. Créez donc un « renderer » pour elle aussi. Par contre, vous allez directement indiquer au tableau sur quel numéro de colonne l'appliquer en vous inspirant du code suivant.
public
class
ChienJFrame5 extends
JFrame {
public
ChienJFrame5
(
) {
...
final
JTable tableau =
new
JTable
(
new
ModeleDynamique2
(
));
...
tableau.getColumnModel
(
).getColumn
(
4
).setCellRenderer
(
new
ListeCouleursCellRenderer
(
));
pack
(
);
}
}
III-G. Des tris▲
Durée estimée : 10 minutes.
Vous allez maintenant activer les capacités de tri des colonnes du tableau. Cela est fait au moyen d'un objet « RowSorter ».
Activez-le « sorter » par défaut du tableau en vous inspirant du code suivant :
public
class
ChienJFrame5 extends
JFrame {
public
ChienJFrame5
(
) {
...
final
JTable tableau =
new
JTable
(
new
ModeleDynamique2
(
));
...
tableau.setAutoCreateRowSorter
(
true
);
}
Dans la plupart des cas, c'est suffisant, mais on peut tout de même personnaliser le « sorter ». On peut évidemment créer notre propre « RowSorter », mais il est plus pratique d'utiliser la classe « TableRowSorter » et de la personnaliser pour effectuer des changements plutôt que de redéfinir une nouvelle implémentation ce qui peut s'avérer assez lourde.
public
class
ChienJFrame5 extends
JFrame {
public
ChienJFrame5
(
) {
...
final
JTable tableau =
new
JTable
(
new
ModeleDynamique2
(
));
...
tableau.setAutoCreateRowSorter
(
true
);
final
TableRowSorter<
TableModel>
sorter =
new
TableRowSorter<
TableModel>(
tableau.getModel
(
));
tableau.setRowSorter
(
sorter);
}
Une première chose que vous pouvez faire est de spécifier une colonne comme « non triable » via la méthode « setSortable » :
public
class
ChienJFrame5 extends
JFrame {
public
ChienJFrame5
(
) {
...
sorter.setSortable
(
0
, false
); // colonne "Nom" pas triable
}
Relancez l'application et essayez de trier la colonne « Nom ». Vous devriez constater que c'est désormais impossible, alors qu'on peut le faire sur les autres colonnes.
Vous pouvez également créer des comparateurs spécifiques, par exemple lorsque vous travaillez avec des objets complexes. Comme ce n'est pas le cas dans l'application, amusez-vous à trier les noms complets, non pas par ordre alphabétique, mais par le nombre de caractères qui les composent.
public
class
StringSizeComparator implements
Comparator<
String>
{
@Override
public
int
compare
(
String s1, String s2) {
final
Integer size1 =
s1.length
(
);
final
Integer size2 =
s2.length
(
);
return
size1.compareTo
(
size2);
}
}
Comme vous l'avez sûrement compris, la méthode de comparaison renvoie une valeur positive ou négative en fonction du paramètre le plus grand. Dans ce cas précis, puisque vous travaillez déjà sur des entiers (des tailles), vous auriez donc pu utiliser directement un code ressemblant au suivant :
public
class
StringSizeComparator implements
Comparator<
String>
{
@Override
public
int
compare
(
final
String s1, final
String s2) {
return
s1.length
(
) -
s2.length
(
);
}
}
Puis utilisez-le en vous inspirant du code suivant :
public
class
ChienJFrame5 extends
JFrame {
public
ChienJFrame5
(
) {
...
sorter.setSortable
(
0
, false
); // colonne "Nom" pas triable
sorter.setComparator
(
1
, new
StringSizeComparator
(
)); // Tri sur le nb de lettres
}
Si vous faites attention, vous constaterez que la colonne du poids est triée par ordre alphabétique et non par ordre numérique. Vous devez donc y remédier.
III-H. Des petits boutons▲
Durée estimée : 10 minutes.
Vous allez maintenant donner à l'utilisateur la possibilité de modifier le contenu du tableau. Dans ce TD, vous vous contenterez de supprimer ou d'ajouter des lignes toutes prêtes.
Ajoutez des boutons dans l'IHM en vous inspirant du code suivant :
public
class
ChienJFrame5 extends
JFrame {
private
static
final
Logger LOGGER =
Logger.getLogger
(
ChienJFrame5.class
);
...
public
ChienJFrame5
(
) {
...
final
JTable tableau =
new
JTable
(
new
ModeleDynamique2
(
));
//getContentPane().add(tableau.getTableHeader(), NORTH);
getContentPane
(
).add
(
new
JScrollPane
(
tableau), CENTER);
final
JPanel boutons =
new
JPanel
(
);
boutons.add
(
new
JButton
(
new
AjouterLigneAction
(
)));
getContentPane
(
).add
(
boutons, SOUTH);
...
pack
(
);
}
...
private
class
AjouterLigneAction extends
AbstractAction {
private
AjouterLigneAction
(
) {
super
(
"Ajouter"
);
}
@Override
public
void
actionPerformed
(
ActionEvent e) {
LOGGER.debug
(
"Click sur le bouton ajouter"
);
}
}
}
}
Relancez le programme. Quand vous cliquez sur le bouton, ça doit écrire dans la console.
Modifiez le code de la fenêtre pour que le modèle se retrouve en variable de classe.
Ajoutez une méthode dans le modèle, pour pouvoir ajouter un nouveau chien, en vous inspirant du code suivant :
public
class
ModeleDynamique2 extends
AbstractTableModel {
...
public
void
ajouterChien
(
final
Chien chien) {
LOGGER.debug
(
"ajouterChien"
);
...
}
}
}
Puis demandez au bouton d'appeler cette méthode. Pour simplifier ce TP, on va dire qu'on ajoute toujours un labrador blanc nommé « Idefix ». Dans une vraie application, on aurait plutôt ouvert une fenêtre avec un formulaire.
public
class
ChienJFrame6 extends
JFrame {
...
private
ModeleDynamique2 modele;
...
private
class
AjouterLigneAction extends
AbstractAction {
...
@Override
public
void
actionPerformed
(
ActionEvent e) {
LOGGER.debug
(
"Click sur le bouton ajouter"
);
final
Chien idefix =
new
SimpleChien
(
"Idefix"
, "Idefix"
, MALE, LABRADOR, new
String[]{
"blanc"
}
, 25.0
);
modele.ajouterChien
(
idefix);
}
}
}
}
Si vous relancez le programme, vous verrez que ça ne fait rien. Il faut revenir au modèle pour lui dire quoi faire.
public
class
ModeleDynamique2 extends
AbstractTableModel {
...
public
void
ajouterChien
(
final
Chien chien) {
LOGGER.debug
(
"ajouterChien"
);
// Ajout dans la liste, a la fin.
chiens.add
(
chien);
final
int
position =
chiens.size
(
) -
1
;
fireTableRowsInserted
(
position, position);
}
}
}
Cherchez ce que fait la méthode « fireTableRowsInserted » dans la Javadoc.
Après un clic sur le bouton, la nouvelle ligne doit apparaître en bas du tableau. En fonction de la taille donnée à la fenêtre, il faudra peut-être scroller pour voir Idefix.
Pour supprimer des lignes, il faut employer le même principe.
public
class
ModeleDynamique2 extends
AbstractTableModel {
...
public
void
supprimerChien
(
final
int
rowIndex) {
LOGGER.debug
(
"supprimerChien"
);
chiens.remove
(
rowIndex);
fireTableRowsDeleted
(
rowIndex, rowIndex);
}
}
}
public
class
ChienJFrame6 extends
JFrame {
...
private
JTable tableau;
private
ModeleDynamique2 modele;
...
private
class
SupprimerLigneAction extends
AbstractAction {
private
SupprimerLigneAction
(
) {
super
(
"Supprimer"
);
}
@Override
public
void
actionPerformed
(
ActionEvent e) {
final
int
[] selection =
tableau.getSelectedRows
(
);
for
(
int
i =
selection.length -
1
; i >=
0
; i--
) {
modele.supprimerChien
(
selection[i]);
}
}
}
}
}
Devinez pourquoi on décrémente l'index dans la boucle, au lieu de parcourir les éléments comme ils viennent.
Vérifiez que vous pouvez supprimer une ou plusieurs lignes du tableau et que les chiens correspondants disparaissent bien.
Ici, on se contente d'ajouter ou de supprimer les chiens en mémoire. Quand on ferme le programme, les modifications sont perdues. Dans une vraie application, on aurait donc aussi un menu « Sauver » pour enregistrer les modifications en base ou dans le fichier CSV. Dans le cadre de ce TP, vous pouvez en rester là pour simplifier, mais rien ne vous empêche de coder la fonction de sauvegarde à la maison.
L'action pour ajouter un chien ne fait rien de bien spécial et est plutôt triviale. Par contre, il y a quelques petites subtilités pour la suppression. Tout d'abord, il faut savoir qu'une « JTable » peut fonctionner selon plusieurs modes de sélection :
- SINGLE_SELECTION : permet de sélectionner une seule ligne ;
- SINGLE_INTERVAL_SELECTION : permet de sélectionner un intervalle de ligne ;
- MULTIPLE_INTERVAL_SELECTION : permet de sélectionner de multiples intervalles. C'est la valeur par défaut.
Il faut donc comprendre que le tableau de lignes renvoyé par la méthode « getSelectedRows » peut retourner plusieurs intervalles. Les résultats sont retournés dans l'ordre ascendant. Il nous faut donc les supprimer depuis la fin, sinon on fausserait les résultats.
Faites (à la maison) quelques recherches sur les « table model », car vous aurez au moins une question sur le sujet durant l'évaluation de fin de module.
Le code du programme à ce stade est disponible en ligne : tp-chien-ihm-02.zipFichier tp-chien-ihm-02.zip
IV. Graphes▲
Vous arrivez (enfin) à la partie sympa de ce TP. Vous allez maintenant dessiner des graphes pour avoir une représentation visuelle plus parlante de la liste des chiens.
Comme ça commence à être du « connu », la suite va aller plus vite.
IV-A. Maven▲
Durée estimée : 5 minutes.
Sans surprise, puisque vous allez utiliser la bibliothèque JFreeChart, vous ajouter une dépendance dans le fichier « pom.xml » :
<project ...>
...
<properties>
<!-- JFreeChart -->
<jfreechart.version>
1.0.14</jfreechart.version>
</properties>
<dependencies>
<!-- JFreeChart -->
<dependency>
<groupId>
org.jfree</groupId>
<artifactId>
jfreechart</artifactId>
<version>
${jfreechart.version}</version>
</dependency>
</dependencies>
...
}
Lancez une installation Maven, puis faites un « refresh » dans Eclipse comme vous l'avez fait précédemment. Vérifiez que la bibliothèque JFreeChart est bien arrivée dans la liste de vos dépendances.
IV-B. Bouton et popup▲
Durée estimée : 5 minutes.
Ajoutez un nouveau bouton pour afficher un graphe en camembert en vous inspirant du code suivant :
public
class
ChienJFrame7 extends
JFrame {
...
private
class
CamembertMaleFemaleAction extends
AbstractAction {
private
CamembertMaleFemaleAction
(
) {
super
(
"Camembert M/F"
);
}
@Override
public
void
actionPerformed
(
ActionEvent e) {
LOGGER.debug
(
"cliv clic"
);
}
}
}
}
Pour l'instant, le bouton ne fait rien d'autre que de s'afficher et d'écrire dans la console, ce qui est un bon début ;-)
Maintenant, en plus d'écrire dans la console, vous allez ouvrir une fenêtre :
public
class
ChienJFrame7 extends
JFrame {
...
private
JDialog ratioMaleFemaleJdialog;
...
private
class
CamembertMaleFemaleAction extends
AbstractAction {
...
@Override
public
void
actionPerformed
(
ActionEvent e) {
LOGGER.debug
(
"cliv clic"
);
ratioMaleFemaleJdialog =
new
JDialog
(
);
ratioMaleFemaleJdialog.setTitle
(
"Ratio H/F"
);
ratioMaleFemaleJdialog.pack
(
);
ratioMaleFemaleJdialog.setVisible
(
true
);
}
}
}
}
Relancez le programme et vérifiez qu'une fenêtre s'ouvre bien quand vous cliquez sur le bouton, même si elle est toute petite (vide).
IV-C. Graphes▲
Durée estimée : 10 minutes.
Maintenant que tout est prêt, vous pouvez passer à l'affichage du graphe. Cela se fait en deux temps : vous devez calculer les valeurs à afficher, puis vous devez indiquer comment les afficher.
public
class
ChienJFrame7 extends
JFrame {
...
private
class
CamembertMaleFemaleAction extends
AbstractAction {
...
@Override
public
void
actionPerformed
(
ActionEvent e) {
...
final
List<
Chien>
chiens =
modele.getChiens
(
);
int
nbMale =
0
;
for
(
Chien chien : chiens) {
if
(
chien.getSexe
(
) ==
Sexe.MALE) {
nbMale++
;
}
}
final
int
nbFemele =
chiens.size
(
) -
nbMale;
}
}
}
}
Ce calcul ne devrait pas vous poser de difficulté. L'affichage va nécessiter un peu plus d'attention de votre part.
public
class
ChienJFrame7 extends
JFrame {
...
private
class
CamembertMaleFemaleAction extends
AbstractAction {
...
@Override
public
void
actionPerformed
(
ActionEvent e) {
LOGGER.debug
(
"cliv clic"
);
// Calcul
final
List<
Chien>
chiens =
modele.getChiens
(
);
int
nbMale =
0
;
for
(
Chien chien : chiens) {
if
(
chien.getSexe
(
) ==
Sexe.MALE) {
nbMale++
;
}
}
final
int
nbFemele =
chiens.size
(
) -
nbMale;
ratioMaleFemaleJdialog =
new
JDialog
(
);
ratioMaleFemaleJdialog.setTitle
(
"Ratio H/F"
);
// Affichage
final
DefaultPieDataset pieDataset =
new
DefaultPieDataset
(
);
pieDataset.setValue
(
"Female"
, nbFemele);
pieDataset.setValue
(
"Male"
, nbMale);
final
JFreeChart pieChart =
ChartFactory.createPieChart
(
"Ratio M/F"
, pieDataset, true
, false
, false
);
final
ChartPanel cPanel =
new
ChartPanel
(
pieChart);
ratioMaleFemaleJdialog.getContentPane
(
).add
(
cPanel, CENTER);
ratioMaleFemaleJdialog.pack
(
);
ratioMaleFemaleJdialog.setVisible
(
true
);
}
}
}
}
Relancez de nouveau le programme et testez le résultat.
Ce graphe en camembert était vraiment facile à programmer. Vous allez maintenant dessiner un graphe de répartition des chiens par poids, en les classant par tranches de cing kg :
- 0-5kg ;
- 6-10 kg ;
- 11-15 kg ;
- 16-20 kg;
- 21-25 kg ;
- 26-30 kg ;
- plus de 31 kg.
Pour le début, c'est la même chose. Il faut ajouter un bouton et récupérer la liste des chiens. Vous devriez être en mesure de réaliser cette étape sans aide.
Parcourez la liste des chiens pour calculer les tranches. Vous pouvez regarder une proposition de solution en annexe si vous ne trouvez pas.
Dans la suite, nous dirons que les tranches ont été mises dans une « Map », dont les clés seraient « 0-5 », « 6-10 », etc. :
Map<
String, Integer>
map =
new
HashMap<
String, Integer>(
);
Puis vous pouvez dessiner le graphe.
public
class
ChienJFrame7 extends
JFrame {
...
@Override
public
void
actionPerformed
(
ActionEvent e) {
LOGGER.debug
(
"cliv clic"
);
Map<
String, Integer>
map =
new
HashMap<
String, Integer>(
);
...
final
List<
Chien>
chiens =
modele.getChiens
(
);
for
(
Chien chien : chiens) {
...
}
poidsJdialog =
new
JDialog
(
);
poidsJdialog.setTitle
(
"Poids"
);
final
DefaultCategoryDataset dataset =
new
DefaultCategoryDataset
(
);
for
(
String tranche : map.keySet
(
)) {
final
Integer nb =
map.get
(
tranche);
dataset.addValue
(
nb, "Poids"
, tranche);
}
final
JFreeChart barChart =
ChartFactory.createBarChart
(
"Poids"
,"Tranches"
,"Nombre"
, /**/
dataset, PlotOrientation.VERTICAL, true
, true
, false
);
final
ChartPanel cPanel =
new
ChartPanel
(
barChart);
poidsJdialog.getContentPane
(
).add
(
cPanel, CENTER);
poidsJdialog.pack
(
);
poidsJdialog.setVisible
(
true
);
}
}
}
}
Relancez le programme et vérifiez qu'il n'y a bien aucun chien dans la tranche « 26-30 ».
Calculez et affichez les poids par sexe et par tranche en même temps, ce qui devrait ressembler à la capture suivante.
Si, comme sur les captures précédentes, les tranches ne sont pas organisées dans l'ordre logique, c'est que vous devez améliorer votre algorithme. Ça devrait normalement ressembler à la capture suivante.
Le code du programme à ce stade est disponible en ligne : tp-chien-ihm-03.zipFichier tp-chien-ihm-03.zip
V. Conclusion▲
Ce TD est relativement complet, bien qu'on puisse aller encore plus loin. Il vous fait découvrir de nombreuses technologies, qui vous seront utiles au quotidien et dont vous devez maîtriser les grands principes : data model, design patterns, etc.
Vos retours nous aident à améliorer nos publications. N'hésitez donc pas à commenter cet article sur le forum : 3 commentaires
Ce TP est utilisé par de nombreux étudiants. N'hésitez donc pas à m'envoyer directement des idées d'amélioration, des propositions d'exercices supplémentaires, des pistes intéressantes, etc.
VI. Remerciements▲
Je tiens à remercier, en tant qu'auteur de ce TP, 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 à Mickael BARON (keulkeul), Yann D'Isanto (le Y@m's), et zoom61.
VII. Annexes▲
VII-A. Liens▲
Maven : http://maven.apache.org/
Eclipse : http://eclipse.org/
JFreeChart : http://www.jfree.org/jfreechart ou http://sourceforge.net/projects/jfreechart/ si le premier lien ne marche pas.
VII-B. Pour aller plus loin▲
TP « tp-chien-dao » que vous devez lire en premier :
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/tp-coder-et-tester-dao/
Article « Importer un projet Maven dans Eclipse en 5 minutes » :
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/importer-projet-maven-dans-eclipse-5-min/
Article « Utiliser Maven 2 » :
https://matthieu-lux.developpez.com/tutoriels/java/maven/
Article « Afficher un tableau avec un Table Model Swing en 5 minutes » :
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/afficher-tableau-avec-tablemodel-5-min/
Article « JTables - Un autre regard » de Nicolas Zozol :
https://nicolas-zozol.developpez.com/tutoriel/java/jtable
Article « Création interface graphique avec Swing : les tableaux (JTable) » de Baptiste Wicht :
https://baptiste-wicht.developpez.com/tutoriels/java/swing/jtable
Article « Afficher un graphe jfreechart en 5 minutes » :
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/afficher-graphe-jfreechart-5-min/
Article « Threads et performance avec Swing » :
https://gfx.developpez.com/tutoriel/java/swing/swing-threading/
Le toolkit Swing permet aux développeurs Java de réaliser des applications graphiques très complexes. Sa complexité rend malheureusement aisée la réalisation d'interfaces présentant de piètres performances.
Retrouvez ma page et mes autres articles sur Developpez.com à l'adresse
https://thierry-leriche-dessirier.developpez.com/#page_articlesTutoriels
Ajoutez-moi à vos contacts à l'aide du QR Code suivant :
Suivez-moi sur Twitter : @thierryleriche@thierryleriche
VII-C. Solution possible▲
Voici quelques-uns des fichiers que j'ai utilisés pour rédiger ce TP. Vous trouverez l'intégralité des fichiers et leurs versions successives, dans le Zip. Notez bien qu'il y a plusieurs solutions possibles. Celle que je propose est l'une des plus simples.
package
com.icauda.tp.chien;
import
javax.swing.JFrame;
import
org.apache.log4j.Logger;
import
com.icauda.tp.chien.ihm.ChienJFrame7;
public
class
LauncherIHM {
private
static
final
Logger LOGGER =
Logger.getLogger
(
LauncherIHM.class
);
public
static
void
main
(
String[] args) {
LOGGER.debug
(
"TP des chiens : DEBUT"
);
// final ChienJFrame1 f = new ChienJFrame1();
// final ChienJFrame2 f = new ChienJFrame2();
// final JFrame f = new ChienJFrame3();
// final JFrame f = new ChienJFrame4();
// final JFrame f = new ChienJFrame5();
//final JFrame f = new ChienJFrame6();
final
JFrame f =
new
ChienJFrame7
(
);
f.setVisible
(
true
);
LOGGER.debug
(
"TP des chiens : FIN"
);
}
}
package
com.icauda.tp.chien.ihm;
import
static
com.icauda.tp.chien.domain.RaceDeChien.BERGER_ALLEMAND;
import
static
com.icauda.tp.chien.domain.RaceDeChien.CANICHE;
import
static
com.icauda.tp.chien.domain.RaceDeChien.DANOIS;
import
static
com.icauda.tp.chien.domain.RaceDeChien.GOLDEN;
import
static
com.icauda.tp.chien.domain.RaceDeChien.ROTTWEILER;
import
static
com.icauda.tp.chien.domain.Sexe.FEMALE;
import
static
com.icauda.tp.chien.domain.Sexe.MALE;
import
javax.swing.table.AbstractTableModel;
import
com.icauda.tp.chien.domain.Chien;
import
com.icauda.tp.chien.domain.SimpleChien;
public
class
ModeleStatique2 extends
AbstractTableModel {
private
static
final
long
serialVersionUID =
2253643022432829283
L;
private
final
String[] entetes;
private
final
SimpleChien[] chiens;
public
ModeleStatique2
(
) {
super
(
);
chiens =
new
SimpleChien[] {
/**/
new
SimpleChien
(
"Pluto"
,"Pluto le chien Disney"
,MALE, GOLDEN, new
String[] {
"jaune"
}
, 24.0
), /**/
new
SimpleChien
(
"Lassie"
,"Lassie des alpes"
, FEMALE, BERGER_ALLEMAND, new
String[] {
"blanc"
,"noir"
}
, 32.3
), /**/
new
SimpleChien
(
"Volt"
,"Volt le super chien"
,MALE, CANICHE, new
String[] {
"blanc"
,"noir"
}
, 14.0
), /**/
new
SimpleChien
(
"Medor"
,"Medor"
,MALE, ROTTWEILER, new
String[] {
"gris"
}
, 32.0
), /**/
new
SimpleChien
(
"King"
,"Kiking"
, MALE, DANOIS, new
String[] {
"noir"
}
, 46.5
), /**/
new
SimpleChien
(
"Chocolat"
,"Chocolat mon amour"
, MALE, GOLDEN, new
String[] {
"marron"
}
, 31.0
), /**/
new
SimpleChien
(
"Vanille"
,"Vanille mon amour"
, FEMALE, GOLDEN, new
String[] {
"blanc"
}
, 37.0
), /**/
new
SimpleChien
(
"Pluto"
,"Pluto le chien Disney"
, MALE, GOLDEN, new
String[] {
"jaune"
}
, 24.0
), /**/
new
SimpleChien
(
"Lassie"
,"Lassie des alpes"
, FEMALE, BERGER_ALLEMAND, new
String[] {
"blanc"
,"noir"
}
, 32.3
), /**/
new
SimpleChien
(
"Volt"
,"Volt le super chien"
, MALE, CANICHE, new
String[] {
"blanc"
,"noir"
}
, 14.0
), /**/
new
SimpleChien
(
"Medor"
,"Medor"
,MALE, ROTTWEILER, new
String[] {
"gris"
}
, 32.0
), /**/
new
SimpleChien
(
"King"
, "Kiking"
, MALE, DANOIS, new
String[] {
"noir"
}
, 46.5
), /**/
new
SimpleChien
(
"Chocolat"
, "Chocolat mon amour"
, MALE, GOLDEN, new
String[] {
"marron"
}
, 31.0
), /**/
new
SimpleChien
(
"Vanille"
,"Vanille mon amour"
,FEMALE, GOLDEN, new
String[] {
" blanc"
}
, 37.0
), /**/
new
SimpleChien
(
"Pluto"
,"Pluto le chien Disney"
,MALE, GOLDEN, new
String[] {
" jaune"
}
, 24.0
), /**/
new
SimpleChien
(
"Lassie"
,"Lassie des alpes"
,FEMALE, BERGER_ALLEMAND, new
String[] {
" blanc"
,"noir"
}
, 32.3
), /**/
new
SimpleChien
(
"Volt"
,"Volt le super chien"
,MALE, CANICHE, new
String[] {
" blanc"
,"noir"
}
, 14.0
), /**/
new
SimpleChien
(
"Medor"
,"Medor"
,MALE, ROTTWEILER, new
String[] {
" gris"
}
, 32.0
), /**/
new
SimpleChien
(
"King"
,"Kiking"
,MALE, DANOIS, new
String[] {
" noir"
}
, 46.5
), /**/
new
SimpleChien
(
"Chocolat"
,"Chocolat mon amour"
,MALE, GOLDEN, new
String[] {
" marron"
}
, 31.0
), /**/
new
SimpleChien
(
"Vanille"
,"Vanille mon amour"
,FEMALE, GOLDEN, new
String[] {
" blanc"
}
, 37.0
) /**/
}
;
entetes =
new
String[] {
" Nom"
,"Nom complet"
,"Sexe"
,"Race"
,"Couleurs"
,"Poids"
}
;
}
@Override
public
String getColumnName
(
int
columnIndex) {
return
entetes[columnIndex];
}
@Override
public
int
getRowCount
(
) {
return
chiens.length;
}
@Override
public
int
getColumnCount
(
) {
return
entetes.length;
}
@Override
public
Object getValueAt
(
int
rowIndex, int
columnIndex) {
final
Chien chien =
chiens[rowIndex];
// Ordre :" Nom","Nom complet","Sexe","Race","Couleurs","Poids"
switch
(
columnIndex) {
case
0
:
return
chien.getNom
(
);
case
1
:
return
chien.getNomComplet
(
);
case
2
:
return
chien.getSexe
(
);
case
3
:
return
chien.getRace
(
);
case
4
:
return
chien.getCouleurs
(
);
case
5
:
return
chien.getPoids
(
);
default
:
throw
new
IllegalArgumentException
(
"Le numero de colonne indique n'est pas valide."
);
}
}
}
package
com.icauda.tp.chien.ihm;
import
java.util.List;
import
javax.swing.table.AbstractTableModel;
import
org.apache.log4j.Logger;
import
com.icauda.tp.chien.domain.Chien;
import
com.icauda.tp.chien.domain.RaceDeChien;
import
com.icauda.tp.chien.domain.Sexe;
import
com.icauda.tp.chien.service.ChienService;
public
class
ModeleDynamique2 extends
AbstractTableModel {
private
static
final
long
serialVersionUID =
-
139320534128196933
L;
private
static
final
Logger LOGGER =
Logger.getLogger
(
ModeleDynamique2.class
);
private
final
String[] entetes;
private
List<
Chien>
chiens;
final
private
static
String CHIENS_FILE_NAME =
" src/main/resources/chiens.csv"
;
private
ChienService chienService =
ChienService.getInstance
(
);
public
ModeleDynamique2
(
) {
super
(
);
chiens =
chienService.findAllChiens
(
CHIENS_FILE_NAME);
entetes =
new
String[] {
" Nom"
,"Nom complet"
,"Sexe"
,"Race"
,"Couleurs"
,"Poids"
}
;
}
@Override
public
String getColumnName
(
int
columnIndex) {
return
entetes[columnIndex];
}
@Override
public
int
getRowCount
(
) {
return
chiens.size
(
);
}
@Override
public
int
getColumnCount
(
) {
return
entetes.length;
}
@Override
public
Object getValueAt
(
int
rowIndex, int
columnIndex) {
final
Chien chien =
chiens.get
(
rowIndex);
// Ordre :" Nom","Nom complet","Sexe","Race","Couleurs","Poids"
switch
(
columnIndex) {
case
0
:
return
chien.getNom
(
);
case
1
:
return
chien.getNomComplet
(
);
case
2
:
return
chien.getSexe
(
);
case
3
:
return
chien.getRace
(
);
case
4
:
return
chien.getCouleurs
(
);
case
5
:
return
chien.getPoids
(
);
default
:
throw
new
IllegalArgumentException
(
"Le numero de colonne indique n'est pas valide."
);
}
}
@Override
public
Class<
?>
getColumnClass
(
int
columnIndex) {
switch
(
columnIndex) {
case
0
:
case
1
:
return
String.class
;
case
2
:
return
Sexe.class
;
case
3
:
return
RaceDeChien.class
;
// case 4:
// return List.class;
//
// case 5:
// return Double.class;
default
:
return
Object.class
;
}
}
public
void
ajouterChien
(
final
Chien chien) {
LOGGER.debug
(
"ajouterChien"
);
chiens.add
(
chien);
final
int
position =
chiens.size
(
) -
1
;
fireTableRowsInserted
(
position, position);
}
public
void
supprimerChien
(
final
int
rowIndex) {
LOGGER.debug
(
"supprimerChien"
);
chiens.remove
(
rowIndex);
fireTableRowsDeleted
(
rowIndex, rowIndex);
}
public
List<
Chien>
getChiens
(
) {
return
chiens;
}
}
package
com.icauda.tp.chien.ihm;
import
java.awt.Component;
import
javax.swing.JTable;
import
javax.swing.table.DefaultTableCellRenderer;
import
com.icauda.tp.chien.domain.RaceDeChien;
public
class
RaceCellRenderer extends
DefaultTableCellRenderer {
private
static
final
long
serialVersionUID =
5826273340420630286
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);
final
RaceDeChien race =
(
RaceDeChien) value;
setText
(
race.getLabel
(
));
return
this
;
}
}
package
com.icauda.tp.chien.ihm;
import
java.util.Comparator;
public
class
StringSizeComparator implements
Comparator<
String>
{
@Override
public
int
compare
(
String s1, String s2) {
final
Integer size1 =
s1.length
(
);
final
Integer size2 =
s2.length
(
);
return
size1.compareTo
(
size2);
}
}
package
com.icauda.tp.chien.ihm;
import
java.awt.Component;
import
java.util.List;
import
javax.swing.JTable;
import
javax.swing.table.DefaultTableCellRenderer;
public
class
ListeCouleursCellRenderer extends
DefaultTableCellRenderer {
private
static
final
long
serialVersionUID =
-
844387322539021290
L;
@Override
@SuppressWarnings
(
value =
" unchecked"
)
public
Component getTableCellRendererComponent
(
JTable table, Object value, boolean
isSelected, boolean
hasFocus, int
row, int
column) {
super
.getTableCellRendererComponent
(
table, value, isSelected, hasFocus, row, column);
final
List<
String>
colors =
(
List<
String>
) value;
final
StringBuilder sb =
new
StringBuilder
(
);
boolean
first =
true
;
for
(
String color : colors) {
if
(!
first) {
sb.append
(
","
);
}
sb.append
(
color);
first =
false
;
}
setText
(
sb.toString
(
));
return
this
;
}
}
package
com.icauda.tp.chien.service;
import
java.io.File;
import
java.util.List;
import
org.apache.log4j.Logger;
import
com.icauda.tp.chien.dao.csv.CsvChienDao;
import
com.icauda.tp.chien.dao.csv.EngineCsvChienDao;
import
com.icauda.tp.chien.domain.Chien;
public
class
ChienService {
private
static
final
Logger LOGGER =
Logger.getLogger
(
ChienService.class
);
private
CsvChienDao csvChienDao;
/**
* Instance de la classe, pour le singleton.
*/
private
static
ChienService instance;
/**
* Constructeur prive.
*/
private
ChienService
(
) {
LOGGER.debug
(
"Constructeur"
);
csvChienDao =
new
EngineCsvChienDao
(
);
}
/**
* Singleton classique, synchro, avec creation sur demande.
*
*
@return
*/
public
static
synchronized
ChienService getInstance
(
) {
if
(
instance ==
null
) {
instance =
new
ChienService
(
);
}
return
instance;
}
public
List<
Chien>
findAllChiens
(
final
String fileName) {
final
File file =
new
File
(
fileName);
csvChienDao.init
(
file);
return
csvChienDao.findAllChiens
(
);
}
}
package
com.icauda.tp.chien.ihm;
import
static
com.icauda.tp.chien.domain.RaceDeChien.LABRADOR;
import
static
com.icauda.tp.chien.domain.Sexe.MALE;
import
static
java.awt.BorderLayout.CENTER;
import
static
java.awt.BorderLayout.NORTH;
import
static
java.awt.BorderLayout.SOUTH;
import
java.awt.Dimension;
import
java.awt.event.ActionEvent;
import
java.util.HashMap;
import
java.util.List;
import
java.util.Map;
import
javax.swing.AbstractAction;
import
javax.swing.JButton;
import
javax.swing.JDialog;
import
javax.swing.JFrame;
import
javax.swing.JPanel;
import
javax.swing.JScrollPane;
import
javax.swing.JTable;
import
javax.swing.table.TableModel;
import
javax.swing.table.TableRowSorter;
import
org.apache.log4j.Logger;
import
org.jfree.chart.ChartFactory;
import
org.jfree.chart.ChartPanel;
import
org.jfree.chart.JFreeChart;
import
org.jfree.chart.plot.PlotOrientation;
import
org.jfree.data.category.DefaultCategoryDataset;
import
org.jfree.data.general.DefaultPieDataset;
import
com.icauda.tp.chien.domain.Chien;
import
com.icauda.tp.chien.domain.RaceDeChien;
import
com.icauda.tp.chien.domain.Sexe;
import
com.icauda.tp.chien.domain.SimpleChien;
public
class
ChienJFrame7 extends
JFrame {
private
static
final
long
serialVersionUID =
-
638731145561555723
L;
private
static
final
Logger LOGGER =
Logger.getLogger
(
ChienJFrame7.class
);
private
JTable tableau;
private
ModeleDynamique2 modele;
private
JDialog ratioMaleFemaleJdialog;
private
JDialog poidsJdialog;
private
JDialog poids2Jdialog;
public
ChienJFrame7
(
) {
super
(
);
setTitle
(
"Liste des chiens (v7)"
);
setPreferredSize
(
new
Dimension
(
500
, 400
));
setDefaultCloseOperation
(
EXIT_ON_CLOSE);
modele =
new
ModeleDynamique2
(
);
tableau =
new
JTable
(
modele);
//getContentPane().add(tableau.getTableHeader(), NORTH);
getContentPane
(
).add
(
new
JScrollPane
(
tableau), CENTER);
final
JPanel boutons =
new
JPanel
(
);
boutons.add
(
new
JButton
(
new
AjouterLigneAction
(
)));
boutons.add
(
new
JButton
(
new
SupprimerLigneAction
(
)));
boutons.add
(
new
JButton
(
new
CamembertMaleFemaleAction
(
)));
boutons.add
(
new
JButton
(
new
PoidsAction
(
)));
boutons.add
(
new
JButton
(
new
Poids2Action
(
)));
getContentPane
(
).add
(
boutons, SOUTH);
tableau.setDefaultRenderer
(
Sexe.class
, new
SexeCellRenderer
(
));
tableau.setDefaultRenderer
(
RaceDeChien.class
, new
RaceCellRenderer
(
));
tableau.getColumnModel
(
).getColumn
(
4
).setCellRenderer
(
new
ListeCouleursCellRenderer
(
));
tableau.setAutoCreateRowSorter
(
true
);
final
TableRowSorter<
TableModel>
sorter =
new
TableRowSorter<
TableModel>(
tableau.getModel
(
));
tableau.setRowSorter
(
sorter);
sorter.setSortable
(
0
, false
); // colonne Nom non triable
sorter.setComparator
(
1
, new
StringSizeComparator
(
)); // Tri sur le nb de
// lettres
pack
(
);
}
private
class
AjouterLigneAction extends
AbstractAction {
private
static
final
long
serialVersionUID =
7183768497443802311
L;
private
AjouterLigneAction
(
) {
super
(
"Ajouter"
);
}
@Override
public
void
actionPerformed
(
ActionEvent e) {
LOGGER.debug
(
"Click sur le bouton ajouter"
);
final
Chien idefix =
new
SimpleChien
(
"Idefix"
,"Idefix"
,MALE, LABRADOR, new
String[] {
" blanc"
}
, 25.0
);
modele.ajouterChien
(
idefix);
}
}
private
class
SupprimerLigneAction extends
AbstractAction {
private
static
final
long
serialVersionUID =
-
5556554884674073716
L;
private
SupprimerLigneAction
(
) {
super
(
"Supprimer"
);
}
@Override
public
void
actionPerformed
(
ActionEvent e) {
final
int
[] selection =
tableau.getSelectedRows
(
);
for
(
int
i =
selection.length -
1
; i >=
0
; i--
) {
modele.supprimerChien
(
selection[i]);
}
}
}
private
class
CamembertMaleFemaleAction extends
AbstractAction {
private
static
final
long
serialVersionUID =
4515460004284651581
L;
private
CamembertMaleFemaleAction
(
) {
super
(
"Camembert M/F"
);
}
@Override
public
void
actionPerformed
(
ActionEvent e) {
LOGGER.debug
(
"cliv clic"
);
final
List<
Chien>
chiens =
modele.getChiens
(
);
int
nbMale =
0
;
for
(
Chien chien : chiens) {
if
(
chien.getSexe
(
) ==
Sexe.MALE) {
nbMale++
;
}
}
final
int
nbFemele =
chiens.size
(
) -
nbMale;
ratioMaleFemaleJdialog =
new
JDialog
(
);
ratioMaleFemaleJdialog.setTitle
(
"Ratio H/F"
);
final
DefaultPieDataset pieDataset =
new
DefaultPieDataset
(
);
pieDataset.setValue
(
"Female"
,nbFemele);
pieDataset.setValue
(
"Male"
,nbMale);
final
JFreeChart pieChart =
ChartFactory.createPieChart
(
"Ratio M/F"
,pieDataset, true
, false
, false
);
final
ChartPanel cPanel =
new
ChartPanel
(
pieChart);
ratioMaleFemaleJdialog.getContentPane
(
).add
(
cPanel, CENTER);
ratioMaleFemaleJdialog.pack
(
);
ratioMaleFemaleJdialog.setVisible
(
true
);
}
}
private
class
PoidsAction extends
AbstractAction {
private
static
final
long
serialVersionUID =
-
2621678352128531798
L;
private
PoidsAction
(
) {
super
(
"Poids"
);
}
@Override
public
void
actionPerformed
(
ActionEvent e) {
LOGGER.debug
(
"cliv clic"
);
String[] trancheNames =
{
" 0-5"
,"6-10"
,"11-15"
,"16-20"
,"21-25"
,"26-30"
,"31+"
}
;
Map<
String, Integer>
map =
new
HashMap<
String, Integer>(
);
for
(
String trancheName:trancheNames) {
map.put
(
trancheName, 0
);
}
final
List<
Chien>
chiens =
modele.getChiens
(
);
for
(
Chien chien : chiens) {
String tranche =
" 0-5"
;
if
(
6
<=
chien.getPoids
(
)) {
tranche =
" 6-10"
;
}
if
(
11
<=
chien.getPoids
(
)) {
tranche =
" 11-15"
;
}
if
(
16
<=
chien.getPoids
(
)) {
tranche =
" 16-20"
;
}
if
(
21
<=
chien.getPoids
(
)) {
tranche =
" 21-25"
;
}
if
(
26
<=
chien.getPoids
(
)) {
tranche =
" 26-30"
;
}
if
(
31
<=
chien.getPoids
(
)) {
tranche =
" 31+"
;
}
Integer value =
map.get
(
tranche);
value++
;
map.put
(
tranche, value);
}
poidsJdialog =
new
JDialog
(
);
poidsJdialog.setTitle
(
"Poids"
);
final
DefaultCategoryDataset dataset =
new
DefaultCategoryDataset
(
);
for
(
String tranche : trancheNames) {
final
Integer nb =
map.get
(
tranche);
dataset.addValue
(
nb,"Poids"
,tranche);
}
final
JFreeChart barChart =
ChartFactory.createBarChart
(
"Poids"
,"Tranches"
,"Nombre"
,/**/
dataset, PlotOrientation.VERTICAL, true
, true
, false
);
final
ChartPanel cPanel =
new
ChartPanel
(
barChart);
poidsJdialog.getContentPane
(
).add
(
cPanel, CENTER);
poidsJdialog.pack
(
);
poidsJdialog.setVisible
(
true
);
}
}
private
class
Poids2Action extends
AbstractAction {
private
static
final
long
serialVersionUID =
-
2621678352128531798
L;
private
Poids2Action
(
) {
super
(
"Poids M/F"
);
}
@Override
public
void
actionPerformed
(
ActionEvent e) {
LOGGER.debug
(
"cliv clic"
);
String[] trancheNames =
{
" 0-5"
,"6-10"
,"11-15"
,"16-20"
,"21-25"
,"26-30"
,"31+"
}
;
Map<
String, Integer>
maleMap =
new
HashMap<
String, Integer>(
);
Map<
String, Integer>
femaleMap =
new
HashMap<
String, Integer>(
);
for
(
String trancheName:trancheNames) {
maleMap.put
(
trancheName, 0
);
femaleMap.put
(
trancheName, 0
);
}
final
List<
Chien>
chiens =
modele.getChiens
(
);
for
(
Chien chien : chiens) {
String tranche =
" 0-5"
;
if
(
6
<=
chien.getPoids
(
)) {
tranche =
" 6-10"
;
}
if
(
11
<=
chien.getPoids
(
)) {
tranche =
" 11-15"
;
}
if
(
16
<=
chien.getPoids
(
)) {
tranche =
" 16-20"
;
}
if
(
21
<=
chien.getPoids
(
)) {
tranche =
" 21-25"
;
}
if
(
26
<=
chien.getPoids
(
)) {
tranche =
" 26-30"
;
}
if
(
31
<=
chien.getPoids
(
)) {
tranche =
" 31+"
;
}
final
Map<
String, Integer>
map =
(
chien.getSexe
(
) ==
Sexe.MALE) ? maleMap : femaleMap;
Integer value =
map.get
(
tranche);
value++
;
map.put
(
tranche, value);
}
poids2Jdialog =
new
JDialog
(
);
poids2Jdialog.setTitle
(
"Poids M/F"
);
final
DefaultCategoryDataset dataset =
new
DefaultCategoryDataset
(
);
for
(
String tranche : trancheNames) {
final
Integer nb =
maleMap.get
(
tranche);
dataset.addValue
(
nb," Male"
,tranche);
}
for
(
String tranche : trancheNames) {
final
Integer nb =
femaleMap.get
(
tranche);
dataset.addValue
(
nb," Female"
,tranche);
}
final
JFreeChart barChart =
ChartFactory.createBarChart
(
"Poids"
,"Tranches"
,"Nombre"
,/**/
dataset, PlotOrientation.VERTICAL, true
, true
, false
);
final
ChartPanel cPanel =
new
ChartPanel
(
barChart);
poids2Jdialog.getContentPane
(
).add
(
cPanel, CENTER);
poids2Jdialog.pack
(
);
poids2Jdialog.setVisible
(
true
);
}
}
}