Swing : les interactions¶
Ce chapitre présente différents types d’interaction que l’on peut mettreen place dans les applications graphiques basées sur Swing.
Les listeners¶
Pour interagir avec l’utilisateur, chaque composant graphique peut intercepterdes événements (frappe d’une touche sur le clavier, clic souris…) et réagiren conséquence. Pour associer un comportement à un événement, on ajoute unécouteur d’événement (listener) au composant, c’est-à-dire un objetqui implémente l’interface EventListener.Cette interface est simplement une interface marqueur dont héritent toutes lesinterfacesqui représentent des listeners pour des événements particuliers.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788 |
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 {@Overridepublic void mouseClicked(MouseEvent e) {printEvent(e);}@Overridepublic void mouseEntered(MouseEvent e) {printEvent(e);}@Overridepublic void mouseExited(MouseEvent e) {printEvent(e);}@Overridepublic void mousePressed(MouseEvent e) {printEvent(e);}@Overridepublic void mouseReleased(MouseEvent e) {printEvent(e);}}private class ExempleKeyListener implements
KeyListener {@Overridepublic void keyPressed(KeyEvent e) {printEvent(e);}@Overridepublic void keyReleased(KeyEvent e) {printEvent(e);}@Overridepublic void keyTyped(KeyEvent
e) {printEvent(e);}}@Overrideprotected 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 formed’un carré de 300 pixels sur 300 pixels. Aux lignes 72 et 73, on ajoute à cecomposantune instance de MouseListeneret une instance de KeyListener(les classesimplémentant ces interfaces sont déclarées sous la forme de classes internes). Ceslisteners se contentent d’afficher sur la sortie standard la représentationsous forme de chaîne de caractères de chaque événement.
Chaque événement fournit des informations liées à son origine. Par exemple, unMouseEventindique si un bouton de la souris est pressé. Un KeyEventindiquela touche du clavier qui est soit pressée soit relâchée. Un composant peut utiliserces informations pour modifier son état. Ainsi, la classe JEditorPane ,utiliséedans l’exemple précédent, enregistre en interne un KeyListenerpour savoirsi une touche a été pressée et en déduit le caractère qui doit être ajouté dansl’éditeur.
Un listener couramment utilisé est le type ActionListener.Ce listenerécoute les événements de type ActionEvent.Un ActionEventreprésenteune interaction utilisateur simple. Il est associé à une commande qui estun simple identifiant sous la forme d’une chaîne de caractères. Les boutonsacceptent des listeners de ce type.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
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;@Overrideprotected 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()
{@Overridepublic void actionPerformed(
ActionEvent e) {onOk();}});JButton cancelButton = new JButton(
"Annuler");cancelButton.addActionListener(new
ActionListener()
{@Overridepublic 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 standardSystem.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êtrethis.setVisible(false
);// on supprime la fenêtrethis.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 utilisaitle GridBagLayoutdans le chapitre précédent. Entre les lignes 44 et 58, oncrée les boutons de l’applicationen ajoutant des instances de ActionListenersous la forme de classes anonymes. Lorsquel’utilisateur clique sur le bouton Ok (respectivement Annuler), laméthodeprivée onOk (respectivement onCancel) est appelée. La méthode onOk(lignes64-68) affiche sur la sortie standard les informations récupérées des différenteszones de saisie. La méthode onCancel (lignes 70-75) cache la fenêtre etappellela méthode disposepour la détruire.
L’interface Action¶
Dans une application, une même fonctionnalité peut souvent être déclenchée deplusieurs 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’ajouterun listener, il est possible d’associer une action à une objet de type JMenuItem ouJButton.L’interface Actionhérite de ActionListenerpour pouvoir fournir uncomportement 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 raccourciclavier. Tous les composants associés s’adapteront en fonction de l’état del’action.Si une action est désactivée avec sa méthode setEnabledalors tous les boutonsassociés apparaîtront grisés.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
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));}@Overridepublic void actionPerformed(
ActionEvent e) {try {// on ouvre la page Web dans le navigateur par défautDesktop.getDesktop().browse(new URI("https://fr.wikipedia.org/wiki/Java_(langage)"));
} catch (Exception ex) {ex.printStackTrace();
}}}@Overrideprotected 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()
{@Overridepublic void actionPerformed(
ActionEvent e) {exempleAction.setEnabled(
checkBox.getState());}});menu.add(checkBox);menu.addSeparator();menu.add(new JMenuItem(
"Fermer")).
addActionListener(new
ActionListener()
{@Overridepublic 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ôtque d’implémenter l’interface Action,la classe interne ExempleActionétend la classe abstraite AbstractAction.Comme l’interface Actionpeutêtre assez complexe à implémenter, cette classe abstraite fournit la plus grandepartie du code hormis l’implémentation de la méthode actionPerformedquicorrespond 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 deExempleAction est associée àune entrée dans le menu et au bouton dans la fenêtre. De plus, une autre entrée dansle menu permet de désactiver l’action (ce qui aura pour conséquence de désactiverle 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. Parexemple JDialogpermet 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) ounonmodale.
Swing fournit également la classe JOptionPanequi permet de créer rapidementdes boites de dialogue simples. Les méthodes fournies par la classeJOptionPanesont 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.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354 |
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 JFileChooserpermet de créer une boite de dialogue de sélection defichiers et/ou de répertoires.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879 |
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;@Overrideprotected 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
() {@Overridepublic void actionPerformed(
ActionEvent e
) {open();}});menu.addSeparator();menu.add(new
JMenuItem(
"Fermer"
)).
addActionListener
(new ActionListener
() {@Overridepublic 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 (IOExceptione) {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 lefichierque l’on veut consulter. La méthode open déclarée à partir de la ligne55utilise une instance de JFileChooserpour récupérer le fichier sélectionnéet donner son URL au composant JEditorPanequi l’affiche.
Boite de sélection de couleur¶
La classe JColorChooseraffiche une boite de dialogue permettant de choisir unecouleur.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263 |
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;@Overrideprotected 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
() {@Overridepublic void actionPerformed(
ActionEvent e
) {chooseColor();}});menu.addSeparator();menu.add(new
JMenuItem(
"Fermer"
)).
addActionListener
(new ActionListener
() {@Overridepublic 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 lafenêtre.
Exercice¶
Application pour sauvegarder les donnéespersonnelles (suite)
- Objectif
-
Reprenez l’application de saisie des données personnelles.Cette applicationdoit avoir un bouton
Sauverqui ouvre une boite de dialogue desauvegardede fichier. Le fichier doit ensuite être sauvé sur le disque au format VCard.L’application doit vérifier au préalable qu’au moins leschamps prénom et nomont été saisis. Si ce n’est pas le cas, une boite de dialogue doitinformerl’utilisateur des champs manquants. Utilisez pour cela JOptionPane .
Application pour charger/sauvegarder les donnéespersonnelles (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 unfichier avecl’extension vcf(l’extension des fichiers VCard) et dechargerles 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 AbstractActionpour gérer ces entrées etassociez-les aux raccourcis clavier.