La programmation événementielle

Chapitres traités   

La gestion des événements est d'une importance capitale pour les programmes ayant une interface utilisateur graphique. En effet, en mode graphique, ce n'est plus une programmation déterministe, qui impose donc un fonctionnement séquentiel, mais plutôt une programmation qui propose un ensemble d'actions spécifiques, au moment où l'utilisateur le désire et en rapport avec des événements particuliers, comme par exemple le clic d'un bouton.

Cette étude explique le fonctionnement du modèle d'événement AWT. Vous apprendrez à capturer des événements en provenance de la souris et du clavier, ainsi que l'usage des éléments les plus simples d'une interface, comme les boutons. Nous verrons en particulier comment utiliser les événements de base générés par ces composants.

Pour appréhender ce cours, il est nécessaire de maîtriser la notion des interfaces, mais aussi l'utilisation des classes inernes et des classes anonymes. Revoir les interfaces. Revoir les classes internes et anonymes.

Choix des chapitresIntroduction à la gestion des événements

Tout système d'exploitation qui supporte des interfaces graphiques doit constamment surveiller l'environnement afin de détecter des événements tels que la pression sur une touche du clavier ou sur un bouton de la souris. Le système d'exploitation en informe alors les programmes en cours d'exécution. Chaque programme détermine ensuite s'il doit répondre à ces événements.

La programmation événementielle constitue la caractéristique essentielle d'une interface graphique. La plupart des événements sont créés par des composants que nous auront introduit dans la fenêtre, comme les menus, les boutons, les boîtes de dailoques, etc. Nous allons ainsi voir comment traiter les événements qu'ils génèrent.

L'environnement de programmation Java a choisi une approche particulière de la gestion événementielle. En effet, dans les limites des événements connus d'AWT, vous contrôlez complètement la manière dont les événements sont transmis de la source (par exemple, un bouton ou une barre de défilement) à l'écouteur (celui qui va capturer et gérer l'événement).

Vous pouvez désigner n'importe quel objet comme écouteur d'événement, c'est ce qui fait d'ailleurs la particularité de Java. Dans la pratique, vous choisirez un objet écouteur qui soit capable de fournir une réponse appropriée à l'événement.

Les sources d'événement possèdent des méthodes addXXXListener() qui leur permettent d'enregistrer (ou de recenser) les écouteurs d'événement sélectionnés XXXListener. Lorsqu'un événement arrive à la source, celle-ci envoie une notification à tous les objets écouteurs recensés pour cet événement.

Bien entendu, dans un langage orienté objet comme Java, l'information relative à l'événement est encapsulée dans un objet événement. Tous les objets événement dérivent, directement ou indirectement, de la classe java.util.EventObject. Il existe évidemment des sous-classes pour chaque type d'événement, comme ActionEvent et WindowEvent.

Pour résumer, voici en gros comment les événements sont gérer par l'AWT :

  1. Un objet écouteur est une instance d'une classe qui implémente une interface spéciale appelée interface écouteur (listener interface).
  2. Une source d'événement est un objet qui est capable de recenser des objets écouteurs et leur envoyer des objets événements.
  3. Lorsqu'un événement se produit, la source d'événement envoie l'objet événement à tous les écouteurs recensés.
  4. Les objets écouteurs utilisent alors l'information contenue dans l'objet événement pour déterminer leur réponse.

Pour recenser l'objet écouteur auprès de l'objet source, utiliser une instruction construite sur ce modèle :

ObjetSourceEvénement.addEvénementListener(objetEcouteurEvénement)

Voici un exemple :

ActionListener écouteur = ...;
JButton bouton = new JButton("Ok");
bouton.addActionListener(écouteur);

L'exemple précédent exige que la classe à laquelle appartient l'objet écouteur implémente l'interface appropriée (en l'occurence, ActionListener). Comme pour toutes les interfaces Java, l'implémentation d'une interface signifie qu'il faut redéfinir les méthodes prévues (respectant ainsi le contrat) avec la signature correcte. Ainsi, pour implémenter l'interface ActionListener, la classe de l'écouteur doit posséder une méthode nommée actionPerformed() qui recevra l'objet ActionEvent comme paramètre :
class Ecouteur implements ActionListener {
   ...
   public void actionPerformed(ActionEvent événement) {
       // traitement particulier à la réaction d'un clic sur le bouton
       ...
   }
}

Chaque fois que l'utilisateur clique sur le bouton, l'objet bouton de type JButton crée un objet événement de type ActionEvent et appelle la méthode écouteur.actionPerformed(événement) en lui passant cet objet. Il est possible d'ajouter plusieurs objets en tant qu'écouteurs d'une source d'événement, par exemple un bouton. Dans ce cas, le bouton appelle les méthodes actionPerformed() de tous les écouteurs, chaque fois que l'utilisateur clique sur ce bouton.

 

Choix du chapitre Apprentissage au travers de deux petits exemples

Afin d'illustrer cette entrée en matière, je vous propose de mettre en oeuvre deux petits programmes, un qui permet de prendre en compte un événement de type Action, et un autre qui gère les événements liés à la souris.

Interface ActionListener

Dans un premier temps, et en reprenant l'exemple du clic sur un bouton avec un écouteur de type ActionListener, nous allons mettre en oeuvre le petit programme que nous avons déjà vu, en version simplifiée, qui réalise la conversion entre des €uros et des francs :

 1 package conversion;
 2 
 3 import java.awt.*;
 4 import java.awt.event.*;
 5 import javax.swing.*;
 6 
 7 public class Conversion extends JFrame implements ActionListener {
 8    private JTextField saisie = new JTextField("0");
 9    private JButton conversion = new JButton("Conversion");
10    private JLabel résultat = new JLabel("0 Franc");
11    private JPanel panneau = new JPanel();
12    
13    public Conversion() {
14       setTitle("Conversion €uros -> Francs");
15       setBounds(100, 100, 280, 80);
16       setDefaultCloseOperation(EXIT_ON_CLOSE);
17       panneau.setLayout(new BorderLayout());
18       saisie.setHorizontalAlignment(JTextField.RIGHT);
19       panneau.add(saisie);
20       conversion.addActionListener(this);
21       panneau.add(conversion, BorderLayout.EAST);
22       add(panneau, BorderLayout.NORTH);
23       résultat.setHorizontalAlignment(JLabel.RIGHT);  
24       résultat.setBorder(BorderFactory.createEtchedBorder());
25       add(résultat, BorderLayout.SOUTH);
26       getContentPane().setBackground(Color.GREEN);
27       setResizable(false);
28       setVisible(true);
29    }
30    
31    public static void main(String[] args) {
32       new Conversion();  
33    }
34 
35    public void actionPerformed(ActionEvent e) {
36        final double TAUX = 6.55957;
37        double €uro = Double.parseDouble(saisie.getText());
38        double franc = €uro * TAUX;
39        résultat.setText(franc+" Francs");
40    }
41 }
42 

L'interface ActionListener utilisée dans notre exemple n'est pas limitée aux clics sur des boutons. Elle peut être utilisée dans bien d'autres situations, par exemple :

  1. Lors de la sélection d'un élément de menu.
  2. Lors de la sélection d'un élément dans une zone de liste à l'aide d'un double-clic.
  3. Lorsque la touche "Entrée" est activée dans un champ de texte. A titre d'exemple, dans le code précédent, vous pouvez rajouter cette ligne :

    saisie.addActionListener(this);
  4. Lorsqu'un composant Timer déclenche une impulsion après l'écoulement d'un temps donné.

