Tout est dans le titre de cette sous-partie !
Vous connaissez déjà :
- l'interface MouseListener qui interagit avec votre souris ;
- l'interface ActionListener qui interagit lors d'un clic sur un composant ;
- l'interface ItemListener qui écoute les événements sur une liste déroulante.
Voici à présent l'interface
KeyListener.
Comme dit dans le titre, celle-ci va vous permettre d'intercepter les événements clavier lorsqu'on :
- presse une touche ;
- relâche une touche ;
- tape sur une touche.
Vous savez ce qu'il vous reste à faire : créer un implémentation de cette interface dans notre projet.
Créez une classe interne implémentant cette interface et utilisez l'astuce d'Eclipse pour générer les méthodes à implémenter.
Vous constatez que celle-ci a trois méthodes :
- keyPressed(KeyEvent event) : appelée lorsqu'on presse une touche ;
- keyReleased(KeyEvent event) : appelée lorsqu'on relâche une touche. C'est à ce moment que le composant se voit affecter la valeur de la touche ;
- keyTyped(KeyEvent event) : appelée entre les deux méthodes citées ci-dessus.
Comme vous devez vous en douter, l'objet
KeyEvent va nous permettre d'obtenir des informations sur les touches qui ont été utilisées... Parmi celles-ci, nous allons utiliser :
- getKeyCode() : retourne le code de la touche ;
- getKeyChar() : retourne le caractère correspondant à la touche.
Vous pouvez aussi savoir si certaines touches de contrôle ont été utilisées (
SHIFT,
CTRL...), connaître le composant à l'origine de l'événement... Nous n'en parlerons pas ici mais ce genre d'informations sont faciles à trouver :
Google.
Pour des raisons de simplicité, nous n'allons pas utiliser de
JFormattedTextField mais un
JTextField sans
MaskFormatter. Ainsi, nous n'aurons pas à nous préoccuper des "
-" de notre champ.
Pour commencer, nous allons voir dans quel ordre se passent les événements clavier.
Voici le code source que nous allons utiliser, il est presque identique aux précédents, rassurez-vous :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109 | import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.text.ParseException;
import java.util.regex.Pattern;
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.text.MaskFormatter;
public class Fenetre extends JFrame {
private JPanel container = new JPanel();
private JTextField jtf;
private JLabel label = new JLabel("Téléphone FR ");
private JButton b = new JButton ("OK");
//Création de l'objet pattern dont nous allons nous servir pour
//tester le contenu de notre champ
private Pattern regex;
/**
* Constructeur de l'objet
*/
public Fenetre(){
//On initialise notre pattern
this.regex = Pattern.compile("^0[0-689](-[\\d]{2}){4}$");
this.setTitle("Animation");
this.setSize(300, 150);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
container.setBackground(Color.white);
container.setLayout(new BorderLayout());
jtf = new JTextField();
JPanel top = new JPanel();
Font police = new Font("Arial", Font.BOLD, 14);
jtf.setFont(police);
jtf.setPreferredSize(new Dimension(150, 30));
jtf.setForeground(Color.BLUE);
//On ajoute l'écouteur à notre composant
jtf.addKeyListener(new ClavierListener());
b.addActionListener(new BoutonListener());
top.add(label);
top.add(jtf);
top.add(b);
this.setContentPane(top);
this.setVisible(true);
}
class BoutonListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
System.out.println("Téléphone FR : " + jtf.getText());
if(regex.matcher(jtf.getText()).matches()){
System.out.println("Numéro de téléphone OK ! !");
String str = jtf.getText().replaceAll("\\d", "X");
System.out.println("Après remplacement : " + str);
}
else{
System.out.println("Numéro de téléphone PAS OK ! !");
//Si la saisie est erronée
//On remplace tous les caractères alphabétiques par des 0
String str = jtf.getText().replaceAll("\\w", "0");
jtf.setText(str);
System.out.println("Après remplacement : " + str);
}
}
}
/**
* Implémentataion de l'interface KeyListener
* @author CHerby
*/
class ClavierListener implements KeyListener{
public void keyPressed(KeyEvent event) {
System.out.println("Code touche pressée : " + event.getKeyCode() +
" - caractère touche pressée : " + event.getKeyChar());
}
public void keyReleased(KeyEvent event) {
System.out.println("Code touche relâchée : " + event.getKeyCode() +
" - caractère touche relâchée : " + event.getKeyChar());
}
public void keyTyped(KeyEvent event) {
System.out.println("Code touche tapée : " + event.getKeyCode() +
" - caractère touche tapée : " + event.getKeyChar());
}
}
}
|
Voici un petit jeu d'essai de ce code :
C'est vrai que les événements vont tellement vite que n'avez pas le temps de voir le défilement.
Vous pouvez ajouter une pause à la fin de chaque méthode de l'implémentation afin de mieux voir l'ordre d'exécution, comme ceci :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 | class ClavierListener implements KeyListener{
public void keyPressed(KeyEvent event) {
System.out.println("Code touche pressée : " + event.getKeyCode() +
" - caractère touche pressée : " + event.getKeyChar());
pause();
}
public void keyReleased(KeyEvent event) {
System.out.println("Code touche relâchée : " + event.getKeyCode() +
" - caractère touche relâchée : " + event.getKeyChar());
pause();
}
public void keyTyped(KeyEvent event) {
System.out.println("Code touche tapée : " + event.getKeyCode() +
" - caractère touche tapée : " + event.getKeyChar());
pause();
}
}
private void pause(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
|
Maintenant, vous pouvez voir dans quel ordre les événements du clavier sont gérés.
En premier lorsqu'on presse la touche, en second lorsque celle-ci est tapée et enfin relâchée.
Dans le cas qui nous intéresse, nous voulons que lorsque l'utilisateur saisit un caractère non autorisé, celui-ci soit retiré automatiquement de la zone de saisie. Pour cela, nous allons faire un traitement spécifique dans la méthode
keyReleased(KeyEvent event).
Si vous avez fait beaucoup de tests de touches, vous avez dû remarquer que les codes des touches correspondant aux chiffres du pavé numérique sont compris entre
96 et
105 !
À partir de là, c'est simple : il vous suffit de supprimer le caractère tapé de la zone de saisie si le code de celui-ci n'est pas compris dans cette tranche de code.
Mais voilà, un problème se pose avec cette méthode : pour celles et ceux qui ont un PC portable, sans pavé numérique, la saisie sera impossible alors que vous pouvez avoir des chiffres en faisant
MAJ + (
& ou
é ou
' ou
( ou encore
- ...).
À cause de ce souci, nous allons opter pour une autre méthode. Nous allons créer une méthode ayant comme type de renvoi un booléen et qui va se charger de nous dire si la saisie est numérique ou non. Comment ? Tout simplement en faisant un
Integer.parseInt(value);, le tout enveloppé dans un
try{...} catch(NumberFormatException ex){}.
Si nous essayons de convertir un caractère "a" en entier, l'exception sera levée et nous retournerons FAUX, et VRAI dans le cas contraire...
Attention : la méthode parseInt() prendra un String en paramètre ! La méthode getKeyChar(), elle, nous renvoie un char... Il faudra penser à faire la conversion...
Voici notre implémentation quelque peu modifiée :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 | class ClavierListener implements KeyListener{
public void keyReleased(KeyEvent event) {
if(!isNumeric(event.getKeyChar())){
jtf.setText(jtf.getText().replace(String.valueOf(event.getKeyChar()), ""));
}
}
//Inutile de redéfinir ces méthodes
//Nous n'en avons plus l'utilité !
public void keyPressed(KeyEvent event) {}
public void keyTyped(KeyEvent event) {}
/**
* Retourne vrai si le paramètre est numérique
* Retourne Faux dans le cas contraire
* @param carac
* @return Boolean
*/
private boolean isNumeric(char carac){
try {
Integer.parseInt(String.valueOf(carac));
} catch (NumberFormatException e) {
return false;
}
return true;
}
}
|
Vous pouvez voir que les lettres simples sont désormais interdites à la saisie =>
Mission accomplie !
Les caractères spéciaux comme "ô", "ï"... ne sont pas pris en charge par cette méthode... Par conséquent, leur saisie reste possible

. Mais c'est à ça que sert notre contrôle avec la regex

.
Par contre, je ne sais pas pour vous mais, le fait d'avoir deux méthodes sans corps me dérange un peu...
On peut éviter ce genre de chose ?

Comment ? Puisque nous devons redéfinir toutes les méthodes de l'interface !
Tout à fait. Il existe une classe,
KeyAdapter, que vous pouvez étendre (par là comprenez : créez une classe héritée) et ne redéfinir que la méthode qui nous intéresse, et donc ADIEU aux deux méthodes vides !
Vous pouvez bien entendu créer un classe interne héritée de
KeyAdapter et redéfinir la méthode
keyReleased(KeyEvent event) mais je vais en profiter pour vous montrer une autre méthode.
Utiliser les classes anonymes
Il n'y a rien de compliqué dans cette manière de faire, mais je me rappelle avoir été déconcerté lorsque je l'ai vue pour la première fois...
En fait, les classes anonymes sont le plus souvent utilisées pour la gestion d'événements ponctuels, là où créer une classe pour un seul traitement est trop lourd...
Notre exemple est très bien pour les classes anonymes : nous n'avons qu'un champ et une redéfinition de méthode. Maintenant, adieu à l'implémentation que vous avez codée tout à l'heure, nous allons dire à notre
JTextField qu'une instance d'une classe anonyme va l'écouter. Attention les yeux, ça risque de piquer un peu :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 | import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.regex.Pattern;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class Fenetre extends JFrame {
private JPanel container = new JPanel();
private JTextField jtf;
private JLabel label = new JLabel("Téléphone FR ");
private JButton b = new JButton ("OK");
//Création de l'objet pattern dont nous allons nous servir pour
//tester le contenu de notre champ
private Pattern regex;
/**
* Constructeur de l'objet
*/
public Fenetre(){
//On initialise notre pattern
this.regex = Pattern.compile("^0[0-689](-[\\d]{2}){4}$");
this.setTitle("Animation");
this.setSize(300, 150);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
container.setBackground(Color.white);
container.setLayout(new BorderLayout());
jtf = new JTextField();
JPanel top = new JPanel();
Font police = new Font("Arial", Font.BOLD, 14);
jtf.setFont(police);
jtf.setPreferredSize(new Dimension(150, 30));
jtf.setForeground(Color.BLUE);
//**********************************************
//Voilà notre classe anonyme
//**********************************************
jtf.addKeyListener(new KeyAdapter(){
public void keyReleased(KeyEvent event) {
System.out.println("keyReleased dans une classe anonyme");
if(!isNumeric(event.getKeyChar())){
jtf.setText(jtf.getText().replace(String.valueOf(event.getKeyChar()), ""));
}
}
private boolean isNumeric(char carac){
try {
int i =Integer.parseInt(String.valueOf(carac));
} catch (NumberFormatException e) {
return false;
}
return true;
}
});
//**********************************************
b.addActionListener(new BoutonListener());
top.add(label);
top.add(jtf);
top.add(b);
this.setContentPane(top);
this.setVisible(true);
}
class BoutonListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
System.out.println("Téléphone FR : " + jtf.getText());
if(regex.matcher(jtf.getText()).matches()){
System.out.println("Numéro de téléphone OK ! !");
String str = jtf.getText().replaceAll("\\d", "X");
System.out.println("Après remplacement : " + str);
}
else{
System.out.println("Numéro de téléphone PAS OK ! !");
//Si la saisie est erronée
//On remplace tous les caractères alphabétiques par des 0
String str = jtf.getText().replaceAll("\\w", "0");
jtf.setText(str);
System.out.println("Après remplacement : " + str);
}
}
}
}
|
Ce code a le même effet que le précédent : la seule chose qui change, c'est qu'au lieu d'avoir une implémentation de l'interface
KeyListener ou d'avoir une classe interne héritée de
KeyAdapter, nous utilisons une classe anonyme au moment où nous définissons l'écouteur pour notre composant.
Décortiquons tout ça...
Nous avons toujours notre instruction
jtf.addKeyListener();, sauf qu'au lieu de lui donner une instance habituelle, nous créons une classe qui redéfinit la méthode qui nous intéresse. Ceci en faisant :
Code : Java | new KeyAdapter(){
//Redéfinition de la classe
};
|
De ce fait, vous pouvez aussi créer une instance de type
KeyAdapter en utilisant une classe interne comme implémentation :
Code : Java | KeyAdapter kAdapter = new KeyAdapter(){
//Redéfinissions de la classe
};
jtf.addKeyListener(kAdapter);
|
Attention : vous avez dû le remarquer mais je préfère le dire, dans ce type de déclaration, le ";" final se trouve après l'accolade fermante de la classe anonyme ! !
L'une des particularités de cette façon de faire, c'est que cet écouteur n'écoutera que ce composant !
Pourquoi on appelle ça une classe anonyme ?
C'est simple : le fait de procéder de cette manière revient à créer une classe héritée sans être obligés de créer de façon explicite ladite classe.
L'héritage se fait automatiquement, en fait, le code ci-dessus reviendrait à faire :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 | class Fenetre extends JFrame{
//...
jtf.addKeyListener(new KeyAdapterBis());
//...
public class KeyAdapterBis extends Keyadapter{
public void keyReleased(KeyEvent event) {
System.out.println("keyReleased dans une classe anonyme");
if(!isNumeric(event.getKeyChar())){
jtf.setText(jtf.getText().replace(String.valueOf(event.getKeyChar()), ""));
}
}
private boolean isNumeric(char carac){
try {
int i =Integer.parseInt(String.valueOf(carac));
} catch (NumberFormatException e) {
return false;
}
return true;
}
}
}
|
Mais la classe créée n'a pas de nom ! L'héritage se fait de façon tacite. On bénéficie donc de tous les avantages de la classe mère en ne redéfinissant que la méthode qui nous intéresse.
Vous devez savoir aussi que les classes anonymes peuvent être utilisées pour implémenter des interfaces. Ce code est tout aussi équivalent aux précédents :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | jtf.addKeyListener(new KeyListener(){
public void keyReleased(KeyEvent event) {
System.out.println("keyReleased dans une classe anonyme");
if(!isNumeric(event.getKeyChar())){
jtf.setText(jtf.getText().replace(String.valueOf(event.getKeyChar()), ""));
}
}
private boolean isNumeric(char carac){
try {
int i =Integer.parseInt(String.valueOf(carac));
} catch (NumberFormatException e) {
return false;
}
return true;
}
//Méthode de l'interface a redéfinir
public void keyPressed(KeyEvent e) {}
public void keyTyped(KeyEvent e) {}
});
|
Les classes anonymes sont soumises aux mêmes lois que les classes
normales :
- utilisation des méthodes non redéfinies de la classe mère ;
- OBLIGATION de redéfinir TOUTES LES MÉTHODES d'une interface ;
- OBLIGATION de redéfinir les méthodes abstraites d'une classe abstraite.
Cependant, elles ont des restrictions de par leur essence et par là, je veux dire leur rôle et leur but :
- ces classes ne peuvent pas être déclarées abstract !
- elles ne peuvent pas non plus être static ;
- elles ne peuvent pas définir de constructeur ;
- elles sont automatiquement déclarées final : impossible de dériver de cette classe, donc héritage impossible !
Encore une chose avant de terminer ce chapitre sur le JTextField : il existe encore deux objets fonctionnant de la même manière :
- le JPasswordField : utilisé pour les saisies de mots de passe ;
- le JTextArea : utilisé, lui, pour les saisies multilignes.
Essayez-les, vous verrez que leur utilisation est très simple.
Bon, après toutes ces émotions, je crois qu'un petit topo s'impose...
