1. Introduction très brève à Swing et AWT
Si Java était une voiture, Swing et AWT en seraient le « tableau de bord » et le « volant ». AWT (Abstract Window Toolkit) est la toute première bibliothèque pour créer des applications de bureau en Java. Elle utilise les éléments d'interface « natifs » du système d'exploitation, donc l'apparence diffère sous Windows, Linux et Mac.
Swing est une surcouche plus moderne à AWT, entièrement écrite en Java. Swing est plus esthétique, plus flexible, prend en charge quantité de fonctionnalités supplémentaires et présente le même aspect sur toutes les plateformes (enfin, presque). Dans les deux cas, tous les éléments d'interface (boutons, champs de texte, cases à cocher, etc.) prennent en charge les événements.
Dans ce cours, nous utiliserons Swing : il est plus simple pour les débutants et plus fréquent dans les exemples pédagogiques. Voyons comment fonctionne le traitement des événements à travers la tâche la plus populaire — la réaction au clic sur un bouton.
Créer un bouton
Dans Swing, on crée un bouton ainsi :
JButton button = new JButton("Cliquez-moi !");
Ici, « Cliquez-moi ! » est le libellé du bouton.
Ajouter un écouteur
Pour réagir au clic, il faut « abonner » un écouteur à l'événement :
button.addActionListener(new MyActionListener());
Mais qui est ce MyActionListener ? C'est une classe qui implémente l'interface ActionListener :
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MyActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Le bouton a été cliqué !");
}
}
Lorsque l'utilisateur clique sur le bouton, Swing appelle la méthode actionPerformed de tous les écouteurs enregistrés via addActionListener.
Exemple complet : une fenêtre simple avec un bouton
Rassemblons tout et créons une application fonctionnelle :
import javax.swing.*;
import java.awt.event.*;
public class SimpleButtonApp {
public static void main(String[] args) {
JFrame frame = new JFrame("Exemple d'événement");
JButton button = new JButton("Cliquez-moi !");
// Ajoutons un écouteur au bouton
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Le bouton a été cliqué !");
JOptionPane.showMessageDialog(frame, "Hourra ! Bouton cliqué !");
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(button);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
Ce qui se passe ici :
- Nous créons une fenêtre (JFrame).
- Nous créons un bouton (JButton).
- Nous ajoutons un écouteur (classe anonyme pour éviter de multiplier les fichiers).
- Dans actionPerformed, nous affichons un message dans la console et une boîte de dialogue (JOptionPane).
- Nous ajoutons le bouton à la fenêtre et affichons la fenêtre à l'utilisateur.
Essayez vous-même de copier ce code, de l'exécuter — et de cliquer sur le bouton !
2. Classes anonymes et expressions lambda
Dans le code Java moderne, écrire des classes séparées pour un seul gestionnaire, c'est comme prendre un char pour aller acheter du pain. Il est bien plus pratique d'utiliser des classes anonymes ou des expressions lambda.
Classe anonyme
Nous avons déjà vu cette approche ci‑dessus :
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// code de traitement
}
});
Ici, la classe est déclarée sur place, sans nom.
Expression lambda (Java 8+)
Si l'interface de l'écouteur est fonctionnelle (c'est-à-dire qu'elle ne contient qu'une seule méthode abstraite), on peut utiliser une lambda :
button.addActionListener(e -> {
System.out.println("Bouton cliqué via une lambda !");
});
Ou même plus court si le corps tient sur une seule ligne :
button.addActionListener(e -> System.out.println("Lambda : bouton cliqué !"));
Cela ne réduit pas seulement le code, cela le rend aussi plus lisible. Fait pour les curieux : l'interface ActionListener est fonctionnelle, car elle ne contient qu'une seule méthode, actionPerformed.
4. Autres types d'événements
Le monde ne se limite pas aux boutons ! Dans Swing et AWT, presque chaque élément d'interface prend en charge ses propres événements. Voici les plus populaires :
Événements de souris : MouseListener et MouseAdapter
Pour réagir aux clics de souris, utilisez MouseListener :
button.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("Clic de souris sur le bouton !");
}
// Les autres méthodes peuvent rester vides
@Override public void mousePressed(MouseEvent e) {}
@Override public void mouseReleased(MouseEvent e) {}
@Override public void mouseEntered(MouseEvent e) {}
@Override public void mouseExited(MouseEvent e) {}
});
Écrire cinq méthodes vides pour un seul gestionnaire n'est pas très pratique. Pour cela, il existe MouseAdapter :
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("Clic de souris sur le bouton (via l'adaptateur) !");
}
});
MouseAdapter implémente toutes les méthodes de l'interface, et vous ne redéfinissez que celles dont vous avez besoin.
Événements clavier : KeyListener/KeyAdapter
Pour intercepter les frappes :
button.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
System.out.println("Touche enfoncée : " + e.getKeyChar());
}
});
Événements de modification de texte : DocumentListener
Pour les champs de texte (JTextField, JTextArea) il existe des écouteurs spécifiques :
JTextField textField = new JTextField();
textField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
System.out.println("Texte modifié : " + textField.getText());
}
@Override public void removeUpdate(DocumentEvent e) {}
@Override public void changedUpdate(DocumentEvent e) {}
});
Au fait, pour la plupart des événements, il existe des adaptateurs afin d'éviter d'écrire des méthodes vides.
5. Pratique : mini-exemple avec une fenêtre et un bouton
Faisons une application GUI toute simple : une fenêtre avec un bouton qui, lorsqu'on clique dessus, incrémente un compteur et affiche un message.
Exemple : compteur de clics
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ClickCounterApp {
public static void main(String[] args) {
JFrame frame = new JFrame("Compteur de clics");
JButton button = new JButton("Cliquez-moi !");
JLabel label = new JLabel("Clics : 0");
// Compteur de clics (doit être final ou effectively final)
final int[] count = {0};
button.addActionListener(e -> {
count[0]++;
label.setText("Clics : " + count[0]);
});
frame.setLayout(new FlowLayout());
frame.add(button);
frame.add(label);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(250, 100);
frame.setVisible(true);
}
}
Explication :
- Nous créons une fenêtre, un bouton et un label pour afficher le compteur.
- Nous utilisons un tableau à un seul élément (final int[] count = {0};) pour contourner la contrainte des lambdas sur les variables final/effectivement final.
- À chaque clic sur le bouton, nous incrémentons le compteur et mettons à jour le texte du label.
Idée pour s'exercer : essayez de changer la couleur du bouton à chaque cinquième clic !
6. Comment fonctionne le modèle événementiel en interne
Jetons un coup d'œil sous le capot pendant une minute. Lorsque vous appelez :
button.addActionListener(listener);
le bouton (objet JButton) ajoute votre écouteur à une liste interne. Quand l'utilisateur clique sur le bouton, il se passe ceci :
- Le bouton crée un objet événement (ActionEvent).
- Le bouton parcourt la liste des écouteurs et appelle chez chacun la méthode actionPerformed.
- Votre gestionnaire exécute les actions nécessaires (par exemple, met à jour le label).
Point essentiel à retenir : chaque événement peut avoir un nombre quelconque d'écouteurs — vous pouvez ajouter plusieurs gestionnaires pour un même bouton, ils seront appelés à la suite.
7. Schéma visuel : traitement d'un événement
graph TD
A[L'utilisateur a cliqué sur le bouton] --> B{JButton}
B --> C[Créer ActionEvent]
C --> D[Appeler actionPerformed pour tous les écouteurs]
D --> E[Le gestionnaire exécute les actions]
7. Erreurs typiques avec les événements dans Swing et AWT
Erreur n° 1 : vous avez oublié d'enregistrer l'écouteur. Le bouton ne réagit pas aux clics, car vous avez oublié d'appeler addActionListener.
Erreur n° 2 : travail lourd dans le gestionnaire. Si, dans votre gestionnaire d'événement, vous lancez une longue boucle ou téléchargez quelque chose sur Internet, l'interface va « geler ». Pour les tâches longues, utilisez des threads séparés ou SwingWorker.
Erreur n° 3 : tentative de modifier une variable depuis une lambda sans final/effectivement final. Dans une lambda, vous ne pouvez utiliser que des variables final ou « effectivement final ». Pour des compteurs, utilisez des tableaux ou des classes dédiées (AtomicInteger, si vous voulez faire ça proprement).
Erreur n° 4 : oubli de supprimer l'écouteur. Si vous supprimez un composant mais ne retirez pas l'écouteur, une fuite de mémoire est possible. Désabonnez les écouteurs quand le composant n'est plus nécessaire.
Erreur n° 5 : toutes les méthodes de l'interface ne sont pas implémentées. Si vous implémentez, par exemple, MouseListener directement, n'oubliez pas toutes les méthodes ! Mieux vaut utiliser des adaptateurs (MouseAdapter, KeyAdapter).
GO TO FULL VERSION