En résumé, et quel que soit le contexte, ActionListener s'utilise de la même manière dans tous les situations que nous venons d'évoquer ; sa méthode (unique) actionPerformed() reçoit en paramètre un objet de type ActionEvent. Cet objet fournit des informations sur l'événement qui a été déclenché.

La question qui se pose constamment, c'est le choix de l'écouteur. Java est très souple à ce sujet. Vous pouvez créer vos propres classes ou utiliser celles qui sont déjà présentes. Le tout, c'est que la ou les méthodes relatives aux événements puissent réaliser le traitement souhaité. Ici, il est indispensable de pouvoir atteindre à la fois l'objet saisie et l'objet résultat, puisque le traitement à réaliser est en relation avec ces deux éléments. Vu que nous avons qu'un seul bouton qui provoque l'événement, il me paraît judicieux que ce soit la fenêtre elle-même qui soit écouteur de ce type d'événement puisqu'elle dispose des éléments que nous venons d'évoquer et qui vont donc servir au traitement. Il est tout à fait possible également de créer une nouvelle classe spécifique à la gestion de cet événement mais, pour qu'elle puisse accéder aux objets saisie et résultat, il faut que ce soit impérativement une classe interne. Ce sujet sera traiter ultérieurement.

Interface MouseListener

Voyons également comment traiter l'événement que constitue un clic sur une surface (objet issu de JPanel) qui se trouve sur la zone principale de la fenêtre. Nous nous contenterons de signaler l'événement en affichant les coordonnées de la souris sur la partie basse de la fenêtre (coordonnées objet de la classe Coordonnées qui hérite de JLabel).

Nous allons appliquer la même démarche que précédemment en recensant tous les éléments nécessaires à la gestion complète de l'événement choisi, savoir :

  1. En java, je le rappelle, tout événement possède ce que l'on nomme une source. Il s'agit de l'objet lui ayant donné naissance, comme un bouton, un article de menu, une fenêtre, un panneau, etc. Dans notre exemple, cette source est la surface de travail.
  2. Pour traiter un événement, nous devons associer à la source un objet de son choix dont la classe implémente une interface particulière correspondant à une catégorie d'événements. On dit que cet objet est un écouteur de cette catégorie d'événements. Chaque méthode proposée par l'interface correspond à un événement particulier de la catégorie choisie.
  3. Ainsi, il existe une catégorie d'événements souris que nous pouvons traiter à l'aide d'un écouteur de souris, c'est à dire un objet d'une classe implémentant l'interface MouseListener. Cette dernière comporte cinq méthodes correspondant chacune à un événement particulier :
    1. mousePressed() : appuie sur un bouton de la souris.
    2. mouseReleased() : relâchement de l'action sur un bouton de la souris.
    3. mouseClicked() : clic sur un bouton de la souris qui correspond en réalité à un appuie suivie d'un relâchement.
    4. mouseEntered() : le curseur de la souris passe au dessus de l'élément source.
    5. mouseExited() : le curseur sort de la zone prise par l'élément source.
  4. Une classe susceptible d'instancier un objet écouteur de ces différents événements devra donc correspondre à ce schéma :

L'événement qui nous intéresse ici correspond à un clic usuel (appui suivi de relâchement, sans déplacement). Nous devrons donc proposer une classe écouteur qui redéfinie la méthode mouseClicked() afin de réaliser le traitement souhaité. Mais, comme notre classe doit implémenter l'interface MouseListener, vous remarquez qu'elle doit également redéfinir toutes les autres méthodes (contrat à respecter par rapport à une interface). Nous pouvons toutefois nous permettre de ne rien faire de particulier pour toutes ces méthodes supplémentaires non utiles pour notre application. La définition de ces méthodes sera donc "vide".

Pour traiter un clic de la souris dans la surface de travail de la fenêtre, il suffit d'associer à notre surface un objet d'un type tel que EcouteurSouris. Pour ce faire, nous utilisons la méthode addMouseListener() (le nom de la méthode est évocateur puisque qu'il est associé au type d'écouteur - add+MouseListener) :

sourceEvénement.addMouseListener(objetEcouteur);

dans laquelle objetEcouteur est un objet d'une classe du type EcouteurSouris dont nous venons de fournir le schéma et sourceEvénement correspond à notre surface de travail.

Voici le code source correspond à notre analyse :

Encore une fois, je propose un écouteur sur un composant graphique. Toutefois, je crée une nouvelle classe qui hérite d'un JLabel et qui implémente donc l'interface MouseListener. L'intérêt ici, c'est de pouvoir utiliser la méthode setText() inhérente d'un JLabel et de proposer ainsi l'affichage des coordonnées de la souris

Choix de l'objet écouteur

Comme je l'ai déjà évoqué, Java se montre très souple puisque l'objet écouteur peut être n'importe quel objet dont la classe implémente l'interface voulue. Dans une situation aussi simple qu'ici, nous pouvons même ne pas créer de classe séparée telle que Coordonnées en faisant de la fenêtre elle-même son propre écouteur d'événement souris. Notez que cela est possible car la seule chose que nous demandons à un objet écouteur est que sa classe implémente l'interface voulue (ici MouseListener).

Utilisation de l'information associé à un événement

Préoccupons nous maintenant de l'arguement transmis à la méthode mouseClicked(). Ici, il s'agit d'un objet de type MouseEvent. Cette classe correspond en fait à la catégorie d'événements gérés par l'interface MouseListener. Un objet de cette classe est automatiquement créé par Java lors du clic, et transmis à l'écouteur voulu. Il contient un certain nombre d'informations, en particulier les coordonnées du curseur de la souris au moment du clic, lesquelles sont accessibles, respectivement, par les méthodes getX() et getY().

 

Choix du chapitre La notion d'adaptateur

Dans les exemples précédents, nous n'avions besoin que d'une seule méthode, la méthode mouseClicked(). Toutefois, pour respecter le contrat prévu, nous avons dû fournir des définitions vides pour toutes les autres méthodes requises afin d'implémenter correctement l'interface MouseListener.

Une classe qui implémente une interface doit obligatoirement tenir la promesse de définir chacune des méthodes présentes dans l'interface.
.

il est fastidieux d'écrire des signatures de quatre méthodes qui ne font rien. Pour simplifier la tâche du programmeur, chacune des interfaces AWT possédant plusieurs méthodes est accompagnée d'une classe adaptateur qui implémente toutes les méthodes de l'interface en leur attribuant des instructions vides. Par exemple, la classe MouseAdapter possède cinq méthodes qui ne font rien.



Cela signifie que la classe adpatateur satisfait automatiquement aux exigences techniques imposées par Java pour l'implémentation de l'interface écouteur qui lui est associée. Nous pouvons ainsi étendre la classe adaptateur afin de spécifier les réactions souhaités pour certains événements, mais sans avoir besoin de répondre explicitement à tous les événements de l'interface.

Toutefois, une interface comme ActionListener, qui ne possède qu'une seule méthode, n'a pas besoin de classe adaptateur.
.

Profitons de cette caractéristique et utilisons l'adaptateur de la souris. Nous pouvons ainsi étendre la classe MouseAdapter, héritant ainsi de cinq méthodes qui ne font rien, et nous contenter de surcharger la méthode mouseClicked() :

Voici un schéma récaptitulatif montrant comment utiliser cette technique pour n'écouter, à l'aide d'un objet d'une classe EcouteurSouris, que les clics complets générés par une fenêtre (la classe MaFenêtre devient source des événements) :

