La gestion des évènements
La gestion des évènements en Java
Le langage Java propose un mécanisme clairement établit pour le traitement d'évènements.Ce mécanisme repose sur la notion de "Listener" (d'écouteurs si vous préférez).
En fait, ce mécanisme n'a pas été le premier proposé dans le JDK 1.0. En effet, la première version du JDK proposait un tout autre mécanisme de gestion d'évènements. Celui-ci était basé sur une autre notion. Il s'est avéré que ce premier mécanisme fut problématique à utiliser. Dés la version 1.1 du JDK, le second mécanisme fut donc proposé. Dans ce cours nous ne traiterons que ce dernier modèle (le premier étant maintenant largement déprécié) !
Ce qui faut aussi savoir, c'est que ce mécanisme de gestion d'évènements n'est, en fait, pas directement rattaché à une quelconque API d'interface graphique. Au contraire, il se veut indépendant de tout contexte. On peut donc traités les évènements Java en réponse à des clicks souris, mais aussi en réponse à tout autre activité se déroulant au sein de la JVM.
Bien entendu, en Java, tout est objet. En conséquence, les évènements sont aussi des objets. D'ou deux classes particulières : java.util.EventObject et java.awt.AWTEvent. La première correspond à la classe d'évènements la plus générique (elle dérive directement de java.lang.Object. De la seconde dérive tous les évènements le l'AWT et de Swing : c'est donc la classe mère des évènements d'interfaces utilisateurs.
La classe java.util.EventObject possède une méthode fort intéressante : getSource(). Elle permet de récupérer l'objet sur lequel l'évènement est initialement apparut. En conséquence, quand vous traiterez un évènement, vous pourrez toujours savoir quel est l'objet initiateur du traitement.
Malgré ces quelques explications, sachez que pour traiter les évènements vous devrez, au moins, importer deux packages : java.awt.* et java.awt.event.*;
Traitement d'évènements simples
Nous allons maintenant voir comment savoir qu'un évènement à eut lieu, et comment allons nous pouvoir y répondre. Pour cela, il nous faut définir un écouteur, puis enregistrer ce dernier sur un objet susceptible de déclencher un évènement. Ainsi, si l'évènement apparaît, il pourra être traité par l'écouteur.
Définition d'un écouteur
Dans le modèle Java de traitement d'évènements, il nous faut définir un écouteur. Un écouteur est un objet qui possède au moins une méthode qui pourra être invoquée si l'évènement attendu apparaît. En fait, un écouteur doit remplir un contrat bien particulier. Dan cet objectif, il se doit d'implémenter un comportement (la plupart du temps en implémentant une interface, ou bien en dérivant de certaines classes).
Il existe plusieurs types d'écouteurs, donc plusieurs types d'interfaces. Toutes ces interfaces ont un nom se terminant par Listener (par exemple ActionListener, WindowListener, ...). Selon l'interface, il y a une ou plusieurs méthodes à implémenter.
A titre d'exemple, considérons l'évènement ActionEvent : cet évènement est déclenché dès lors que l'on action un bouton (par exemple) soit en cliquant dessus avec la souris soit en utilisant le clavier. Pour être capable d'écouter et de traiter un tel évènement, un objet écouteur se doit d'implémenter une interface nommée ActionListener. Si vous êtes curieux et que vous étudier l'interface ActionListener, vous remarquerez qu'elle ne contient qu'une unique méthode à implémenter : actionPerformed. Cette méthode doit donc contenir le code à exécuter si jamais vous cliquez sur votre bouton.
import java.awt.*;
import java.awt.event.*;
public class PushMe implements ActionListener {
static public void main(String argv[]) {
new PushMe();
}
// Le constructeur de la classe PushMe
public PushMe() {
Frame f = new Frame("Ma fenêtre");
Button b = new Button("Push me");
f.add(b); // On ajoute le bouton dans la fenêtre
f.pack();
f.setVisible(true);
}
// gestionnaire d'évènement définit dans ActionListener
public void actionPerformed(ActionEvent event) {
System.out.println("Bouton " + event.getSource() + " cliqué !");
}
} |
Cependant, si vous exécutez ce code, rien ne se passe. Pourquoi ?
Enregistrement d'un écouteur
Le problème est simple. On à définit un écouteur (l'objet de classe PushMe) susceptible de répondre à un click sur le bouton. Mais qui à dit au bouton, qu'il fallait avertir l'écouteur, si on lui cliquait dessus ? Absolument personne : le problème est que chaque écouteur doit être enregistré auprès des objets qu'il est censé écouter (un écouteur peut écouter plusieurs sources d'évènements - une source d'évènement peut alerter plusieurs écouteurs). L'exemple suivant enregistre l'écouteur à une source d'évènements.
import java.awt.*;
import java.awt.event.*;
public class PushMe implements ActionListener {
static public void main(String argv[]) {
new PushMe();
}
// Le constructeur de la classe PushMe
public PushMe() {
Frame f = new Frame("Ma fenêtre");
Button b = new Button("Push me");
b.addActionListener(this);
f.add(b); // On ajoute le bouton dans la fenêtre
f.pack();
f.setVisible(true);
}
// gestionnaire d'évènement définit dans ActionListener
public void actionPerformed(ActionEvent event) {
System.out.println("Bouton " + event.getSource() + " cliqué !");
}
} |
Maintenant, vous pouvez cliquer sur le bouton. Un message est affiché en confirmation sur la console. Notez que l'objet source de l'évènement est aussi affiché sur la console.
Qu'a permit de faire la méthode addActionListener ? En fait c'est simple, elle à simplement permit de rajouter un écouteur au bouton. A chaque fois que le bouton est actionné, il cherche maintenant à invoquer la méthode actionPerformed sur chacun de ses écouteurs. ActionListener pouvant être utilisé comme un type, le compilateur ne dit rien et tout fonctionne.
Cependant, l'exemple du code précédent peut se réécrire différemment : en effet un objet d'écoute peut être créé exclusivement dans ce but. Et comme les générateurs d'interfaces graphiques utilisent ces autres mécanismes, regardons les de plus près.
Définition de classes privées
Une première solution consiste à définir des classes privées simplement dédiées au traitement des l'évènements. Deux façons de coder peuvent être retenue.
import java.awt.*;
import java.awt.event.*;
public class PushMe {
static public void main(String argv[]) {
new PushMe();
}
// Le constructeur de la classe PushMe
public PushMe() {
Frame f = new Frame("Ma fenêtre");
Button b = new Button("Push me");
b.addActionListener(new Handler());
f.add(b); // On ajoute le bouton dans la fenêtre
f.pack();
f.setVisible(true);
}
}
class Handler implements ActionListener {
// gestionnaire d'évènement définit dans ActionListener
public void actionPerformed(ActionEvent event) {
System.out.println("Bouton " + event.getSource() + " cliqué !");
}
} |
import java.awt.*;
import java.awt.event.*;
public class PushMe {
static public void main(String argv[]) {
new PushMe();
}
// Le constructeur de la classe PushMe
public PushMe() {
Frame f = new Frame("Ma fenêtre");
Button b = new Button("Push me");
b.addActionListener(new Handler());
f.add(b); // On ajoute le bouton dans la fenêtre
f.pack();
f.setVisible(true);
}
private class Handler implements ActionListener {
// gestionnaire d'évènement définit dans ActionListener
public void actionPerformed(ActionEvent event) {
System.out.println("Bouton " + event.getSource() + " cliqué !");
}
}
} |
Définition de classes anonymes
Cette seconde méthode, un peu déroutante au démarrage, permet malgré tous une écriture simplifiée de votre programmes. Je vous conseil de bien l'assimiler, dans le sens ou elle est fréquemment utilisées. En fait, c'est très simple : la méthode addActionListener à besoin d'un objet de type ActionListener, on en dérive donc une classe anonyme qui redéfinit quelques méthodes (ici une) est qui directement utilisée pour créer un unique objet d'écoute.
import java.awt.*;
import java.awt.event.*;
public class PushMe {
static public void main(String argv[]) {
new PushMe();
}
// Le constructeur de la classe PushMe
public PushMe() {
Frame f = new Frame("Ma fenêtre");
Button b = new Button("Push me");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.out.println("Bouton cliqué !");
}
});
f.add(b); // On ajoute le bouton dans la fenêtre
f.pack();
f.setVisible(true);
}
} |
Notez bien qu'une telle classe anonyme ne peut servir qu'à générer un unique objet. Dans ce cas, on associe un écouteur à une unique source d'évènements.
Les classes d'adaptation
Dans les exemples précédents, les choses étaient simples : l'interface utilisée ne définie qu'une seule méthode. Mais en est-il de même pour les autres types d'évènements ? Et bien non. Les évènements de fenêtre en sont un bon exemple : en effet une seule interface est définie pour tous ce qui peut arriver sur une fenêtre (ouverture de fenêtre, fermeture, iconification, ...). Au total, ce sont sept méthodes qui y sont définies. Or, n'oublions pas qu'en implémentant une interface, vous vous devez d'implémenter chacune de ses méthodes (sans quoi votre classe est abstraite) et ce même si certaines ne vous sont pas utile (vous ne vouliez réagir qu'à la fermeture de la fenêtre).
Une solution intéressante est de coupler les mécanismes de classe anonyme et de classe adaptation. Une classe d'adaptation est une classe qui implémente une interface en fournissant, pour chaque méthode, un corps vide. L'intérêt est évident : si vous avez besoin de fournir du code pour une unique méthode de l'interface, vous ne devrez donner, dans la définition de la classe anonyme, que le code ce cette méthode. Pour les autres, elles sont déjà implémentées, même si leur code est inexistant (un corps vide { }).
Une petite remarque : toutes les classes d'adaptation ont sensiblement le même nom que leur interface associée. Il suffit de remplacer le mot 'Listener' par 'Adapter'. Ainsi l'interface WindowListener possède une classe associée, nommée WindowAdapter. Autre remarque, si une interface du JDK ne possède qu'une seule méthode abstraite, aucune classe d'adaptation n'y est rattachée. Le petit exemple suivant illustre cette notion en proposant une méthode chargée de tuer l'application si vous cliquez sur le bouton de fermeture de la fenêtre.
import java.awt.*;
import java.awt.event.*;
public class PushMe {
static public void main(String argv[]) {
new PushMe();
}
// Le constructeur de la classe PushMe
public PushMe() {
Frame f = new Frame("Ma fenêtre");
Button b = new Button("Push me");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.out.println("Bouton cliqué !");
}
});
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent event) {
System.exit(0);
}
});
f.add(b); // On ajoute le bouton dans la fenêtre
f.pack();
f.setVisible(true);
}
} |
Types d'évènements supportés par l'AWT et Swing
Pour clore ce chapitre, nous allons présenter quelques autres types d'évènements que vous pourriez traiter : la liste n'est bien entendu pas exhaustive ! Commençons par la gestion du clavier : à tout composant (objet de classe Component), vous pouvez enregistrer un KeyListener. Toute classe implémentant cette interface se doit de fournir le code de trois méthodes : keyPressed, keyReleased et keyTyped. Ces trois dernières méthodes doivent prendre en paramètre un objet de classe KeyEvent.
Une autre interface utile à implémenter est MouseListener. Elle définit cinq méthodes prenant toutes en paramètre en objet de classe MouseEvent. En voici leurs noms : mouseClicked, mouseEntered, mouseExited, mousePressed et mouseReleased. Bien entendu, tout objet de l'AWT peut enregistrer un MouseListener.
Dans la volée, nous pouvons aussi parler de l'interface FocusListener : elle est utilisée pour répondre à la prise, ou à la perte, du focus par un élément de l'AWT. Cette interface définie deux méthodes : focusGained et focusLost. Ces deux méthodes requièrent en unique paramètre un objet de classe FocusEvent.
Enfin, parlons aussi de l'interface TextListener qui permet de répondre à un changement de contenu d'une zone de saisie de texte. Cette interface défini une unique méthode : textValueChanged. Un TextListener s'enregistre après d'un TextComponent (dont dérivent les classes TextArea et TextField). La classe d'évènement associée est, bien entendu, TextEvent.
Pour plus d'informations sur les éléments présentés dans ce chapitre (et ceux qui ne l'ont pas été), reportez-vous à la documentation de l'API du JDK.
|