Swing : notions avancées¶
Ce chapitre couvre quelques notions avancées pour la programmation d’applications graphiques avec Swing
Principe du MVC¶
Le MVC (modèle-vue-contrôleur) est un modèle de conception adapté pour le développement d’interface graphique. Le MVC découpe le traitement applicatif selon trois catégories :
- Le modèle
- Il contient les données applicatives ainsi que les logiques de traitement propres à l’application.
- La vue
- Elle gère la représentation graphique des données et l’interface utilisateur
- Le contrôleur
- Il est sollicité par les interactions de l’utilisateur ou les modifications des données. Il assure la cohérence entre le modèle et la vue.
Ce modèle se retrouve dans l’architecture des composants Swing. Ce modèle est particulièrement important à comprendre pour les composants graphiques les plus complexes comme JTable, JList et JTree.
Les interactions entre les trois éléments du modèles MVC sont réalisées en Swing grâce à des listeners. Par exemple, la vue peut être prévenue par le modèle que des données ont évolué et que la représentation graphique doit être rafraîchie.
Pour des composants graphiques comme le JTable, la vue et le contrôleur sont très largement pris en charge par le composant Swing. Le développeur doit fournir la partie modèle en implémentant une classe qui joue le rôle du modèle de données (data model).
La classe JTable¶
La classe JTable représente un tableau à deux dimensions (type tableur). Chaque cellule peut afficher une information. À la création de ce composant, il est possible de fournir une instance de TableModel. Il s’agit d’une interface qui fournit les informations nécessaires au composant pour s’afficher avec notamment le nombre de lignes, le nombre de colonnes et le contenu de chaque cellule. Comme l’interface TableModel peut s’avérer complexe à implémenter, la classe abstraite AbstractTableModel fournit une partie de l’implémentation.
Si nous disposons de la classe Individu :
package com.cgi.poei;
public class Individu {
private String nom;
private String prenom;
public Individu() {
}
public Individu(String prenom, String nom) {
this.prenom = prenom;
this.nom = nom;
}
public String getNom() {
return nom;
}
public String getPrenom() {
return prenom;
}
public void setNom(String nom) {
this.nom = nom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
@Override
public String toString() {
return "" + this.prenom + " " + this.nom;
}
}
Nous pouvons créer une application Swing qui va afficher une liste d’individus à l’aide d’une JTable. Dans notre application, il est possible de :
- ajouter des individus à la liste
- modifier le nom et le prénom de chaque individu
- mettre automatiquement en majuscule la première lettre du nom et du prénom de chaque individu de la liste
Nous créons une implémentation de TableModel que nous appelons IndividuTableModel :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | package com.cgi.poei.gui;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.table.AbstractTableModel;
import com.cgi.poei.Individu;
public class IndividuTableModel extends AbstractTableModel {
private static final int COLONNE_NOM = 0;
private static final int COLONNE_PRENOM = 1;
private List<Individu> individus = new ArrayList<>();
public IndividuTableModel(Individu ...individus) {
this.individus.addAll(Arrays.asList(individus));
}
@Override
public String getColumnName(int columnIndex) {
switch (columnIndex) {
case COLONNE_NOM:
return "Nom";
case COLONNE_PRENOM:
return "Prénom";
default:
return "";
}
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public int getRowCount() {
return individus.size();
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
switch (columnIndex) {
case COLONNE_NOM:
individus.get(rowIndex).setNom(aValue.toString());
break;
case COLONNE_PRENOM:
individus.get(rowIndex).setPrenom(aValue.toString());
break;
}
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
switch (columnIndex) {
case COLONNE_NOM:
return individus.get(rowIndex).getNom();
case COLONNE_PRENOM:
return individus.get(rowIndex).getPrenom();
default:
return "";
}
}
public void addIndividu(Individu u) {
this.individus.add(u);
this.fireTableRowsInserted(this.individus.size()-1, this.individus.size()-1);
}
public void addIndividu() {
this.addIndividu(new Individu());
}
public void fixMajuscule() {
int rowIndex = 0;
for (Individu individu : individus) {
individu.setNom(fixMajuscule(individu.getNom(), rowIndex, COLONNE_NOM));
individu.setPrenom(fixMajuscule(individu.getPrenom(), rowIndex, COLONNE_PRENOM));
++rowIndex;
}
}
public List<Individu> getIndividus() {
return individus;
}
private String fixMajuscule(String value, int rowIndex, int columnIndex) {
if (value == null || value.length() == 0) {
return value;
}
if (Character.isLowerCase(value.charAt(0))) {
this.fireTableCellUpdated(rowIndex, columnIndex);
return value.substring(0, 1).toUpperCase() + value.substring(1);
}
return value;
}
}
|
La classe IndividuTableModel hérite de AbstractTableModel qui implémente déjà une bonne partie de l’interface TableModel. Notre classe redéfinit des méthodes comme getColumnName, getColumnCount et getRowCount pour fournir à la vue les informations nécessaires pour connaître le nom de chaque colonne, leur nombre et le nombre de lignes. Le modèle maintient en interne une liste d’instances de Indidivu. La seule méthode que nous devons impérativement implémenter est getValueAt (ligne 62). Elle permet à la vue de connaître la valeur d’une cellule du tableau à afficher. Afin d’autoriser la modification du nom et du prénom depuis la vue, nous devons également implémenter la méthode setValueAt (ligne 50) afin que de traiter les informations qui nous seront fournies à travers la vue.
Le modèle fournit également ses propres méthodes pour modifier la liste des individus. Ainsi les méthodes addIndividu (lignes 73 et 82) permettent d’ajouter un individu à la liste. Quant à la méthode fixMajuscule (ligne 95), elle permet de corriger, si nécessaire, la première lettre du nom et du prénom pour la passer en majuscule. Ces méthodes modifient donc l’état du modèle. Lorsque le modèle change, il doit en avertir la vue afin que celle-ci puisse rafraîchir les données à l’écran. La classe abstraite AbstractTableModel fournit une gestion de listeners spécialisés. Lorsqu’un objet implémentant TableModel est associé à un composant JTable, ce dernier enregistre plusieurs listeners auprès du modèle pour être prévenu des modifications éventuelles du modèle. Pour notifier de ces modifications une classe qui hérite de AbstractTableModel doit appeler les méthodes fireTableCellUpdated, fireTableDataChanged, fireTableRowsDeleted, fireTableRowsInserted fireTableRowsUpdated ou fireTableStructureChanged selon le type de modifications qui ont eu lieu sur le modèle.
Pour notre implémentation, à la ligne 75, nous appelons la méthode fireTableRowsInserted pour signaler à la vue qu’une nouvelle ligne a été ajoutée et à la ligne 101, nous appelons la méthode fireTableCellUpdated pour signaler que le contenu d’une cellule a changé.
Enfin, la classe IndividuTableur représente la fenêtre de l’application contenant le composant JTable :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | package com.cgi.poei.gui;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.WindowConstants;
import com.cgi.poei.Individu;
public class IndividuTableur extends JFrame {
private IndividuTableModel individuModel;
@Override
protected void frameInit() {
super.frameInit();
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setTitle("Table des individus");
this.setJMenuBar(new JMenuBar());
this.getJMenuBar().add(createMenu());
this.individuModel = new IndividuTableModel();
this.add(new JScrollPane(new JTable(individuModel)));
this.setSize(800, 600);
}
private JMenu createMenu() {
JMenu menu = new JMenu("Individus");
menu.add(new JMenuItem("Ajouter")).addActionListener(e -> this.individuModel.addIndividu());
menu.add(new JMenuItem("Corriger Maj.")).addActionListener(e -> this.individuModel.fixMajuscule());
menu.add(new JMenuItem("Imprimer")).addActionListener(e -> this.imprimer());
menu.addSeparator();
menu.add(new JMenuItem("Fermer")).addActionListener(e -> this.dispose());;
return menu;
}
private void imprimer() {
individuModel.getIndividus().forEach(System.out::println);
}
public void addIndividu(Individu u) {
this.individuModel.addIndividu(u);
}
public static void main(String[] args) {
IndividuTableur window = new IndividuTableur();
window.addIndividu(new Individu("John", "Doe"));
window.addIndividu(new Individu("Anabella", "Doe"));
window.addIndividu(new Individu("jean", "dupond"));
window.setLocationRelativeTo(null);
window.setVisible(true);
}
}
|
À la ligne 26, nous créons l’instance de IndividuTableModel et nous l’associons à une instance de JTable. Aux lignes 34, 35 et 36, nous créons des entrées de menu pour permettre à l’utilisateur d’interagir. Certaines des actions appellent des méthodes du modèle qui le modifie et qui déclencherons un événement en direction de la vue qui n’aura plus qu’à se rafraîchir.
Dans Swing, il existe d’autres composants de haut niveau qui reprennent le modèle du MVC pour permettre de gérer des représentations complexes de données comme la JList pour afficher une liste d’éléments ou le JTree pour gérer des représentations arborescentes de données.