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.
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 :
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);
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.
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.
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 :
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.
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 :

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".
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
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).

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().
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 :

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 :
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.

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) ?

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.
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().


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().


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.

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.

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.
.
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.
AWT fait une distinction utile entre événements de bas niveau et événements sémantiques :
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.
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()
Les interfaces suivantes permettent d'écouter ces événements :
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.
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 :
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.
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.
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.
.
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.
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 :
Si vous désirez que votre minuteur fonctionne, pensez bien à le démarrer au moyen de la méthode start().
.
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())); } }
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.

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.
.

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.
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).
.
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 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 :
Par exemple, la frappe du caractère A entraînera les appels suivants :
En revanche, la frappe du caractère a (minuscule) n'entraîne que les appels suivants :
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().
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é.
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.
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 :
Java considère qu'un événement clavier possède comme sources :
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.
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()); } }

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.

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.
Lorsque l'utilisateur clique sur un bouton de la souris, trois méthodes de l'écouteur MouseListener sont appelées :
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().
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 |
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 |
public void mousePressed(MouseEvent e) { if ((e.getModifiers() & (InputEvent.BUTTON3_MASK | InputEvent.CTRL_DOWN_MASK)) != 0) ...; ... }
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 :
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.
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).



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