Cependant, si l'on procède ainsi, les deux classes MaFenêtre et EcouteurSouris sont indépendant. Dans certains programmes, on préferera que la fenêtre concernée soit son propre écouteur. Dans ce cas, un petit problème se pose : la classe fenêtre correspondante ne peut pas dériver à la fois de JFrame et de MouseAdapter. C'est là que la notion de classe anonyme prend tout son intérêt. Il suffit en effet de remplacer le canevas précédent par le suivant :

Ici, nous créons un objet d'un type classe anonyme dérivée de MouseAdapter et dans laquelle nous redéfinissons de façon appropriée la méthode mouseClicked(). Du coup, voilà comment transformer notre programme précédant pour tenir compte de toutes ces considérations :

 

Choix des chapitres La gestion des événements en général

Nous venons de voir comment un événement, déclenché par un objet nommé source, pouvait être traité par un autre objet nommé écouteur préalablement associé à la source. Tout ce qui a été exposé ici, sur deux exemples simples, se généralisera aux autres événements, quels qu'ils soient et quelle que soit leur source.

En particulier, nous associerons toujours un objet écouteur à un événement d'une catégorie donnée ##Listener par une méthode add##Listener(). Chaque fois qu'une catégorie donnée disposera de plusieurs méthodes, nous pourrons :

  1. soit redéfinir toutes ces méthodes, certaines ayant éventuellement un corps vide,
  2. soit faire appel à une classe dérivée d'une classe adaptateur - ##Adapter - et ne fournir que les méthodes qui nous intéressent.

L'objet écouteur pourra être n'importe quel objet de votre choix ; en particulier, il pourra s'agir de l'objet source lui-même. Enfin, bien que nous n'ayons pas rencontré ce cas jusqu'ici, sachez qu'un même événement peut tout à fait disposer de plusieurs écouteurs.

  1. Les sources d'événement sont des composants de l'interface utilisateur, des fenêtres et des menus.
  2. Le système d'exploitation notifie à une source d'événement, les activités qui peuvent l'intéresser, comme les mouvements de la souris ou les frappes du clavier.
  3. La source d'événement décrit la nature de l'événement dans un objet événement - ##Event. Elle stocke également une liste d'écouteur - ##Listener - des objets qui souhaitent être prévenus quand l'événement se produit.
  4. La source d'événement appelle la méthode appropriée de l'interface écouteur afin de fournir des informations sur l'événement aux divers écouteurs recencés. Pour cela, la source passe l'événement objet adéquat à la méthode de la classe écouteur.
  5. L'écouteur analyse l'objet événement pour obtenir de informations détaillées. Par exemple, nous pouvons utiliser la méthode getSource() pour connaître la source, ou les méthodes getX() et getY() de la classe MouseEvent pour connaître la position courante de la souris.

 

Choix du chapitre Choix de l'objet écouteur

Nous verrons par la suite qu'il existe toute sorte d'événements. Avant de les découvrir, j'aimerais que nous nous consacrions de nouveau sur le choix de ou des écouteurs. Nous avons déjà eu une petite approche, mais jusqu'à présent, nous n'avions qu'une seule source d'événement. Que se passe-t-il si nous devons gérer plusieurs sources d'événement pour une même destination (traitements différents pour le même composant) ?

En réalité, beaucoup de solutions sont envisageables. Nous allons tenter d'en découvrir quelles unes au travers d'un même exemple. Nous allons effectivement mettre en oeuvre un programme qui permet de changer la couleur de la surface de travail de la fenêtre de votre application en cliquant sur des boutons adaptés - ou en appuyant sur la touche Espace du clavier lorsque le bouton choisi comporte le focus d'entrée ; c'est le cas sur cet exemple avec le bouton Cyan (Rectangle bleu). Nous nous servons encore une fois de l'interface ActionListener qui correspond à notre désir et qui comporte une seule méthode actionPerformed(). Dans ce cas de figure, l'objet notifié est un ActionEvent. Quelque soit l'exemple étudié, les sources des événements seront toujours les deux boutons.

La surface de travail de la fenêtre est l'écouteur des actions proposées sur les deux boutons

Pour ce premier exemple, la surface de travail panneau qui est issue de la classe Panneau est à l'écoute de deux événements possibles qui correspondent aux actions sur les boutons boutonCyan et boutonMagenta. Nous utilisons ensuite la méthode getActionCommand() pour récupérer le libellé du bouton qui a provoqué l'événement et ainsi pour proposer la couleur correspondante.

Le choix de cette méthode getActionCommand() n'est pas des plus heureux. En effet, pour une raison quelconque, nous pouvons changer le texte du libellé du bouton. Dans ce cas là, les événements seraient alors mal gérés. Eviter, si possible, d'utiliser cette démarche.

La fenêtre est l'écouteur des actions proposées sur les deux boutons

Cette fois-ci, c'est la fenêtre de l'application qui fait office d'écoute des événements proposés par l'action sur l'un des deux boutons. Il n'est pas nécessaire, dans ce cas là, de fabriquer une classe spéciale Panneau. Puisque depuis la fenêtre, il est possible de connaître les boutons qui sont à l'origine de l'action, il suffit de récupérer les objets représentatif grâce à la méthode getSource().

Chacun des boutons est à la fois source et écouteur

Nous allons cette fois-ci créer une classe abstraite Bouton qui hérite de JButton et qui implémente l'interface ActionListener, ce qui sous-entend que nous créons un bouton qui est à la fois la source et écouteur de son propre événement ActionEvent. Nous allons ensuite créer chaque bouton - par le système des classes anonymes - qui implémentera cette classe et qui proposera en conséquence l'événement correspondant. La classe Bouton est abstraite puisque à son niveau, il n'est pas encore possible de redéfinir la méthode actionPerformed().

L'écouteur est totalement indépendant de tout composant graphique

Cette fois-ci, nous créons un écouteur de toute de pièce, indépendamment de tout composant existant. Dans ce cas de figure, il n'existe aucun héritage. Dans cet exemple, ce choix ne me paraît pas judiceux. Toutefois, certaines situations peuvent nécessiter de fabriquer un écouteur sans héritage.

L'écouteur est toujours indépendant de tout composant graphique, mais c'est aussi une classe interne

En java, il est possible de déclarer une classe à l'intérieur d'une autre. Même si cette technique n'est pas fréquente, elle présente l'avantage de donner la possiblité d'accéder aux attributs de la classe conteneur depuis la classe interne. C'est une technique qu'il ne faut pas avoir peur d'utiliser. Elle offre effectivement beaucoup de souplesses.

Peut-être la meilleure solution - le bouton un objet interne qui est à la fois source et écouteur

Pour finir, je vous propose le code source suivant, qui allie les différentes opportunités, en prenant les avantages de chacune des techniques envisagées. Le meilleurs choix est souvent la classe interne puisqu'elle accède à tous les éléments de la classe conteneur. Egalement, le fait de travailler avec le composant source, vous pouvez faire appel, une fois pour toute dans le constructeur, à la méthode addXXXListener(). Enfin, le traitement est simplifié par l'ajout d'un attribut interne qui prend en compte la valeur à transmettre pour l'action à lancer. Encore une fois, le fait d'avoir une classe interne permet de réaliser le traitement sur l'élément souhaité.

