Swing : notions avancées

Ce chapitre couvre quelques notions avancées pour la programmation d’applicationsgraphiques avec Swing.

Création de composant

Avec Swing, il est possible de créer ses propres composants graphiques. Généralement,un nouveau composant héritera de JComponent.Ce nouveau composant peut enregistrerses propres listeners pour écouter les événements souris et/ou clavier.Habituellement, le nouveau composant graphique redéfinit (override) lesméthodesgetPreferredSize()et paintComponent(Graphicsg).

getPreferredSize()
Cette méthode permet de donner la dimension préférée à l’écran ducomposant.Généralement, il s’agit de la taille qui lui permet d’afficher correctementson contenu. Les layouts utilisent cette informationpour positionner les différents composants dont ils ont la charge.
paintComponent(Graphicsg)
Cette méthode permet de peindre le contenu d’un composant. L’objet de type Graphics reçu en paramètre est utilisé pour dessiner des lignes, des formes géométriquesou des lettres. Pour forcer un composant à se redessiner, on appelle une deses méthodes repaint.

Note

Lorsqu’on utilise les primitives de dessin de la classe Graphics ,il fautpasser des coordonnées x,y. x représente la position horizontale en pixels ety la position verticale en pixels. Le point (0,0) est toujours en haut à gauchede la zone du composant.

Exemple d’un composant Swing
package com.cgi.udev
												;import java.awt.Color;import java.awt.Dimension;import java.awt.Graphics;import javax.swing.JComponent;public class ExempleComponent extends JComponent
												{public ExempleComponent() {setBackground(Color.
												WHITE);}@Overridepublic Dimension getPreferredSize()
												{return new Dimension(200,
												200);}@Overrideprotected void paintComponent(
												Graphics g) {// L'appel à l'implémentation parente doit être placée en premiersuper.paintComponent(
												g);g.setColor(Color
												.BLACK);g.drawLine(10, 10
												, 180,
												10);g.setColor(Color
												.BLUE);g.drawString("Hello"
												, 10, 50);g.setColor(Color.YELLOW);g.fillRect(60, 100, 80, 80);g.setColor(Color
												.DARK_GRAY);g.drawRect(60
												, 100,
												80, 80);}}

Indication

Il est possible de transtyper (cast) un objet de type Graphics entype Graphics2Dafin d’obtenir des primitives de dessin supplémentaires.

package com.cgi.udev
												;import java.awt.Color;import java.awt.Dimension;import java.awt.Graphics;import java.awt.Graphics2D;import javax.swing.JComponent;public class ExempleComponent2D extends
												JComponent {public ExempleComponent2D() {
												setBackground(Color.WHITE);}@Overridepublic Dimension getPreferredSize()
												{return new Dimension(200,
												200);}@Overrideprotected void paintComponent(
												Graphics g) {// L'appel à l'implémentation parente doit être placée en premiersuper.paintComponent(
												g);Graphics2D g2d = (Graphics2D) g;// Graphics2D offre des primitives de transfomation 2D// pour réaliser par exemple des rotationsg2d.translate(100, 100
												);g2d.rotate(Math.PI / 4);g2d.setColor(Color.YELLOW);g2d.fillRect(-40, -
												40, 80, 80);g2d.setColor(Color.DARK_GRAY);g2d.drawRect(-40, -
												40, 80, 80);}}

Exercice

Application de dessin Mondrian

Objectif

Créez une application qui permet à l’utilisateur dedessiner des rectanglesà l’écran. Pour cela vous développerez un nouveau composant graphique Swing,le MondrianPanel.

L’utilisateur doit pouvoir presser un bouton de sa souris puis faireglissersa souris pour donner la dimension du rectangle. Quand il relâche leboutonde la souris, alors le rectangle doit être dessiné.

Note

Pour réaliser cette fonctionnalité, vous allez avoirbesoin de déclarerun MouseListeneret un MouseMotionListenerpour écouter respectivementla pression sur le bouton et le déplacement (mouse dragged).

Étape 2
L’utilisateur doit pouvoir dessiner plusieurs rectangles à la suite.
Étape 3
L’utilisateur doit pouvoir annuler les rectangles dans l’ordre inversedans lequel il les a dessinés.
Étape 4
L’utilisateur doit pouvoir sélectionner la couleur du rectangle avantde le dessiner.
Étape 5
L’utilisateur doit pouvoir sélectionner la forme qu’il souhaite dessiner :rectangle, carré, ovale, cercle

Pour réaliser cet exercice, il est intéressant de créer une classe abstraiteFormequi représentera la forme à dessiner ainsi que les classes Rectangle, Carre,Ovaleet Cercle qui en héritent.

Modèle Maven du projet à télécharger
swing-template.zip
Mise en place du projet
Éditer le fichier pom.xml du template et modifier la baliseartifactId pour spécifier le nom de votre projet.

Principe du MVC

Le MVC(modèle-vue-contrôleur) est un modèle de conception adapté pour ledéveloppement d’interface graphique. Le MVCdécoupe le traitement applicatifselon trois catégories :

Le modèle
Il contient les données applicatives ainsi que les logiques de traitementpropres à 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 modificationsdes 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 estparticulièrement important à comprendre pour les composants graphiques les pluscomplexescomme JTable,JListet JTree.

