Dans une interface graphique, l'application est très peu active. La plupart du temps, aucune instruction n'est exécutée. On attend qu'il se passe quelque chose pour agir en conséquence, puis une fois la situation résolue, on retourne au repos. Cette façon de concevoir une application est nommée programmation évènementielle.
Les classes que nous avons utilisées jusqu'ici savent déjà réagir correctement à certains évènements : par exemple une fenêtre de la classe JFrame est capable de se minimiser lorsque le système de fenêtrage lui en donne l'ordre.
Pour d'autres évènements, il est moins facile de prévoir quoi faire. Lorsque l'utilisateur donne l'ordre de fermer une fenêtre en appuyant sur la croix de la barre de titre, par défaut une JFrame va disparaître mais ne va pas terminer l'application. Pour changer ce comportement, il faut la reconfigurer :
fenetre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Une autre sorte d'évènement qui est déjà géré mais dont il peut être utile de changer la réponse est le rafraîchissement : l'ordre donné à un composant graphique de dessiner à l'écran ce qu'il est censé contenir. Toutes les classes qui héritent de JComponent possèdent une méthode paintComponent qui répond à cet ordre. Nous avons vu précédemment comment redéfinir cette méthode pour changer la façon dont un composant réagit, et donc contrôler l'apparence du composant.
Remarque Il est possible (et souvent utile) de provoquer un rafraîchissement artificiellement :
monComposant.repaint();
Beaucoup d'évènements n'ont pas de réponse universelle. Que se passe-t-il lorsque je clique sur un bouton ? Chaque application a sa propre réponse à cette question.
Un composant JButton, que nous avons vu précédemment, ne fait donc rien d'intéressant lorsqu'on le pousse (à part une confirmation visuelle). Son rôle n'est pas de réagir à un clic, mais de détecter un clic et de prévenir les objets qui souhaitent réagir, que l'on nomme observateurs. Un bouton n'a aucun observateur par défaut, mais il est facile d'en ajouter :
bouton.addActionListener(unObservateur);
Quand un bouton est poussé, il donne à chacun de ses observateurs une chance de réagir en invoquant leur méthode actionPerformed. Un observateur est donc simplement un objet qui contient une telle méthode, et le contenu de la méthode est le code que nous souhaitons voir s'exécuter à chaque activation du bouton.
Pour s'assurer que l'observateur confié au bouton possède bien la méthode requise, nous allons exploiter la programmation par contrat : un tel objet devra apartenir à une classe qui hérite de l'interface ActionListener, qui est définie comme suit :
public interface ActionListener { void actionPerformed(ActionEvent evenement); }
Le paramètre de cette méthode est un objet de type ActionEvent qui décrit la source et les circonstances de l'évènement. En particulier, les méthodes getSource et getActionCommand d'un tel objet fourniront des informations précieuses.
Dans le cas d'un JButton, la méthode actionPerformed est invoquée à chaque fois que le bouton est poussé, et la méthode getActionCommand renvoie le texte du bouton.
Dans le cas d'un JTextField, la méthode actionPerformed est invoquée à chaque fois que l'utilisateur appuie sur la touche «entrée» pendant l'édition du champ de texte, et la méthode getActionCommand renvoie le texte contenu dans le champ de texte.
Dans le cas d'un JRadioButton, la méthode actionPerformed est invoquée à chaque fois que l'utilisateur sélectionne l'option correspondante, et la méthode getActionCommand renvoie le texte associé à l'option.
Dans le cas d'un JCheckBox, un évènement de type ActionEvent ne nous intéresse pas car il ne fait pas la différence entre la sélection et la désélection de la case à cocher. Heureusement, il existe d'autres types d'évènements que l'on peut intercepter à l'aide de différentes classes d'observateurs.
L'interface ItemListener contient une seule méthode :
void itemStateChanged(ItemEvent evenement);
Elle est principalement utilisée pour surveiller les changements de sélection dans les listes à sélection multiple (JList) et les listes déroulantes (JComboBox), mais ces classes sont pour l'instant hors de notre portée. Par contre, une case à cocher peut être vue comme une liste d'un seul élément qui peut être sélectionné ou pas, et elle accepte donc les observateurs de ce type :
coche.addItemListener(observateur);
La méthode itemStateChanged sera invoquée par une case à cocher à chaque fois qu'elle est sélectionnée ou déselectionnée. Le paramètre de cette méthode (de type ItemEvent) possède des informations sur l'évènement, accessibles en employant les méthodes getSource, getItem et surtout getStateChange.
Les fenêtres ont une famille d'évènements qui leur sont spécifiquement associées et qui marquent leurs interactions avec le système de fenêtrage : passage au premier plan ou en arrière-plan, minimisation et restauration, ouverture et fermeture.
La classe JFrame gère déjà correctement toutes ces interactions, mais si vous souhaitez ajouter d'autres conséquences à ces interactions il est possible de les surveiller avec un observateur de type WindowListener. Cette interface possède les méthodes suivantes :
void windowActivated(WindowEvent evenement); // premier plan void windowClosed(WindowEvent evenement); // après fermeture void windowClosing(WindowEvent evenement); // avant fermeture void windowDeactivated(WindowEvent evenement); // arrière-plan void windowDeiconified(WindowEvent evenement); // restauration void windowIconified(WindowEvent evenement); // minimisation void windowOpened(WindowEvent evenement); // après ouverture
L'objet décrivant chacun de ces évènements appartient à la classe WindowEvent. Il est rare qu'il soit la source d'informations utiles, mais les méthodes getWindow et getOppositeWindow sont à noter.
Remarque Il est impossible d'ignorer les ordres du système de fenêtrage, donc les réactions que vous allez coder ne peuvent que s'ajouter au comportement attendu. La seule exception est l'ordre de fermeture, qui peut être ignoré. Si la fenêtre est configurée ainsi :
fenetre.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
La croix n'aura aucun effet. Il devient alors possible de proposer une fonctionnalité de substitution grâce à la méthode windowClosing, par exemple en choisissant de ne fermer la fenêtre que dans des circonstances précises. L'instruction qui ferme la fenêtre est :
fenetre.dispose();
Alternativement, l'instruction qui termine (brutalement) l'application est :
System.exit(0);
Fond. Écrivez une application qui ouvre une fenêtre contenant trois boutons marqués «Cyan», «Magenta» et «Jaune».
Appuyer sur l'un de ces boutons devra changer la couleur de fond en la couleur indiquée.
Remarque La méthode setBackground n'a pas d'effet visible sur une fenêtre, donc vous devrez utiliser un panneau pour contrôler la couleur de fond.
Attente. Écrivez une application qui ouvre une fenêtre contenant un disque magenta sur fond vert. Lorsque la fenêtre est en arrière-plan, le cercle doit être remplacé par un sablier.
Radio. Reprenez le premier exercice en remplaçant les boutons par des boutons radio.
Combinaison. Reprenez l'exercice précédent, en remplaçant les boutons radio par des cases à cocher. Lorsqu'aucune case n'est cochée le fond sera blanc ; lorsque toutes les cases sont cochées le fond sera noir, et ainsi de suite comme le montre ce schéma de combinaison soustractive des couleurs :
Commande. Reprenez l'exercice précédent en remplaçant les cases à cocher par un unique champ de texte. Si l'utilisateur tape dans ce champ de texte «Cyan», «Magenta» ou «Jaune» puis valide, la couleur de fond doit changer conformément. Quelque soit le texte entré, en validant on doit vider le champ de texte.