Participer au site avec un Tip
Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Utilisation de layouts Changement de contenu d'un Layout



La gestion des évènements en Java

Accès rapide :
La vidéo
Introduction
Traitement d'évènements simples
Définition d'un écouteur
Enregistrement d'un écouteur
Définition de classes privées
Définition de classes anonymes
Les classes d'adaptation
Types d'évènements supportés par l'AWT et Swing

La vidéo

Ce tutoriel vous montre comment coder des gestionnaires d'événements Swing, via différentes techniques (classes anonymes, lambda expressions, références sur méthodes, ...).


La gestion des évènements en Java

Introduction

Le langage Java propose un mécanisme clairement établit pour le traitement d'évènements.Ce mécanisme repose sur la notion de "Listener" (d'écouteurs si vous préférez).

En fait, ce mécanisme n'a pas été le premier proposé dans le JDK 1.0. En effet, la première version du JDK proposait un tout autre mécanisme de gestion d'évènements. Celui-ci était basé sur une autre notion. Il s'est avéré que ce premier mécanisme fut problématique à utiliser. Dés la version 1.1 du JDK, le second mécanisme fut donc proposé. Dans ce cours nous ne traiterons que ce dernier modèle (le premier étant maintenant largement déprécié) !

Ce qui faut aussi savoir, c'est que ce mécanisme de gestion d'évènements n'est, en fait, pas directement rattaché à une quelconque API d'interface graphique. Au contraire, il se veut indépendant de tout contexte. On peut donc traités les évènements Java en réponse à des clicks souris, mais aussi en réponse à tout autre activité se déroulant au sein de la JVM.