package événement;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Fenêtre extends JFrame {
   private Bouton cyan = new Bouton("Cyan", Color.CYAN);
   private Bouton magenta = new Bouton("Magenta", Color.MAGENTA);
   private JPanel panneau = new JPanel();
   
   public Fenêtre() {
      super("Les événements");
      setSize(300, 250);
      setLocation(50, 20);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      panneau.add(cyan);
      panneau.add(magenta);
      add(panneau, BorderLayout.SOUTH);
      setVisible(true);
   }
   
   public static void main(String[] args) {
      new Fenêtre();        
   }
   
   private class Bouton extends JButton implements ActionListener {
      private Color couleur;

      public Bouton(String libellé, Color couleur) {
         super(libellé);
         this.couleur = couleur;
         addActionListener(this);
      }

      public void actionPerformed(ActionEvent e) {
         getContentPane().setBackground(couleur);
      }     
   }
}

Imaginez qu'au lieu de deux boutons, vous en ayez cinq. Le fait de factoriser dans une seule classe interne, tout ce qui correspond à la gestion des événements, est vraiment avantageux.

Il existe bien d'autres possibilités. Il suffit d'avoir un peu d'imagination. Java est un langage qui offre beaucoup de souplesse.
.

 

Choix du chapitre Recensement des différents types d'événement

Maintenant que nous connaissons toute la technique concernant la gestion des événements, nous allons recenser l'ensemble des événements que Java propose. Tous les événements utilisés par les composants graphiques de Swing sont des classe filles de java.util.EventObject. Un objet événement encapsule des informations sur l'événement que la source d'événement communique aux écouteurs, comme nous l'avons fait à l'aide des méthodes getSource() et getActionCommand() dans les exemples précédents.

Evénements sémantiques et de bas niveau

AWT fait une distinction utile entre événements de bas niveau et événements sémantiques :

  1. Un événement sémantique exprime ce que fait l'utilisateur, par exemple "Cliquer sur un bouton" ; en conséquence, un événement ActionEvent est sémantique.
  2. Les événements de bas niveau sont ceux qui rendent l'action possible. Dans le cas d'un clic sur un bouton de l'interface utilisateur, cela représente en réalité une pression sur le bouton de la souris, des déplacements du pointeur de la souris, puis un relâchement du bouton de la souris (mais seulement si le pointeur se trouve encore dans la zone du bouton affiché à l'écran). Ce peut être également une pression sur une touche du clavier, au cas où l'utilisateur sélectionne le bouton avec la touche de tabulation puis active la barre d'espacement.

De la même manière, un ajustement de la position d'une barre de défilement est un événement sémantique, mais le déplacement de la souris est un événement de bas niveau.

Classes d'événements sémantiques les plus utilisés dans le paquetage java.awt.event
  1. ActionEvent : pour un clic de bouton, une sélection d'un élement de menu ou de liste, ou une pression de la touche "Entrée" dans un champ de texte.
  2. AdjustementEvent : l'utilisateur déplace le curseur d'une barre de défilement.
  3. ItemEvent : l'utilisateur fait une sélection dans un groupe de cases à cocher ou dans une liste.
Classes d'événement de bas niveau
  1. KeyEvent : une touche du clavier est enfoncée ou relâchée.
  2. MouseEvent : le bouton de la souris est enfoncée ou relâchée ; le pointeur de la souris se déplace ou glisse.
  3. MouseWheelEvent : la mollete de la souris tourne.
  4. FocusEvent : un composant obtient le focus.
  5. WindowsEvent : l'état de la fenêtre change.

Tous les événements de bas niveau héritent de ComponentEvent. Cette classe dispose d'une méthode, nommée getComponent(), qui indique le composant ayant généré l'événement ; vous pouvez employer getComponent() à la place de getSource(). En effet, la méthode getComponent() renvoie la même valeur que getSource(), mais l'a déjà transtypée en Component. Par exemple, si un événement clavier a été déclenché à la suite d'une frappe dans un champ de texte, getComponent() renvoie une référence à ce champ de texte.

getComponent() <=> (Component)getSource()

Ecouteurs d'événement

Les interfaces suivantes permettent d'écouter ces événements :

  1. ActionListener
  2. AjustementListener
  3. FocusListener
  4. ItemListener
  5. KeyListener
  6. MouseListener
  7. MouseMotionListener
  8. MouseWheelListener
  9. WindowsListener
  10. WindowsFocusListener
  11. WindowsStateListener

Vous remarquez la présence de deux interfaces séparées pour la souris, pour des raisons d'efficacité : MouseListener et MouseMoveListener. La deuxième est plutôt spécialisée au mouvement de la souris. Il se produit de nombreux événements lorsque l'uitlisateur déplace la souris. Un écouteur qui s'intéresse uniquement aux clics de la souris ne doit pas être inutilement prévenu de tous les déplacements de la souris.

Classes adaptateur

