Swing : les interactions¶
Ce chapitre présente différents types d’interaction que l’on peut mettre en place dans les applications graphiques basées sur Swing.
Les listeners¶
Pour interagir avec l’utilisateur, chaque composant graphique peut intercepter des événements (frappe d’une touche sur le clavier, clic souris…) et réagir en conséquence. Pour associer un comportement à un événement, on ajoute un écouteur d’événement (listener) au composant, c’est-à-dire un objet qui implémente l’interface EventListener. Cette interface est simplement une interface marqueur dont héritent toutes les interfaces qui représentent des listeners pour des événements particuliers.
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 | package com.cgi.udev.gui;
import java.awt.Dimension;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.EventObject;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
public class ExempleListener extends JFrame {
private class ExempleMouseListener implements MouseListener {
@Override
public void mouseClicked(MouseEvent e) {
printEvent(e);
}
@Override
public void mouseEntered(MouseEvent e) {
printEvent(e);
}
@Override
public void mouseExited(MouseEvent e) {
printEvent(e);
}
@Override
public void mousePressed(MouseEvent e) {
printEvent(e);
}
@Override
public void mouseReleased(MouseEvent e) {
printEvent(e);
}
}
private class ExempleKeyListener implements KeyListener {
@Override
public void keyPressed(KeyEvent e) {
printEvent(e);
}
@Override
public void keyReleased(KeyEvent e) {
printEvent(e);
}
@Override
public void keyTyped(KeyEvent e) {
printEvent(e);
}
}
@Override
protected void frameInit() {
super.frameInit();
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setTitle("Exemple listener");
JComponent component = new JEditorPane();
component.setPreferredSize(new Dimension(300, 300));
component.addMouseListener(new ExempleMouseListener());
component.addKeyListener(new ExempleKeyListener());
this.add(component);
this.pack();
}
private void printEvent(EventObject e) {
System.out.println(e);
}
public static void main(String[] args) {
JFrame window = new ExempleListener();
window.setLocationRelativeTo(null);
window.setVisible(true);
}
}
|
Dans l’exemple ci-dessus, l’application affiche un éditeur de texte sous la forme d’un carré de 300 pixels sur 300 pixels. Aux lignes 72 et 73, on ajoute à ce composant une instance de MouseListener et une instance de KeyListener (les classes implémentant ces interfaces sont déclarées sous la forme de classes internes). Ces listeners se contentent d’afficher sur la sortie standard la représentation sous forme de chaîne de caractères de chaque événement.
Chaque événement fournit des informations liées à son origine. Par exemple, un MouseEvent indique si un bouton de la souris est pressé. Un KeyEvent indique la touche du clavier qui est soit pressée soit relâchée. Un composant peut utiliser ces informations pour modifier son état. Ainsi, la classe JEditorPane, utilisée dans l’exemple précédent, enregistre en interne un KeyListener pour savoir si une touche a été pressée et en déduit le caractère qui doit être ajouté dans l’éditeur.
Un listener couramment utilisé est le type ActionListener. Ce listener écoute les événements de type ActionEvent. Un ActionEvent représente une interaction utilisateur simple. Il est associé à une commande qui est un simple identifiant sous la forme d’une chaîne de caractères. Les boutons acceptent des listeners de ce type.
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 108 109 110 111 112 113 114 115 | package com.cgi.udev.gui;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.WindowConstants;
public class ExempleActionListener extends JFrame {
private JComboBox<String> civilite;
private JTextField nom;
private JTextField prenom;
private JTextArea adresse;
@Override
protected void frameInit() {
super.frameInit();
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setTitle("Exemple Listeners");
this.getContentPane().setLayout(new GridBagLayout());
int rowIndex = 0;
civilite = new JComboBox<String>(new String[] {"Madame", "Monsieur"});
nom = new JTextField();
prenom = new JTextField();
adresse = new JTextArea(10, 20);
addRow(rowIndex++, "Civilité", civilite);
addRow(rowIndex++, "Nom", nom);
addRow(rowIndex++, "Prénom", prenom);
addRow(rowIndex++, "Addresse", adresse);
JButton okButton = new JButton("Ok");
okButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
onOk();
}
});
JButton cancelButton = new JButton("Annuler");
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
onCancel();
}
});
addButtons(rowIndex++, okButton, cancelButton);
this.pack();
this.setResizable(false);
}
private void onOk() {
// On affiche le contenu du formulaire sur la sortie standard
System.out.println(String.format("%1$s %2$s %3$s résidant au %4$s",
civilite.getSelectedItem(), prenom.getText(), nom.getText(), adresse.getText()));
}
private void onCancel() {
// on cache la fenêtre
this.setVisible(false);
// on supprime la fenêtre
this.dispose();
}
private void addRow(int rowIndex, String titre, JComponent component) {
GridBagConstraints cst = new GridBagConstraints();
cst.fill = GridBagConstraints.HORIZONTAL;
cst.anchor = GridBagConstraints.NORTH;
cst.insets = new Insets(5, 20, 5, 20);
cst.gridy = rowIndex;
cst.gridx = 0;
cst.weightx = .3;
JLabel label = new JLabel(titre);
label.setLabelFor(component);
this.add(label, cst);
cst.gridx = 1;
cst.weightx = .7;
this.add(component, cst);
}
private void addButtons(int rowIndex, JButton...buttons) {
JPanel panel = new JPanel();
for (JButton button : buttons) {
panel.add(button);
}
GridBagConstraints cst = new GridBagConstraints();
cst.insets = new Insets(5, 10, 0, 0);
cst.fill = GridBagConstraints.HORIZONTAL;
cst.gridy = rowIndex;
cst.gridx = 0;
cst.gridwidth = 2;
this.add(panel, cst);
}
public static void main(String[] args) {
JFrame window = new ExempleActionListener();
window.setLocationRelativeTo(null);
window.setVisible(true);
}
}
|
Le code ci-dessus reprend l’application de saisie de formulaire qui utilisait le GridBagLayout dans le chapitre précédent. Entre les lignes 44 et 58, on crée les boutons de l’application en ajoutant des instances de ActionListener sous la forme de classes anonymes. Lorsque l’utilisateur clique sur le bouton Ok (respectivement Annuler), la méthode privée onOk (respectivement onCancel) est appelée. La méthode onOk (lignes 64-68) affiche sur la sortie standard les informations récupérées des différentes zones de saisie. La méthode onCancel (lignes 70-75) cache la fenêtre et appelle la méthode dispose pour la détruire.
L’interface Action¶
Dans une application, une même fonctionnalité peut souvent être déclenchée de plusieurs façons par un utilisateur :
- en cliquant dans un menu
- en cliquant sur une icône dans la barre d’icônes
- en exécutant un raccourci clavier
- en cliquant sur un bouton dans une boite de dialogue
Swing permet de gérer ce phénomène grâce à l’interface Action. Plutôt que d’ajouter un listener, il est possible d’associer une action à une objet de type JMenuItem ou JButton. L’interface Action hérite de ActionListener pour pouvoir fournir un comportement lorsque l’utilisateur clique sur un bouton. Mais l’interface Action permet également de définir un libellé, une icône, une description et un raccourci clavier. Tous les composants associés s’adapteront en fonction de l’état de l’action. Si une action est désactivée avec sa méthode setEnabled alors tous les boutons associés apparaîtront grisés.
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 | package com.cgi.udev.gui;
import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.net.URI;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
public class ExempleMenu extends JFrame {
private Action exempleAction;
private class ExempleAction extends AbstractAction {
public ExempleAction() {
super("Java", UIManager.getIcon("FileView.fileIcon"));
putValue(SHORT_DESCRIPTION, "Cliquez pour en savoir plus sur Java");
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_A, ActionEvent.CTRL_MASK));
}
@Override
public void actionPerformed(ActionEvent e) {
try {
// on ouvre la page Web dans le navigateur par défaut
Desktop.getDesktop().browse(new URI("https://fr.wikipedia.org/wiki/Java_(langage)"));
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
@Override
protected void frameInit() {
super.frameInit();
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setTitle("Exemple Menus");
this.exempleAction = new ExempleAction();
this.setJMenuBar(new JMenuBar());
this.getJMenuBar().add(createMenu());
JPanel panel = new JPanel();
panel.add(new JButton(exempleAction));
this.add(panel);
this.setSize(500, 300);
}
private JMenu createMenu() {
JMenu menu = new JMenu("Menu");
menu.add(new JMenuItem(exempleAction));
JCheckBoxMenuItem checkBox = new JCheckBoxMenuItem("Activer", true);
checkBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
exempleAction.setEnabled(checkBox.getState());
}
});
menu.add(checkBox);
menu.addSeparator();
menu.add(new JMenuItem("Fermer")).addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
ExempleMenu.this.dispose();
}
});
return menu;
}
public static void main(String[] args) {
JFrame window = new ExempleMenu();
window.setLocationRelativeTo(null);
window.setVisible(true);
}
}
|
Dans l’exemple ci-dessus, on déclare une action comme classe interne. Plutôt que d’implémenter l’interface Action, la classe interne ExempleAction étend la classe abstraite AbstractAction. Comme l’interface Action peut être assez complexe à implémenter, cette classe abstraite fournit la plus grande partie du code hormis l’implémentation de la méthode actionPerformed qui correspond au traitement proprement dit.
L’action déclarée par notre application possède un nom, une description (pour afficher une bulle d’aide), une icône et un raccourci clavier (Ctrl+A). La même instance de ExempleAction est associée à une entrée dans le menu et au bouton dans la fenêtre. De plus, une autre entrée dans le menu permet de désactiver l’action (ce qui aura pour conséquence de désactiver le raccourci clavier et de griser le bouton et le menu associés).
Les boites de dialogue¶
Si vous souhaitez créer des boites de dialogues, Swing fournit la classe JDialog. Cette classe offre des fonctionnalités spécifiques pour ce type d’interface. Par exemple JDialog permet de choisir si la boite de dialogue doit être modale (c’est-à-dire si elle doit empêcher toute interaction avec la fenêtre parente) ou non modale.
Swing fournit également la classe JOptionPane qui permet de créer rapidement des boites de dialogue simples. Les méthodes fournies par la classe JOptionPane sont bloquantes. Cela signifie que l’exécution s’arrête jusqu’à ce que l’utilisateur ait choisi parmi les options de la boite de dialogue.
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 | package com.cgi.udev.gui;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
public class ExempleOptionPane extends JFrame {
private static final String APP_TITLE = "Exemple OptionPane";
public static void main(String[] args) {
JOptionPane.showMessageDialog(null, "Bonjour", APP_TITLE, JOptionPane.PLAIN_MESSAGE);
JOptionPane.showMessageDialog(null, "Ceci est un message", APP_TITLE, JOptionPane.INFORMATION_MESSAGE);
JOptionPane.showMessageDialog(null, "Ceci est un avertissement", APP_TITLE, JOptionPane.WARNING_MESSAGE);
JOptionPane.showMessageDialog(null, "Ceci est une erreur", APP_TITLE, JOptionPane.ERROR_MESSAGE);
JOptionPane.showMessageDialog(null, "On passe à la suite ?", APP_TITLE, JOptionPane.QUESTION_MESSAGE);
int jouer = JOptionPane.showConfirmDialog(null, "Voulez-vous jouer ?", APP_TITLE, JOptionPane.YES_NO_OPTION);
if (jouer == JOptionPane.YES_OPTION) {
Random random = new Random();
do {
int bonneResponse = random.nextInt(20) + 1;
String reponse = JOptionPane.showInputDialog(null, "Donnez un nombre entre 1 et 20 :",
APP_TITLE, JOptionPane.QUESTION_MESSAGE);
if (reponse == null) {
break;
}
try {
int valeur = Integer.valueOf(reponse);
if (valeur == bonneResponse) {
JOptionPane.showMessageDialog(null, "Bravo vous avez gagné !",
APP_TITLE, JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(null, "Perdu ! La bonne réponse était " + bonneResponse + ".",
APP_TITLE, JOptionPane.WARNING_MESSAGE);
}
} catch (NumberFormatException nfe) {
JOptionPane.showMessageDialog(null, "'" + reponse + "' n'est pas un nombre !",
APP_TITLE, JOptionPane.ERROR_MESSAGE);
}
} while (JOptionPane.showConfirmDialog(null, "Voulez-vous rejouer ?",
APP_TITLE, JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION);
JOptionPane.showMessageDialog(null, "Au revoir...", APP_TITLE, JOptionPane.PLAIN_MESSAGE);
}
}
}
|
Les boites de dialogues avancées¶
Swing fournit des classes pour des boites de dialogue évoluées.
Boite de dialogue des fichiers¶
La classe JFileChooser permet de créer une boite de dialogue de sélection de fichiers et/ou de répertoires.
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 | package com.cgi.udev.gui;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.WindowConstants;
import javax.swing.filechooser.FileNameExtensionFilter;
public class EditeurTexte extends JFrame {
private JEditorPane editor;
@Override
protected void frameInit() {
super.frameInit();
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setTitle("Simple éditeur de texte");
this.setJMenuBar(new JMenuBar());
this.getJMenuBar().add(createMenu());
editor = new JEditorPane();
editor.setEditable(false);
this.add(new JScrollPane(editor));
this.setSize(800, 600);;
}
private JMenu createMenu() {
JMenu menu = new JMenu("Fichier");
menu.add(new JMenuItem("Ouvrir...")).addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
open();
}
});
menu.addSeparator();
menu.add(new JMenuItem("Fermer")).addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dispose();
}
});
return menu;
}
private void open() {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
fileChooser.setMultiSelectionEnabled(false);
fileChooser.addChoosableFileFilter(new FileNameExtensionFilter("Fichiers texte (txt, html, rtf)",
"txt", "html", "xhtml", "rtf"));
int choix = fileChooser.showOpenDialog(this);
if (choix == JFileChooser.APPROVE_OPTION) {
try {
editor.setPage(fileChooser.getSelectedFile().toURI().toString());
} catch (IOException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(this, "Une erreur est survenue :\n" + e.getMessage(),
"Erreur", JOptionPane.ERROR_MESSAGE);
}
}
}
public static void main(String[] args) {
JFrame window = new EditeurTexte();
window.setLocationRelativeTo(null);
window.setVisible(true);
}
}
|
La classe ci-dessus crée un éditeur de texte simple qui permet de choisir le fichier que l’on veut consulter. La méthode open déclarée à partir de la ligne 55 utilise une instance de JFileChooser pour récupérer le fichier sélectionné et donner son URL au composant JEditorPane qui l’affiche.
Boite de sélection de couleur¶
La classe JColorChooser affiche une boite de dialogue permettant de choisir une couleur.
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 | package com.cgi.udev.gui;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JColorChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
public class VisualiseurCouleur extends JFrame {
private JPanel colorPanel;
@Override
protected void frameInit() {
super.frameInit();
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setTitle("Selecteur de couleur");
this.setJMenuBar(new JMenuBar());
this.getJMenuBar().add(createMenu());
this.colorPanel = new JPanel();
this.add(this.colorPanel);
this.setSize(800, 600);;
}
private JMenu createMenu() {
JMenu menu = new JMenu("Couleur");
menu.add(new JMenuItem("Couleur de fond...")).addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
chooseColor();
}
});
menu.addSeparator();
menu.add(new JMenuItem("Fermer")).addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dispose();
}
});
return menu;
}
private void chooseColor() {
Color newColor = JColorChooser.showDialog(this, "Choisissez la couleur de fond",
colorPanel.getBackground());
this.colorPanel.setBackground(newColor);
}
public static void main(String[] args) {
JFrame window = new VisualiseurCouleur();
window.setLocationRelativeTo(null);
window.setVisible(true);
}
}
|
L’application ci-dessus offre un menu pour changer la couleur de fond de la fenêtre.
Exercice¶
Application pour sauvegarder les données personnelles (suite)
- Objectif
Reprenez l’application de saisie des données personnelles. Cette application doit avoir un bouton
Sauverqui ouvre une boite de dialogue de sauvegarde de fichier. Le fichier doit ensuite être sauvé sur le disque au format VCard.L’application doit vérifier au préalable qu’au moins les champs prénom et nom ont été saisis. Si ce n’est pas le cas, une boite de dialogue doit informer l’utilisateur des champs manquants. Utilisez pour cela JOptionPane.
Application pour charger/sauvegarder les données personnelles (suite)
- Objectif
Reprenez l’application de saisie des données personnelles. Ajoutez un menu
Données. Ce menu contient quatre entrées :Entrée Raccourci Description Ouvrir… Ctrl+O Cette entrée de menu permet de sélectionner sur le disque un fichier avec l’extension vcf(l’extension des fichiers VCard) et de charger les données dans les champs de l’application.Sauver… Ctrl+S Cette entrée réalise la même action que le bouton Sauverintroduit à l’exercice précédentRéinitialiser Cette entrée remet tous les champs à vide après avoir demandé confirmation à l’utilisateur. Quitter Ctrl+Q Cette entrée permet de fermer l’application Créez des classes héritant de AbstractAction pour gérer ces entrées et associez-les aux raccourcis clavier.