Bien entendu, en Java, tout est objet. En conséquence, les évènements sont aussi des objets. D'ou deux classes particulières : java.util.EventObject et java.awt.AWTEvent. La première correspond à la classe d'évènements la plus générique (elle dérive directement de java.lang.Object. De la seconde dérive tous les évènements le l'AWT et de Swing : c'est donc la classe mère des évènements d'interfaces utilisateurs.

La classe java.util.EventObject possède une méthode fort intéressante : getSource(). Elle permet de récupérer l'objet sur lequel l'évènement est initialement apparut. En conséquence, quand vous traiterez un évènement, vous pourrez toujours savoir quel est l'objet initiateur du traitement.

Malgré ces quelques explications, sachez que pour traiter les évènements vous devrez, au moins, importer deux packages : java.awt.* et java.awt.event.*;

Traitement d'évènements simples

Nous allons maintenant voir comment savoir qu'un évènement a eu lieu, et comment allons nous pouvoir y répondre. Pour cela, il nous faut définir un écouteur, puis enregistrer ce dernier sur un objet susceptible de déclencher un évènement. Ainsi, si l'évènement apparaît, il pourra être traité par l'écouteur.

Définition d'un écouteur

Dans le modèle Java de traitement d'évènements, il nous faut définir un écouteur. Un écouteur est un objet qui possède au moins une méthode qui pourra être invoquée si l'évènement attendu apparaît. En fait, un écouteur doit remplir un contrat bien particulier. Dans cet objectif, il se doit d'implémenter un comportement (la plupart du temps en implémentant une interface, ou bien en dérivant de certaines classes).

Il existe plusieurs types d'écouteurs, donc plusieurs types d'interfaces. Toutes ces interfaces ont un nom se terminant par Listener (par exemple ActionListener, WindowListener, ...). Selon l'interface, il y a une ou plusieurs méthodes à implémenter.

A titre d'exemple, considérons l'évènement ActionEvent : cet évènement est déclenché dès lors que l'on action un bouton (par exemple) soit en cliquant dessus avec la souris soit en utilisant le clavier. Pour être capable d'écouter et de traiter un tel évènement, un objet écouteur se doit d'implémenter une interface nommée ActionListener. Si vous êtes curieux et que vous étudiez l'interface ActionListener, vous remarquerez qu'elle ne contient qu'une unique méthode à implémenter : actionPerformed. Cette méthode doit donc contenir le code à exécuter si jamais vous cliquez sur votre bouton.

import java.awt.*;
import java.awt.event.*;

public class PushMe implements ActionListener {
    static public void main(String argv[]) {
        new PushMe();
    }

    // Le constructeur de la classe PushMe
    public PushMe() {
        Frame f = new Frame("Ma fenêtre");
        Button b = new Button("Push me");

        f.add(b); // On ajoute le bouton dans la fenêtre
        f.pack();
        f.setVisible(true);
    }

    // gestionnaire d'évènement définit dans ActionListener
    public void actionPerformed(ActionEvent event) {
        System.out.println("Bouton " + event.getSource() + " cliqué !");
    }
}

Cependant, si vous exécutez ce code, rien ne se passe. Pourquoi ?

Enregistrement d'un écouteur

Le problème est simple. On à définit un écouteur (l'objet de classe PushMe) susceptible de répondre à un click sur le bouton. Mais qui à dit au bouton, qu'il fallait avertir l'écouteur, si on lui cliquait dessus ? Absolument personne : le problème est que chaque écouteur doit être enregistré auprès des objets qu'il est censé écouter (un écouteur peut écouter plusieurs sources d'évènements - une source d'évènement peut alerter plusieurs écouteurs). L'exemple suivant enregistre l'écouteur à une source d'évènements.

import java.awt.*;
import java.awt.event.*;

public class PushMe implements ActionListener {
    static public void main(String argv[]) {
        new PushMe();
    }

    // Le constructeur de la classe PushMe
    public PushMe() {
        Frame f = new Frame("Ma fenêtre");
        Button b = new Button("Push me");
        b.addActionListener(this);

        f.add(b); // On ajoute le bouton dans la fenêtre
        f.pack();
        f.setVisible(true);
    }

    // gestionnaire d'évènement définit dans ActionListener
    public void actionPerformed(ActionEvent event) {
        System.out.println("Bouton " + event.getSource() + " cliqué !");
    }
}

Maintenant, vous pouvez cliquer sur le bouton. Un message est affiché en confirmation sur la console. Notez que l'objet source de l'évènement est aussi affiché sur la console.

Qu'a permi de faire la méthode addActionListener ? En fait c'est simple, elle à simplement permit de rajouter un écouteur au bouton. A chaque fois que le bouton est actionné, il cherche maintenant à invoquer la méthode actionPerformed sur chacun de ses écouteurs. ActionListener pouvant être utilisé comme un type, le compilateur ne dit rien et tout fonctionne.

Cependant, l'exemple du code précédent peut se réécrire différemment : en effet un objet d'écoute peut être créé exclusivement dans ce but. Et comme les générateurs d'interfaces graphiques utilisent ces autres mécanismes, regardons les de plus près.

Définition de classes privées

Une première solution consiste à définir des classes privées simplement dédiées au traitement des l'évènements. Deux façons de coder peuvent être retenue.

import java.awt.*;
import java.awt.event.*;

public class PushMe {
    static public void main(String argv[]) {
        new PushMe();
    }

    // Le constructeur de la classe PushMe
    public PushMe() {
        Frame f = new Frame("Ma fenêtre");
        Button b = new Button("Push me");
        b.addActionListener(new Handler());

        f.add(b); // On ajoute le bouton dans la fenêtre
        f.pack();
        f.setVisible(true);
    }
}

class Handler implements ActionListener {
    // gestionnaire d'évènement définit dans ActionListener
    public void actionPerformed(ActionEvent event) {
        System.out.println("Bouton " + event.getSource() + " cliqué !");
    }
}

import java.awt.*;
import java.awt.event.*;

public class PushMe {
    static public void main(String argv[]) {
        new PushMe();
    }

    // Le constructeur de la classe PushMe
    public PushMe() {
        Frame f = new Frame("Ma fenêtre");
        Button b = new Button("Push me");
        b.addActionListener(new Handler());

        f.add(b); // On ajoute le bouton dans la fenêtre
        f.pack();
        f.setVisible(true);
    }
    
    private class Handler implements ActionListener {
        // gestionnaire d'évènement définit dans ActionListener
        public void actionPerformed(ActionEvent event) {
            System.out.println("Bouton " + event.getSource() + " cliqué !");
        }
    }
}

Définition de classes anonymes

Cette seconde méthode, un peu déroutante au démarrage, permet malgré tous une écriture simplifiée de votre programmes. Je vous conseil de bien l'assimiler, dans le sens ou elle est fréquemment utilisées. En fait, c'est très simple : la méthode addActionListener à besoin d'un objet de type ActionListener, on en dérive donc une classe anonyme qui redéfinit quelques méthodes (ici une) est qui directement utilisée pour créer un unique objet d'écoute.

import java.awt.*;
import java.awt.event.*;

public class PushMe {
    static public void main(String argv[]) {
        new PushMe();
    }

    // Le constructeur de la classe PushMe
    public PushMe() {
        Frame f = new Frame("Ma fenêtre");
        Button b = new Button("Push me");
        b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                System.out.println("Bouton cliqué !");
            }
        });

        f.add(b); // On ajoute le bouton dans la fenêtre
        f.pack();
        f.setVisible(true);
    }
}