Plusieurs interfaces écouteur AWT - celles qui possèdent plusieurs méthodes - sont accompagnées d'une classe adaptateur qui implémente toutes les méthodes de l'interface afin qu'elles n'accomplissent aucune action (les autres interfaces n'ont qu'une seule méthode et il est donc inutile d'employer des classes adaptateur dans ce cas).

Voici les classes adaptateur les plus souvent utilisées :

  1. FocusAdapter
  2. KeyAdapter
  3. MouseAdapter
  4. MouseMotionAdapter
  5. WindowAdapter

Il faut manifestement connaître un grand nombre de classes et d'interfaces - ce qui peut paraître insurmontable à première vue. Heureusement, le principe est simple. Une classe qui désire recevoir des événements doit impléménter une interface écouteur. Elle se recense auprès de la source d'événement, puis elle reçoit les événements souhaités et les traite grâce aux méthodes de l'interface écouteur.

Ensemble des éléments employés pour la gestion des événements

Nous allons ici recenser les objets événements les plus utilisés avec leurs méthodes associées suivi des interfaces qui les prennent en compte pourvues également de méthodes spécifiques aux différents traitements souhaités.

Objets événements
EventObject
Généré par tous les composants.
Objet parent de tous les objets événement.
getSource() : Renvoie une référence sur l'objet qui a déclenché l'événement.
InputEvent
Généré par tous les composants.
Objet parent de KeyEvent et de MouseEvent.
isAltDown(), isControlDown(), isMetaDown(), isShiftDown() : Ces méthodes renvoient true si la touche de modification correspondante est pressée lorsque l'événement est généré.
FocusEvent
Généré par Component.
Un composant a obtenu ou perdu le focus.
isTempory() : indique si le changement de focus est temporaire ou permanent.
getOppositeComponent() : Renvoie le composant qui a perdu le focus dans le gestionnaire focusGained ou le composant qui a obtenu le focus dans le gestionnaire focusLost.
ComponentEvent
Généré par tous les composants.
Tous les événements de bas niveau héritent de cette classe.
getComponent() : indique le composant ayant généré l'événement.
ActionEvent
Généré par AbstractButton, JComboxBox, JTextField, Timer.
Pour un clic de bouton, une sélection d'un élément de menu ou de liste, une pression de la touche Entrée dans un champ de texte.
getActionCommand()
: renvoie la chaîne de commande associée à cet événement d'action. Si cet événement est déclenché par un bouton, la chaîne de commande contient le libellé du bouton, à moins qu'il n'ait été modifié par la méthode setActionCommand().
getModifiers()
: renvoie une valeur qui indique les modificateurs clavier - alt, ctrl, shift - qui sont effectifs quand l'événement de d'action est déclenché.
AdjustementEvent
Généré par JScrollBar.
L'utilisateur a déplacé le curseur d'une barre de défilement.
getAjustable() : retourne l'objet qui a provoqué l'événement.
getAdjustementType() : indique comment se déroule le défilement. Voici les différentes valeurs retournées : UNIT_INCREMENT, UNIT_DECREMENT, BLOCK_INCREMENT, BLOCK_DECREMENT, TRACK.
getValue() : retourne la valeur courante de l'ascenceur.
ItemEvent
Généré par AbstractButton, JComboxBox.
L'utilisateur fait une sélection dans un groupe de case à cocher ou dans une liste.

getItem() : renvoie un objet représentant l'item qui a tété sélectionné ou désélestionné.
getItemSelectable() : est un remplacement commode à getSource(), et renvoie l'objet ItemSelectable qui a déclenché l'événement.
getStateChange() : renvoie le nouvel état de sélection de l'item : une des constante SELECTED ou DESELECTED.
KeyEvent
Généré par Component.
Une touche du clavier a été pressée ou relâchée.
getKeyChar() : Renvoie le caractère tapé par l'utilisateur.
getKeyCode() : renvoie le code de touche virtuel de cet élément du clavier - VK_0, VK_A, VK_LEFT, ...
getKeyModifiersText() : renvoie une chaîne décrivant les touches de modification comme shift ou ctrl+shift.
getKeyText() : Renvoie une chaîne décrivant le code de touche. Par exemple, getKeyText(KeyEvent.VK_END) renvoie "End" (ou "Fin" si votre version Java est localisée).
isActionKey() : renvoie true si la touche de cet événement est une touche "d'action" - Origine, Fin, Page précédente, Tabulation, etc.
MouseEvent
Généré par Component.
Le bouton de la souris a été enfoncé ou relâché ; le pointeur de la souris a été déplacé, ou glissé dans une opération de glisser-déplacer.
getX(), getY(), getPoint()
: Renvoie la coordonnée x, la coordonnée y et le point sur lequel s'est produit l'événement, dans le système de coordonnées de la source.
getClickCount()
: Renvoie le nombre de clics de souris consécutifs associés à l'événement.
translatePoint() : exécute un déplacement relatif des coordonnées de la souris.
isPopupTrigger() : renvoie true si le bouton concerné (généralement le droit) est celui traditionnellement réservé au menu surgissant.
MouseWheelEvent
Généré par Component.
La molette de la souris tourne.
getWheelRotation() : indique le nombre d'impulsions correspondant à la rotation de la molette.
getScrollAmount() : donne l'unité de mesure de chaque impulsion.
WindowEvent
Généré par Window.
La fenêtre a été activée, désactivée, réduite en icône, réouverte ou fermée.
getWindow() : détermine l'objet Window qui est la source de l'événement.
getOppositeWindow() : retourne la fenêtre qui possédait (anciennement) le focus.
getNewState(), getOldState() : Ces méthodes renvoient l'ancien état et le nouvel état d'une fenêtre dans un événement de modification de l'état de fenêtre. L'entier renvoyé correspond à l'une des valeurs suivantes : Frame.NORMAL, Frame.ICONIFIED, Frame.MAXIMIZED_HORIZ, Frame.MAXIMIZED_VERT, Frame.MAXIMIZED_BOTH.
Interfaces et classes adaptateur
ActionListener
Ecouteur qui prend en compte un choix ou une validation.
actionPerformed(ActionEvent) : traitement à réaliser après une validation venant de l'utilisateur.
AdjustementListener
Ecouteur qui prend en compte le déplacement du curseur d'une barre de défilement.
adjustmentValueChanged(AdjustementEvent) : traitement à réaliser après une modification du curseur de la barre de défilement.
ItemListener
Ecouteur qui prend en compte la sélection dans un groupe de cases à cocher ou dans une liste.
itemStateChanged(ItemtEvent)
: traitement à réaliser après la sélection.
FocusListener - FocusAdapter
Ecouteur qui prend en compte la sélection dans un groupe de cases à cocher ou dans une liste.
focusGained(FocusEvent) : traitement à réaliser avec l'obtention du focus.
focusLost(FocusEvent) : traitement à réaliser après la perte du focus.
KeyListener - KeyAdepter
Ecouteur qui prend en compte le clavier.
keyPressed(KeyEvent) : une touche du clavier a été pressée.
keyReleased(KeyEvent) : une touche du clavier a été relâchée.
keyTyped(KeyEvent) : appelée (en plus des deux précédentes), lorsque le caractère est définitevement saisie, avec la prise en compte des modificateurs ou des touches de fonction ou encore des séquences de touches multiples.
MouseListener - MouseAdapter
Ecouteur qui prend en compte les événements venant de la souris (mais pas les événements liés aux mouvements de la souris).
mousePressed(
MouseEvent) : l'utilisateur a appuyer sur un bouton de la souris.
mouseReleased(MouseEvent) : l'utilisateur a relâché le bouton de la souris.
mouseEntered(MouseEvent) : le pointeur de la souris est rentré dans le composant.
mouseExited(MouseEvent) : Le pointeur de la souris est resorti du composant.
mouseCliqued(MouseEvent): clic complet de la souris avec un appui suivi du relâchement sans déplacement entre temps.
MouseMotionListener - MouseMotionAdapter
Ecouteur qui prend en compte les événements liés aux mouvements de la souris.
mouseDragged(MouseEvent)
: l'utilisateur a déplacé la souris en maintenant un bouton enfoncé.
mouseMoved(MouseEvent) : l'utilisateur a déplacé la souris sans tenir de bouton enfoncé.
MouseWheelListener
Ecouteur qui prend en compte les événements liés à la molette de la souris.
mouseWheelMoved(MouseWheelEvent) : traitements en relation avec la rotation de la molette de la souris.
WindowListener - WindowAdapter
Ecouteur qui prend en compte le changement d'état de la fenêtre.
windowClosing(WindowEvent) : l'utilisateur veut fermer la fenêtre par le menu système ainsi que par le bouton de fermeture. Cette fenêtre ne se fermera qu'avec un appel à sa méthode hide() ou dispose().
windowClosed(WindowEvent) : envoyé après qu'une fenêtre a été fermée par un appel à hide() (rendre non visible) ou dispose().
windowOpened(WindowEvent) : cette méthode est appelée lorsque la fenêtre a été ouverte.
windowIconified(WindowEvent) : la fenêtre est réduite (en icône ou en bouton).
windowDeiconified(WindowEvent): la fenêtre est restaurée (elle retrouve sa taille initiale alors qu'elle était réduite).
windowActivated(WindowEvent) : Envoyé quand la fenêtre est activée. La fenêtre active est généralement repérée par une barre de titre en surbrillance. Une seule fenêtre ne peut être active à un moment donné.
windowDeactivated(WindowEvent) : Envoyé quand la fenêtre cesse d'être la fenêtre active, typiquement quand l'utilisateur active une autre fenêtre.
WindowFocusListener - WindowFocusAdapter
Ecouteur qui prend en compte le changement de focus sur la fenêtre.
windowGainedFocus(WindowEvent) : traitement à réaliser avec l'obtention du focus ou un de ses éléments.
windowLostFocus(WindowEvent) : traitement à réaliser après la perte du focus.
WindowStateListener
Ecouteur qui prend en compte le changement d'état de la fenêtre (maximisée, réduite en icône ou restaurée à sa taille normale).
windowStateChanged(WindowEvent) : traitement à réaliser au changement d'état.

C'est loin d'être exhaustif, mais cela donne une idée de ce que nous pouvons réaliser. Il s'agit ici des éléments les plus souvent utilisés.
.

Choix du chapitre Mise en oeuvre d'un timer (minuteur)

Restons encore une fois sur l'écouteur ActionListener qui est bien utile également pour la gestion des timers. Le paquetage javax.swing contient une classe Timer, qui nous averti de l'expiration d'un délai imparti. Par exemple, si une partie de votre programme contient une horloge, vous pouvez demander à être averti à chaque seconde, de manière à pouvoir mettre à jour l'affichage de l'horloge.

Lorsque vous construisez un minuteur, vous définissez l'intervalle de temps et lui indiquez ce qu'il doit faire lorsque le délai est écoulé. Le traitement à réaliser doit être stipuler dans la méthode actionPerformed() de l'écouteur ActionListener que nous connaissons bien. Au moment où vous construisez votre minuteur, vous spécifier l'intervalle de temps en millièmes de seconde entre chaque notification ainsi que l'écouteur de type ActionListener que vous avez mis en oeuvre :

ActionListener écouteur = ...;
Timer minuteur = new Timer(1000, écouteur);

Une fois que votre minuteur est construit, vous pouvez le démarrer ou l'arrêter au moment où vous le désirez. Voici les méthodes respectives :

  1. start() : démarre le minuteur. La méthode actionPerformed() est alors sollicitée par le minuteur à chaque notification du délai écoulé.
  2. stop() : arrête le minuteur. Une fois arrêté, le minuteur n'appelle plus la méthode actionPerformed().

Si vous désirez que votre minuteur fonctionne, pensez bien à le démarrer au moyen de la méthode start().
.

Mise en oeuvre d'une horloge simple

Afin d'illustrer mes propos, je vous convie à réaliser un petit programme qui met en oeuvre une horloge simple à affichage digitale (sous forme textuelle).

package horloge;

import java.awt.*;
import java.awt.event.*;
import java.text.DateFormat;
import java.util.Date;
import javax.swing.*;

public class Fenêtre extends JFrame implements ActionListener {
   private Timer minuteur = new Timer(1000, this);
   private JLabel heure = new JLabel();
   
   public Fenêtre() {
      super("Horloge");
      setBounds(100, 100, 180, 80);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      heure.setFont(new Font("Arial", Font.BOLD+Font.ITALIC, 32));
      heure.setHorizontalAlignment(JLabel.CENTER);
      add(heure);      
      minuteur.start();
      setResizable(false);
      setVisible(true);
   }
   
   public static void main(String[] args) {
      new Fenêtre();
   }

   public void actionPerformed(ActionEvent e) {
      heure.setText(DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date()));
   }
}

 

Choix des chapitresCapture des événements de fenêtre

Dans un programme professionnel, il est souvent souhaitable de fermer l'application qu'après s'être assuré que l'utilisateur ne perdra pas son travail. Par exemple, vous pouvez souhaiter afficher une boîte de dialogue lorsque l'utilisateur ferme le cadre, pour l'avertir si un travail non sauvegardé risque d'être perdu, et ne sortir qu'après confirmation de l'utilisateur.

Lorsque l'utilisateur tente de fermer un cadre, l'objet JFrame est la source d'un événement WindowEvent. Nous devons donc avoir un objet écouteur approprié et l'ajouter à la liste des écouteurs de fenêtre :

WindowListener écouteur = ... ;
cadre.addWindowListener(écouteur);

L'écouteur de fenêtre doit être un objet d'une classe implémentant l'interface WindowListener, qui possède sept méthodes. Le cadre les appelle en réponse aux septs événements distincts qui peuvent se produire dans une fenêtre. Voici l'interface complète de WindowListener :

public interface WindowListener {
   void windowOpened(WindowEvent e);
   void windowClosing(WindowEvent e);
   void windowClosed(WindowEvent e);
   void windowIconified(WindowEvent e);
   void windowDeiconified(WindowEvent e);
   void windowActivated(WindowEvent e);
   void windowDeactivated(WindowEvent e);
}

Pour savoir si une fenêtre a été maximisée, il est préférable d'installer un WindowStateListener à la place d'un WindowListener.
.

La méthode qui m'intéresse ici est windowClosing(). Elle est effectivement appelée lorsque l'utilisateur clique sur le bouton de fermeture de la fenêtre :


Comme toujours en Java, toute classe qui implémente une interface doit implémenter toutes les méthodes de cette interface ; dans ce cas, cela signifie que les septs méthodes doivent être implémentées. Hors ici, une seule nous intéresse sur les septs prévues.

Nous pouvons, bien entendu définir, une classe qui implémente l'interface, ajouter les traitements spécifiques à la méthode windowClosing() et fournir des blocs vides pour les six autres méthodes. Toutefois, cette solution est fastidieuse. Nous avons vu qu'il existait des classes apaptateurs qui permettent ainsi de redéfinir uniquement les méthodes utiles à l'application. Voici deux codages de la même application avec ou sans classe anonyme :
package editeur;

import java.awt.event.*;
import java.io.*;
import javax.swing.*;

public class Fenêtre extends JFrame {
   private JEditorPane édition = new JEditorPane();
   
   public Fenêtre() {
      super("Editeur de texte");
      setBounds(50, 50, 350, 250);
      add(new JScrollPane(édition));
      addWindowListener(new WindowAdapter() {
          public void windowClosing(WindowEvent e) {
             if (JOptionPane.showConfirmDialog(Fenêtre.this, "Sauvegardez votre travail")==JOptionPane.YES_OPTION) {
                try {
                   PrintWriter enregistrer = new PrintWriter("sauvegarde.txt");
                   enregistrer.println(édition.getText());
                   enregistrer.close();
                } 
                catch (FileNotFoundException ex) { } 
                System.exit(0);
             }           
          }
      });
      setVisible(true);
   }

   public static void main(String[] args) {
       new Fenêtre();
   }
}
  
package editeur;

import java.awt.event.*;
import java.io.*;
import javax.swing.*;

public class Fenêtre extends JFrame {
   private JEditorPane édition = new JEditorPane();
   
   public Fenêtre() {
      super("Editeur de texte");
      setBounds(50, 50, 350, 250);
      add(new JScrollPane(édition));
      addWindowListener(new Ecouteur());
      setVisible(true);
   }
   
   public static void main(String[] args) {
      new Fenêtre();
   }
   
   private class Ecouteur extends WindowAdapter {
      public void windowClosing(WindowEvent e) {
          if (JOptionPane.showConfirmDialog(Fenêtre.this, "Sauvegardez votre travail")==JOptionPane.YES_OPTION) {
             try {
                PrintWriter enregistrer = new PrintWriter("sauvegarde.txt");
                enregistrer.println(édition.getText());
                enregistrer.close();
             } 
             catch (FileNotFoundException ex) { }              
          }   
          System.exit(0);
       }      
   }
}

Dans ce cas particulier, il faut bien penser à ne pas utiliser la méthode setDefaultCloseOperation(EXIT_ON_CLOSE).
.

 

Choix du chapitre Les événements du clavier

A partir de maintenant, nous allons examiner plus en détail les événements qui ne sont pas liés à des composants spécifiques, en particulier les événements relatifs au clavier et à la souris. Commençons donc par les événements issus du clavier.

La plupart du temps, vous n'avez pas à vous préoccuper du clavier car sa gestion est déjà assurée automatiquement par Java. C'est notamment le cas lors de la saisie d'un texte dans une boîte de saisie ou dans un champ de texte (les touches de correction telles que Return, Backspace, Insert, Delete, flèches droite ou gauche sont convenablement prises en compte).

Mais parfois, cette gestion automatique s'avère insuffisante. C'est notamment le cas si vous souhaitez desiner dans une fenêtre eu utilisant les touches du clavier ou encore si vous voulez afficher des caractères frappés au clavier. Nous allons voir ici comment procéder pour exploiter plus finement les événements correspondants.

Les événements générés

Les événements générés par le clavier appartiennent à la catégorie KeyEvent. Ils sont gérés par un écouteur implémentant l'interface KeyListener qui comporte trois méthodes :

  1. keyPressed() : appelée lorsqu'une touche est enfoncée.
  2. keyReleased() : appelée lorsqu'une touche est relâchée.
  3. keyTyped() : appelée (en plus des deux précédentes), lorsque le caractère est définitivement saisie, avec la prise en compte des modificateurs ou des touches de fonction ou encore des séquences de touches multiples.

Par exemple, la frappe du caractère A entraînera les appels suivants :

  1. keyPressed() : pour l'appui sur la touche Shift.
  2. keyPressed() : pour l'appui sur la touche a.
  3. keyReleased() : pour le relâchement de la touche a.
  4. keyReleased() : pour le relâchement de la touche Shift.
  5. keyTyped() : pour le caractère A.

En revanche, la frappe du caractère a (minuscule) n'entraîne que les appels suivants :

  1. keyPressed() : pour l'appui sur la touche a.
  2. keyReleased() : pour le relâchement de la touche a.
  3. keyTyped() : pour le caractère a.

Si nous nous contentons d'appuyer sur une touche telle que Alt et de la relâcher, nous obtiendrons seulement un appel de keyPressed(), suivi d'un appel de keyReleased(), sans aucun appel de keyTyped().

Vous pouvez ainsi suivre dans le moindre détail les actions de l'utilisateur sur le clavier. Bien entendu, si votre but est simplement de lire les caractères, vous pourrez vous contenter de ne traiter que les événements keyTyped().

Identification des touches

L'objet événement (de type keyEvent) reçu par les trois méthodes précédentes contient les informations nécessaires à l'identification de la touche physique du clavier ou du caractère concerné.

  1. D'une part, la méthode getKeyChar() fournit le caractère concerné (sous la forme d'une valeur de type char).
  2. D'autre part, la méthode getKeyCode() fournit un entier nommé code de touche virtuelle permettant d'identifier la touche physique du clavier. Il existe dans la classe KeyEvent un certain nombre de constantes correspondant à chacune des touches que nous pouvons rencontrer sur un clavier. Voici les principales :
  3. Enfin, il existe dans KeyEvent une méthode statique getKeyText() qui permet d'obtenir, sous la forme d'une chaîne, un bref texte expliquant le rôle d'une touche de code donné. Par exemple :

    String chaîne = KeyEvent.getKeyText(VK_SHIFT) ; // renvoie la chaîne "Shift".