Les interactions entre les trois éléments du modèles MVCsont réalisées en Swinggrâce à des listeners. Par exemple, la vue peut être prévenue par le modèleque des données ont évolué et que la représentation graphique doit êtrerafraîchie.

Pour des composants graphiques comme le JTable,la vue et le contrôleur sonttrès largement pris en charge par le composant Swing. Le développeur doit fournirla 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 JTablereprésente un tableau à deux dimensions (type tableur). Chaquecellule peut afficher une information. À la création de ce composant, il estpossible de fournir une instance de TableModel.Il s’agit d’une interfacequi fournit les informations nécessaires au composant pour s’afficher avec notammentle nombre de lignes, le nombre de colonnes et le contenu de chaque cellule.Comme l’interface TableModelpeut s’avérer complexe à implémenter, La classe abstraite AbstractTableModelfournit une partie de l’implémentation.

Si nous disposons de la classe Individu :

package com.cgi.udev
											;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
											;}@Overridepublic 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 dechaque individu de la liste

Nous créons une implémentation de TableModelque nous appelons IndividuTableModel :

  123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
package com.cgi.udev.gui
														;import java.util.ArrayList;import java.util.Arrays;import java.util.List;import javax.swing.table.AbstractTableModel;import com.cgi.udev.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
														));}@Overridepublic String getColumnName(int columnIndex)
														{switch (columnIndex)
														{case COLONNE_NOM:return "Nom";case COLONNE_PRENOM:return "Prénom";default:return "";}}@Overridepublic int getColumnCount()
														{return 2;}@Overridepublic int getRowCount() {return individus.size
														();}@Overridepublic boolean isCellEditable(int rowIndex,
														int columnIndex)
														{return true;}@Overridepublic 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;}}@Overridepublic 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 AbstractTableModelqui implémente déjàune bonne partie de l’interface TableModel.Notre classe redéfinit des méthodescomme getColumnName,getColumnCountet getRowCountpour fournir à la vueles informations nécessaires pour connaître le nom de chaque colonne, leur nombreet le nombre de lignes. Le modèle maintient en interne une liste d’instancesde Indidivu. La seule méthode que nous devons impérativement implémenterestgetValueAt(ligne 62). Elle permet à la vue de connaître la valeur d’une celluledu tableau à afficher. Afin d’autoriser la modification du nom et du prénomdepuis 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 desindividus. Ainsi les méthodes addIndividu (lignes 73 et 82) permettentd’ajouterun individu à la liste. Quant à la méthode fixMajuscule (ligne 95), elle permetde corriger, si nécessaire, la première lettre du nom et du prénom pour la passeren majuscule. Ces méthodes modifient donc l’état du modèle. Lorsque le modèlechange, il doit en avertir la vue afin que celle-ci puisse rafraîchir lesdonnées à l’écran. La classe abstraite AbstractTableModelfournit une gestionde listeners spécialisés. Lorsqu’un objet implémentant TableModel est associé à un composantJTable,ce dernier enregistre plusieurs listeners auprès du modèle pourêtre prévenu des modifications éventuelles du modèle. Pour notifier de cesmodificationsune classe qui hérite de AbstractTableModeldoit appeler les méthodesfireTableCellUpdated,fireTableDataChanged,fireTableRowsDeleted,fireTableRowsInsertedfireTableRowsUpdatedou fireTableStructureChangedselon le type de modificationsqui 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 fireTableCellUpdatedpour signaler que le contenud’une cellule a changé.

Enfin, la classe IndividuTableur représente la fenêtre de l’applicationcontenantle composant JTable:

 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
package com.cgi.udev.gui
														;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;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.udev.Individu;public class IndividuTableur extends JFrame {private IndividuTableModel individuModel;
														@Overrideprotected 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(new
														ActionListener()
														{@Overridepublic void actionPerformed(
														ActionEvent e) {individuModel.addIndividu();}});menu.add(new
														JMenuItem(
														"Corriger Maj."
														)).
														addActionListener(new
														ActionListener()
														{@Overridepublic void actionPerformed(
														ActionEvent e) {individuModel.fixMajuscule();}});menu.add(new
														JMenuItem(
														"Imprimer"
														)).
														addActionListener(new
														ActionListener()
														{@Overridepublic void actionPerformed(
														ActionEvent e) {imprimer();}});menu.addSeparator();menu.add(new JMenuItem(
														"Fermer")).
														addActionListener(new
														ActionListener()
														{@Overridepublic void actionPerformed(
														ActionEvent e) {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 29, nous créons l’instance de IndividuTableModel et nousl’associonsà une instance de JTable.Des lignes 37 à 61, nous créons des entrées de menupour permettre à l’utilisateur d’interagir. Certaines des actions appellent desméthodes du modèle qui le modifie et qui déclencherons un événement en directionde la vue qui n’aura plus qu’à se rafraîchir.

Dans Swing, il existe d’autres composants de haut niveau qui reprennent le modèledu MVC pourpermettre de gérer des représentations complexes de données comme laJListpour afficher une liste d’éléments ou le JTreepour gérer des représentationsarborescentes de données.