Notez bien qu'une telle classe anonyme ne peut servir qu'à générer un unique objet. Dans ce cas, on associe un écouteur à une unique source d'évènements.

Les classes d'adaptation

Dans les exemples précédents, les choses étaient simples : l'interface utilisée ne définie qu'une seule méthode. Mais en est-il de même pour les autres types d'évènements ? Et bien non. Les évènements de fenêtre en sont un bon exemple : en effet une seule interface est définie pour tous ce qui peut arriver sur une fenêtre (ouverture de fenêtre, fermeture, iconification, ...). Au total, ce sont sept méthodes qui y sont définies. Or, n'oublions pas qu'en implémentant une interface, vous vous devez d'implémenter chacune de ses méthodes (sans quoi votre classe est abstraite) et ce même si certaines ne vous sont pas utile (vous ne vouliez réagir qu'à la fermeture de la fenêtre).

Une solution intéressante est de coupler les mécanismes de classe anonyme et de classe adaptation. Une classe d'adaptation est une classe qui implémente une interface en fournissant, pour chaque méthode, un corps vide. L'intérêt est évident : si vous avez besoin de fournir du code pour une unique méthode de l'interface, vous ne devrez donner, dans la définition de la classe anonyme, que le code ce cette méthode. Pour les autres, elles sont déjà implémentées, même si leur code est inexistant (un corps vide { }). 

Une petite remarque : toutes les classes d'adaptation ont sensiblement le même nom que leur interface associée. Il suffit de remplacer le mot 'Listener' par 'Adapter'. Ainsi l'interface WindowListener possède une classe associée, nommée WindowAdapter. Autre remarque, si une interface du JDK ne possède qu'une seule méthode abstraite, aucune classe d'adaptation n'y est rattachée. Le petit exemple suivant illustre cette notion en proposant une méthode chargée de tuer l'application si vous cliquez sur le bouton de fermeture de la fenêtre.

import java.awt.*;
import java.awt.event.*;

public class PushMe {
    static public void main(String argv[]) {
        new PushMe();
    }

    // Le constructeur de la classe PushMe
    public PushMe() {
        Frame f = new Frame("Ma fenêtre");
        Button b = new Button("Push me");
        b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                System.out.println("Bouton cliqué !");
            }
        });

        f.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent event) {
                System.exit(0);
            }
       });

        f.add(b); // On ajoute le bouton dans la fenêtre
        f.pack();
        f.setVisible(true);
    }
}

Types d'évènements supportés par l'AWT et Swing

Pour clore ce chapitre, nous allons présenter quelques autres types d'évènements que vous pourriez traiter : la liste n'est bien entendu pas exhaustive ! Commençons par la gestion du clavier : à tout composant (objet de classe Component), vous pouvez enregistrer un KeyListener. Toute classe implémentant cette interface se doit de fournir le code de trois méthodes : keyPressed, keyReleased et keyTyped. Ces trois dernières méthodes doivent prendre en paramètre un objet de classe KeyEvent.

Une autre interface utile à implémenter est MouseListener. Elle définit cinq méthodes prenant toutes en paramètre en objet de classe MouseEvent. En voici leurs noms : mouseClicked, mouseEntered, mouseExited, mousePressed et mouseReleased. Bien entendu, tout objet de l'AWT peut enregistrer un MouseListener.

Dans la volée, nous pouvons aussi parler de l'interface FocusListener : elle est utilisée pour répondre à la prise, ou à la perte, du focus par un élément de l'AWT. Cette interface définie deux méthodes : focusGained et focusLost. Ces deux méthodes requièrent en unique paramètre un objet de classe FocusEvent.

Enfin, parlons aussi de l'interface TextListener qui permet de répondre à un changement de contenu d'une zone de saisie de texte. Cette interface défini une unique méthode : textValueChanged. Un TextListener s'enregistre après d'un TextComponent (dont dérivent les classes TextArea et TextField). La classe d'évènement associée est, bien entendu, TextEvent.

Pour plus d'informations sur les éléments présentés dans ce chapitre (et ceux qui ne l'ont pas été), reportez-vous à la documentation de l'API du JDK.



Utilisation de layouts Changement de contenu d'un Layout