Il est possible que certaines touches du clavier ne disposent pas de code de touche virtuelle. Dans ce cas Java fournit le code 0 (le texte associé est "Unknown keyCode : 0x0").

Lorsqu'une touche possède plusieurs significations (matérialisées par plusieurs gravures), elle ne dispose généralement que d'un seul code de touche virtuelle. Par exemple, sur un clavier francisé (AZERTY), la même touche comporte les trois gravures 3, " et #. Son code de touche sera toujours VK_3. Nous pouvons toutefois rencontrer quelques exceptions. Par exemple, sur un clavier doté d'un pavé numérique, la touche gravée 7 et flèche oblique fournira l'un des codes VK_NUMPAD7 ou VK_HOME selon que le clavier est vérouillé en numérique ou non.

Etat des touches modificatrices

Pour connaître l'état des touches Shift (Maj), Cntrl, Alt, Alt GR ou Meta, il est bien sûr possible d'intercepter les événements correspondants : VK_SHIFT, VK_CONTROL, VK_ALT, VK_ALT_GRAPH ou VK_META. Mais cette technique est ennuyeuse. Il est plus simple d'utiliser les méthodes spécifiques suivantes :

Source d'un événement clavier

Java considère qu'un événement clavier possède comme sources :

  1. Le composant ayant le focus au moment de l'action,
  2. les conteneurs éventuels de ce composant.

Nous voyons que tant que nous nous contentons d'intercepter les événements clavier dans la fenêtre principale, aucun problème ne se pose. Bien entendu, les composants comme les étiquettes (JLabel), qui ne peuvent pas recevoir de focus, ne pourront pas être la source de l'événement clavier, ce qui d'ailleurs n'aurait aucun sens.

En revanche, les composants comme les panneaux (JPanel) peuvent poser problème, car ils ne mettent pas en évidence leur focalisation. Il faudra alors appeler la méthode setFocusable() pour palier ce problème.

Mise en oeuvre de la gestion événementielle à partir du clavier - saisie confirmée par la touche Entrée (Validation)

Après toute cette étude technique, rentrons dans le vif du sujet en proposant un certain nombre d'exemples. Dans le premier, nous allons mettre en place une simple validation confirmée par la touche Entrée.

Ainsi, dans cet exemple, un message va être introduit dans la zone de saisie. Une fois que le message est écrit, nous confirmons la saisie en appuyant sur la touche Entrée du clavier. Dès lors, le même message apparaît sur la partie haute de la fenêtre, mais en majuscule. Comme il s'agit encore une fois d'une validation, nous utilisons de nouveau l'interface ActionListener et sa méthode associée actionPerformed().

package clavier;

import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;

public class Fenêtre extends JFrame implements ActionListener {
   private JTextField saisie = new JTextField();
   private JLabel message = new JLabel();
   
   public Fenêtre() {
      super("Evénéments du clavier");
      setSize(300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      saisie.addActionListener(this);
      add(saisie, BorderLayout.SOUTH);
      add(message, BorderLayout.NORTH);
      setVisible(true);
   }
   
   public static void main(String[] args) {
      new Fenêtre();  
   }

   public void actionPerformed(ActionEvent e) {
      message.setText(saisie.getText().toUpperCase());
   }
}

L'événement est capturé à la saisie de chaque caractère issu du clavier

Nous désirons, cette fois-ci, avoir le message qui se modifie instantanément après l'introduction de chaque caractère tapé sur le clavier. Par ailleurs, lorsque l'opérateur appuie sur la touche Entrée du clavier, le message s'efface aussi bien sur la zone de saisie que sur le partie haute de la fenêtre.

A priori, l'interface à utiliser pour ce type de comportement est cette fois-ci l'interface KeyListener. Toutefois, nous allons utiliser qu'une seule méthode parmi les trois proposées. Il est alors plutôt judicieux d'utiliser la classe adaptateur associée - KeyAdapter - et de passer ainsi par l'écriture d'une classe anonyme.

package clavier;

import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;

public class Fenêtre extends JFrame {
   private JTextField saisie = new JTextField();
   private JLabel message = new JLabel();
   
   public Fenêtre() {
      super("Evénéments du clavier");
      setSize(300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      add(saisie, BorderLayout.SOUTH);
      add(message, BorderLayout.NORTH);
      setVisible(true);
      saisie.addKeyListener(new KeyAdapter() {
         public void keyReleased(KeyEvent touche) {
             if (touche.getKeyCode()==KeyEvent.VK_ENTER) saisie.setText("");
             message.setText(saisie.getText().toUpperCase());
         }
      });
   }
   
   public static void main(String[] args) {
      new Fenêtre();  
   }
}

Si nous désirons que l'objet message soit une copie du texte de la zone de saisie, il est préférable de prendre la méthode keyReleased(). En effet, après le relâchement de la touche, le caractère saisi est déjà enregistré. La méthode keyPressed() correspond à l'événement appui sur la touche, et à cet instant là, le caractère n'est pas encore récupéré du clavier. Enfin, nous aurions pu pensé qu'il fallait plutôt prendre la méthode getTyped(), mais cette dernière ne délivre le caractère apparemment que sur la saisie du caractère suivant.

Choix du chapitre Retour sur les événements de la souris

Il n'est pas nécessaire de gérer explicitement les événements de la souris si vous désirez seulement que l'utilisateur puisse cliquer sur un bouton ou un menu. Ces opérations sont gérées de façon interne par les divers composants de l'interface utilisateur et traduites en événements sémantiques appropriés. Cependant, si vous voulez permettre à l'utilisateur de dessiner avec la souris, il vous faudra intercepter les mouvements, les clics et les opérations de glisser-déplacer de la souris.

Les événements générés par la souris appartiennent à la catégorie MouseEvent. Suivant le cas, ils sont gérés par un écouteur implémentant l'interface MouseListener et/ou l'interface MouseMotionListener.

Gestion des événements sans prise en compte du déplacement du curseur de la souris

Lorsque l'utilisateur clique sur un bouton de la souris, trois méthodes de l'écouteur MouseListener sont appelées :

  1. mousePressed() : quand le bouton est enfoncé,
  2. mouseReleased() : quand il est relâché,
  3. mouseCliked() : à la suite de la succession des deux événements précédents.

Vous pouvez ignorer les deux premières méthodes si vous n'êtes intéressé que par des clics complets.
.

En utilisant getX() et getY() sur l'argument MouseEvent, vous pouvez obtenir les coordonnées x et y du pointeur de la souris au moment du clic. Si vous souhaitez faire une distinction entre clic simple et double-clic (voir triple-clic), employez la méthode getClicCount().

Identification du bouton de la souris et les touches modificatrices du clavier

Il est possible de connaître le(s) bouton(s) de la souris concernée(s) pour un événement donné, en recourrant à la méthode getModifiers() de la class MouseEvent. Elle fournit un entier dans lequel le bit de rang donné est associé à chacun des boutons et prend la valeur 1 pour indentifier un appui. La classe InputEvent contient des constantes que nous pouvons utiliser comme masque afin de tester la présence d'un bouton donné dans la valeur issue de getModifiers().

Masque Bouton correspondant
InputEvent.BUTTON1_MASK gauche
InputEvent.BUTTON2_MASK central (s'il existe)
InputEvent.BUTTON3_MASK droite

Par exemple pour savoir si le bouton droit est enfoncé :

public void mousePressed(MouseEvent e) {
  if ((e.getModifiers() & InputEvent.BUTTON3_MASK) != 0) ...;
  ...
}
Lorsque nous nous interressons uniquement au relâchement du bouton droit, notamment pour déclencher l'affichage d'un menu surgissant, nous pouvons nous contenter d'utiliser la méthode isPopupTrigger() qui contrôle s'il s'agit bien du bon bouton (généralement le droit) pour activer le menu (dans ce cas, faites bien attention de réaliser le test dans mouseReleased() et non dans mouseClicked()).

Bien cela soit assez rarement souhaitable, vous pouvez combiner les boutons de la souris avec les touches modificatrices du clavier : Crtl, Shift, Alt, Alt Gr et Meta. Dans ce cas, prenez plutôt la méthode getModifiersEx() à la place de la méthode getModifiers(). La classe InputEvent dispose également des masques spécifiques à la gestion de ces touches modificatrices.

Masque Touche correspondante
InputEvent.SHIFT_DOWN_MASK majuscule (Shift)
InputEvent.CTRL_DOWN_MASK Ctrl
InputEvent.ALT_DOWN_MASK Alt
InputEvent.ALT_GRAPH_DOWN_MASK Alt Gr
InputEvent.META_DOWN_MASK Une des quatre touches précédentes

Par exemple pour savoir si le bouton droit est enfoncé en même temps de la touche Ctrl :

public void mousePressed(MouseEvent e) {
  if ((e.getModifiers() & (InputEvent.BUTTON3_MASK | InputEvent.CTRL_DOWN_MASK)) != 0) ...;
  ...
}

Gestion des déplacements de la souris

Dès que vous déplcez la souris, même sans cliquer sur un des boutons, vous provoquez des événements. Tous se passe comme si, à des intervalles de temps relativement réguliers, la souris signalait sa position, ce qui peut donner naissance à deux sortes d'événements :

Entrée-sortie de composant gérés par l'écouteur MouseListener
mouseEntered(MouseEvent) : généré chaque fois que la souris passe de l'extérieur à l'intérieur d'un composant.
mouseExited(MouseEvent) : généré chaque fois que la souris passe de l'intérieur à l'extérieur d'un composant.
Déplacement sur un composant géré par l'écouteur MouseMotionListener
mouseMoved(MouseEvent) : l'utilisateur déplace la souris sans tenir de bouton enfoncé.
mouseDragged(MouseEvent) : l'utilisateur déplace la souris en maintenant un bouton enfoncé. Notez bien que les événements mouseDragged continuent d'être générés (pour le composant concerné) même si la souris sort du composant, et ce jusqu'à ce que l'utilisateur relâche le bouton.

Nous devons encore expliquer comment écouter les événements de souris. Les clics sont signalés par la méthode mouseClicked(), qui fait partie de l'interface MouseListener. Comme de nombreuses applications ne s'intéressent qu'aux clics de souris - et pas à ces mouvements - et comme les événements de déplacement se produisent très fréquemment, les événements de glisser-déplacer de la souris sont définis dans une interface séparée appelée donc MouseMotionListener.

Exemple de gestion des mouvements de la souris

Afin d'illustrer toute cette approche, vous allez mettre en oeuvre un programme qui permet de déplacer un texte à l'aide de la souris. De plus, lorsque le curseur se déplace au dessus du texte, le message de bienvenue change de couleur, du bleu il passe au rouge. Nous pouvons remarquer que le texte est à la fois source des événements, mais également écouteur. Il devra donc s'occuper de la gestion de ses propres événements de la souris.

Pour les événements de la souris, nous venons de voir qu'il existe deux interfaces écouteurs spécifiques : une pour les mouvements de la souris - MouseMotionListener, l'autre pour tous les autres types d'événements - MouseListener. Une des solution consiste à fabriquer une classe Bienvenue qui hérite de JLabel et qui implémente ces deux interfaces. Elles permettent de prendre en compte le fait que le curseur de la souris passe au dessus du texte avec la méthode mouseEntered(), en sort avec la méthode mouseExited(), que l'on clique sur le texte pour mémoriser la position initiale à l'aide de la méthode mousePressed(), et enfin le déplacement de la souris avec le maintien de l'appui sur le bouton au moyen de la méthode mouseDragged() (Glisser-Déposer).






 

Choix du chapitre Les événements de focalisation

Lorsque vous utiliser une souris, vous pouvez pointer sur n'importe quel objet à l'écran. Mais, lorsque vous faites une saisie à l'aide du clavier, les frappes sur les touches doivent concerner un objet spécifique. Le gestionnaire de fenêtre (tel que Windows ou X Window) dirige toutes les frappes de touche vers la fenêtre active. Souvent la fenêtre active se distingue par une barre de titre en surbrillance. Une seule fenêtre peut être active à la fois.

La fenêtre Java reçoit à son tour les frappes du clavier et les dirige vers un composant particulier. Ce composant est désigné comme ayant le focus ou encore qu'il détient la focalisation. En effet, à un instant donné, seul un composant est actif, qui se traduit par une indication visuelle : un champ de