LECON 309


Les champs de texte : l'objet JTextField
Dans ce chapitre, nous allons voir l'objet qui va vous permettre de saisir des informations.
Celui-ci est très simple d'utilisation aussi...

Ne perdons pas de temps, allons-y !
Sommaire du chapitre :

  • Utilisation
  • Un objet plus restrictif : le JFormattedTextField
  • Contrôlez vos données post-saisie
  • Utiliser des regex
  • Contrôle du clavier : l'interface KeyListener
  • Ce qu'il faut retenir

Utilisation

Je pense que vous savez ce qu'il vous reste à faire... Donc, si ce n'est pas encore fait, créez-vous un nouveau projet avec les classes habituelles.

Comme le titre du chapitre l'indique, nous allons utiliser l'objet JTextField. Comme vous pouvez vous en douter, cet objet a lui aussi les méthodes de redimensionnement, de couleur de police...

De ce fait, je commence donc avec un exemple complet. Regardez et testez ce code :

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
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 javax.swing.JCheckBox;
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 = new JTextField("Valeur par défaut");
        private JLabel label = new JLabel("Un JTextField");
        
        
        public Fenetre(){
        
        this.setTitle("Animation");
        this.setSize(300, 300);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLocationRelativeTo(null);
 
        container.setBackground(Color.white);
        container.setLayout(new BorderLayout());
               
        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);
        
        top.add(label);
        top.add(jtf);
                
        container.add(top, BorderLayout.NORTH);
        this.setContentPane(container);
        this.setVisible(true);            
        }
}


Ce qui nous donne :
Vous pouvez voir que c'est très simple ! Vous pouvez saisir ce que vous voulez dans cette zone de texte.
Vous pouvez initialiser le contenu avec la méthode setText(String str) ou le récupérer avec la méthode getText().


Il existe toutefois un objet très ressemblant à celui-ci, un peu plus étoffé. En fait, cet objet permet d'avoir un JTextField formaté pour recevoir un certain type de saisie (date, pourcentage...).

Voyons ça tout de suite...

Un objet plus restrictif : le JFormattedTextField

Avec ce genre d'objet, vous allez pouvoir vous éviter beaucoup de contrôles et de cast sur le contenu de vos zones de texte...

Si vous avez essayé de récupérer le contenu du JTextField utilisé ci-dessus (lors du clic sur un bouton, par exemple...) vous avez dû vous rendre compte que le type de contenu lui était égal...

Mais, un jour sûrement, vous aurez besoin d'une zone de texte qui n'accepte qu'un certain type de donnée... Avec l'objet JFormattedTextField, nous nous en rapprochons, mais vous verrez que vous pouvez faire encore mieux !
Cet objet retourne une valeur uniquement si celle-ci correspond à ce que vous lui avez demandé de contenir, je m'explique : si vous voulez que votre zone de texte DOIVE contenir des entiers, ou des dates... c'est possible !
Par contre, ce contrôle se fait lorsque vous quittez le champ en question ! Vous pouvez saisir des lettres dans un objet n'acceptant que des entiers, mais la méthode getText() ne renverra RIEN car le contenu sera effacé si les données ne correspondent pas aux attentes ! !


Voici un code et deux exemples :

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
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.text.NumberFormat;
 
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
 
 
public class Fenetre extends JFrame {
        
        private JPanel container = new JPanel();
        private JFormattedTextField jtf = new JFormattedTextField(NumberFormat.getIntegerInstance());
        private JFormattedTextField jtf2 = new JFormattedTextField(NumberFormat.getPercentInstance());
        private JLabel label = new JLabel("Un JTextField");
        private JButton b = new JButton ("OK");
        
        public Fenetre(){
        
        this.setTitle("Animation");
        this.setSize(300, 300);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLocationRelativeTo(null);
 
        container.setBackground(Color.white);
        container.setLayout(new BorderLayout());
               
        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);
        
        jtf2.setPreferredSize(new Dimension(150, 30));
        
        b.addActionListener(new BoutonListener());
        
        top.add(label);
        top.add(jtf);
        top.add(jtf2);
        top.add(b);
                
       // container.add(top, BorderLayout.NORTH);
        this.setContentPane(top);
        this.setVisible(true);            
        }       
        
        class BoutonListener implements ActionListener{
 
                public void actionPerformed(ActionEvent e) {
                        System.out.println("TEXT : jtf " + jtf.getText());
                        System.out.println("TEXT : jtf2 " + jtf2.getText());
                }
                
        }
}


Exemple valide :



Exemple invalide :

Vous pouvez voir que notre objet met aussi en forme la saisie lorsque celle-ci est valide ! Celui-ci sépare les nombres 3 par 3 afin de facilité la lecture...


Voici les types de contenus que vous pouvez utiliser :
  • NumberFormat avec :
    • getIntegerInstance()
    • getPercentInstance()
    • getNumberInstance()
  • DateFormat avec :
    • getTimeInstance()
    • getDateInstance()
  • MessageFormat


Sans rentrer dans les détails, vous pouvez aussi utiliser un objet MaskFormatter qui permet d'avoir un format à taille fixe dans votre zone de texte. Ceci est très pratique lorsque vous voulez un numéro de téléphone, un numéro de sécurité sociale...
Vous devez définir ce format en paramètre dans le constructeur à l'aide de méta-caractères. Ceux-ci permettent de dire à votre objet MaskFormatter comment doit être constitué le futur contenu de votre zone de texte. Voici la liste de ces méta-caractères :
  • # indique un chiffre ;
  • ' indique un caractère d'échappement ;
  • U indique une lettre (les minuscules sont changées en majuscules) ;
  • L indique une lettre (les majuscules sont changées en minuscules) ;
  • A indique un chiffre ou une lettre ;
  • ? indique une lettre ;
  • * indique que tous les caractères sont acceptés ;
  • H indique que tous les caractères hexadécimaux sont acceptés (0->9, a->f ou A->F).

L'instanciation d'un tel objet peut lever une ParseException. Vous devrez donc l'entourer d'un bloc try{...}catch(ParseException e){...}.


Voici à quoi ressemblerait un format téléphonique :
Code : Java -
1
2
3
4
5
6
7
try{
   MaskFormatter tel = new MaskFormatter("## ## ## ## ##");
   //Ou encore
   MaskFormatter tel2 = new MaskFormatter("##-##-##-##-##");
   //Vous pouvez ensuite le passer à votre zone de texte
   JFormattedTextField jtf = new JFormattedTextField(tel2);
   }catch(ParseException e){e.printStackTrace();}


Vous pouvez vous rendre compte qu'il n'y a rien de compliqué...
Je vous donne tout de même un exemple de code permettant de saisir un numéro de téléphone français et un numéro de téléphone américain :

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
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.text.ParseException;
 
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.text.MaskFormatter;
 
 
public class Fenetre extends JFrame {
        
    private JPanel container = new JPanel();
    private JFormattedTextField jtf;
    private JFormattedTextField jtf2;
    private JLabel label = new JLabel("Téléphone FR   ");
    private JLabel label2 = new JLabel("Téléphone USA");
    private JButton b = new JButton ("OK");
    
    /**
     * Constructeur de l'objet 
     */
    public Fenetre(){
        
        this.setTitle("Animation");
        this.setSize(300, 150);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLocationRelativeTo(null);
 
        container.setBackground(Color.white);
        container.setLayout(new BorderLayout());
        
        try{
                  MaskFormatter tel = new MaskFormatter("##-##-##-##-##");
                  MaskFormatter telUSA = new MaskFormatter("###-####");
                  jtf = new JFormattedTextField(tel);
                  jtf2 = new JFormattedTextField(telUSA);
        }catch(ParseException e){
               e.printStackTrace();
        }
 
        
        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);
        
        jtf2.setPreferredSize(new Dimension(150, 30));
        
        b.addActionListener(new BoutonListener());
        
        top.add(label);
        top.add(jtf);
        top.add(label2);
        top.add(jtf2);
        top.add(b);
                
       // container.add(top, BorderLayout.NORTH);
        this.setContentPane(top);
        this.setVisible(true);            
     }       
        
        class BoutonListener implements ActionListener{
 
                public void actionPerformed(ActionEvent e) {
                        System.out.println("Téléphone FR  : " + jtf.getText());
                        System.out.println("Téléphone USA : " + jtf2.getText());
                }
                
        }
}

Vous pouvez constater qu'avec le méta-caractère utilisé avec notre objet MaskFormatter, nous sommes obligé de saisir des chiffres !


Et voici le résultat lorsque nous cliquons sur le bouton :


Je ne sais pas pour le numéro américain, mais le numéro de téléphone français est loin d'être un numéro de téléphone valide !


Ah ! je savais que vous alliez remarquer ce petit détail, de taille je vous l'accorde.
Nous voilà confrontés à un problème qui vous hantera tant que vous programmerez : L'intégrité de vos données !

Comme démontré ci-dessus, vous pouvez aider le plus possible l'utilisateur sur ce qu'il doit renseigner comme données dans des champs, vous ne devrez JAMAIS FAIRE UNE CONFIANCE AVEUGLE EN CELLES-CI !
C'est simple : dans ma boîte, on part du principe de ne jamais faire confiance à l'utilisateur !

Nous sommes obligés de faire une multitude de contrôles en plus, mais les applications ont le mérite d'être un tant soit peu sécurisées...
Qu'est-ce que nous pouvons faire dans le cas de ta saisie ?

En réalité, beaucoup de choses :
  • tester chaque élément de votre numéro ;
  • tester le numéro en entier ;
  • dans le cas ou vous n'utilisez pas de MaskFormatter, vérifier en plus que les saisies soient numériques ;
  • utiliser une expression régulière ;
  • empêcher la saisie d'un type de caractères ;
  • ...


En gros, vous devrez vérifier l'intégrité de vos données et, dans le cas qui nous intéresse, l'intégrité de vos chaînes de caractères, pendant ou après la saisie !
D'ailleurs, c'est ce que je vous propose de faire, pas plus tard que maintenant !

Contrôlez vos données post-saisie

Afin de voir comment contrôler au mieux vos données, nous allons travailler avec un JFormattedTextField acceptant tous types de caractères. Voici donc notre code :

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
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.text.ParseException;
 
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.text.MaskFormatter;
 
 
public class Fenetre extends JFrame {
        
    private JPanel container = new JPanel();
    private JFormattedTextField jtf;
    private JFormattedTextField jtf2;
    private JLabel label = new JLabel("Téléphone FR   ");
    private JButton b = new JButton ("OK");
    
    /**
     * Constructeur de l'objet 
     */
    public Fenetre(){
        
        this.setTitle("Animation");
        this.setSize(300, 150);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLocationRelativeTo(null);
 
        container.setBackground(Color.white);
        container.setLayout(new BorderLayout());
        
        try{
                  MaskFormatter tel = new MaskFormatter("**-**-**-**-**");
                  jtf = new JFormattedTextField(tel);
        }catch(ParseException e){
               e.printStackTrace();
        }
 
        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);
                
        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());
        }  
    }
}


Maintenant, vous pouvez saisir n'importe quoi dans ce qui devait être un numéro de téléphone.
Il reste tout de même une restriction sur le nombre de caractères que doit prendre le champ, ici 10, mais, mis à part ça, vous êtes libres de saisir ce que vous voulez :

Première approche



Une méthode de contrôle, un peu compliquée au final, consisterait à exploser la chaîne de caractères grâce à la méthode split(String regex) et tester les éléments un par un...
Cette méthode, split(String regex) , permet de créer un tableau de String à partir d'une chaîne de caractères en l'explosant par rapport à l'expression régulière passée en paramètre. Un exemple est toujours mieux :
Code : Java -
1
2
3
4
5
6
String str = "Je-suis-un-ZérO";
String[] tab = str.split("-");//Donne un tableau à 4 entrées
//tab[0] vaut "Je"
//tab[1] vaut "suis"
//tab[2] vaut "un"
//tab[3] vaut "ZérO"

Le paramètre de cette méthode n'est pas une chaîne de caractères banale ! Il s'agit en fait d'une expression régulière. Nous allons y venir.


Voici une façon de faire, un peu barbare mais elle fonctionne :

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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.text.ParseException;
 
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.text.MaskFormatter;
 
 
public class Fenetre extends JFrame {
        
    private JPanel container = new JPanel();
    private JFormattedTextField jtf;
    private JFormattedTextField jtf2;
    private JLabel label = new JLabel("Téléphone FR   ");
    private JButton b = new JButton ("OK");
    
    /**
     * Constructeur de l'objet 
     */
    public Fenetre(){
        
        this.setTitle("Animation");
        this.setSize(300, 150);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLocationRelativeTo(null);
 
        container.setBackground(Color.white);
        container.setLayout(new BorderLayout());
        
        try{
                  MaskFormatter tel = new MaskFormatter("**-**-**-**-**");
                  jtf = new JFormattedTextField(tel);
        }catch(ParseException e){
               e.printStackTrace();
        }
 
        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);
                
        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());
                String[] tab = jtf.getText().split("-");
 
                //On contrôle le numéro de téléphone
                //-------------------------------------
                if(!controleData(tab)){
                       System.out.println("Numéro erroné ! ");
                       jtf.setText("");
                }
                else{
                       System.out.println("Numéro de téléphone OK ! ");
                }
        }  
        
        /**
         * Méthode qui va contrôler la saisie
         * @param data
         * @return Boolean
         */
        private boolean controleData(String[] data){
               int i = 0;
               
               //On balaye tout le contenu du tableau et on vérifie 
               //que les données sont conformes à nos attentes
               while(i < data.length){
                       
                       switch(i){
                               
                               //Le premier élément doit être numérique et 
                               //égal à 01 ou 02 ou 03 ou 04 ou 05 ou 06 ou 08 ou 09
                               case 0:
                                      try {
                                                     int j = Integer.parseInt(data[i]);
                                                     if(j < 1 || j > 6 && j != 8 && j != 9)
                                                             return false;
                                              } catch (NumberFormatException e) {
                                                     return false;
                                              }
                               
                                      break;
                                      
                               //Les autres chiffres doivent être compris entre 00 et 99 INCLUS
                               //Je ne sais pas si ça marche réellement comme ça, mais c'est pour l'exemple...
                               case 1:
                               case 2:
                               case 3:
                               case 4:
                                      try {
                                                     int j = Integer.parseInt(data[i]);
                                                     if(j < 0 || j > 99)
                                                             return false;
                                              } catch (NumberFormatException e) {
                                                     return false;
                                              }
                                      break;
                       }
                       
                       i++;
               }
               
               return true;
        }
    }
}


Ce qui nous donne :

Un peu fastidieux comme façon de contrôler !
Imaginez un peu que vous ayez une multitude de champs à vérifier... Une sacré galère au final !
Allez, dis-nous tout, on te connaît maintenant...

Personnellement, je trouve qu'utiliser des expressions régulières (ou regex) permet plus de souplesse et une économie de code assez conséquente.

Utiliser des expressions régulières



Comme vous avez pu le constater lors de la lecture du tuto de M@teo, les regex permettent de faire énormément de choses et, dans notre cas, de nous simplifier les contrôles de saisie de notre JFormattedTextField.

Maintenant, afin de pouvoir contrôler la saisie, nous allons devoir définir la regex.
Comme mis dans les commentaires du code précédent, je pars du principe qu'un numéro de téléphone est composé comme suit :
  • un 0 en tout premier chiffre ;
  • un chiffre qui peut être : 1, 2, 3, 4, 5, 6, 8, 9 ;
  • ensuite, 4 blocs composés d'un "-" suivi d'un nombre compris entre 00 et 99 inclus.


Si vous avez bien suivi le tuto de M@teo sur les regex, vous devez avoir une regex qui ressemble à ça :
#^0[0-689](-[0-9]{2}){2}$#
ou à ça :
#^0[0-689](-[\d]{2}){2}$#

Ces deux regex sont tout à fait correctes pour une application PHP, mais elles ne fonctionneront pas avec une application Java. Ceci pour deux raisons.
  • En Java, il n'y a pas besoin de délimiteurs. Vous pouvez donc enlever les deux "#". Ceci concerne les deux regex.
  • Ce point, par contre, ne concerne que la deuxième. Le caractère "\", est utilisé comme caractère d'échappement, ceci afin de dé-spécialiser des caractères comme "\n", "\r"... La classe abrégée "\d", correspondant à un chiffre, ne fonctionnera donc pas.


Afin de pouvoir utiliser les classes abrégées dans une regex, il faut faire en sorte que le backslash de la classe abrégée soit interprété comme tel et non comme un caractère d'échappement.
Comment ?

Il faut tout simplement échapper le caractère d'échappement...
Ce qui nous donne : ^0[0-689](-[\\d]{2}){2}$
Le premier backslash échappe le second, ce qui a pour conséquence que celui-ci est interprété comme un backslash tout ce qu'il y a de plus normal et ainsi que notre classe abrégée fonctionne !

Maintenant, nous sommes parés pour utiliser des regex...

Utiliser des regex

Avant de nous lancer tête baissée dans l'utilisation des regex en Java, vous devez savoir que vous pouvez procéder de deux façon différentes :
  • en utilisant un objet String ;
  • en utilisant l'API regex qui se trouve dans le package java.util.regex .

Les regex et l'objet String



Vous allez voir que c'est simplissime.
Nous avons donc convenu de la regex à utiliser afin de contrôler nos saisies de numéros de téléphone.
Pour mémoire : ^0[0-689](-[\\d]{2}){4}$

Il ne nous reste plus qu'à dire au contenu de notre JFormattedTextField qu'il doit correspondre à celle-ci.

Cette opération se fait grâce à la méthode matches(String regex) , qui renvoie true si notre chaîne correspond à la regex ou false, dans le cas contraire.

Voici le code qui met en oeuvre cette démarche :
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
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.text.ParseException;
 
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.text.MaskFormatter;
 
 
public class Fenetre extends JFrame {
        
    private JPanel container = new JPanel();
    private JFormattedTextField jtf;
    private JFormattedTextField jtf2;
    private JLabel label = new JLabel("Téléphone FR   ");
    private JButton b = new JButton ("OK");
    
    /**
     * Constructeur de l'objet 
     */
    public Fenetre(){
        
        this.setTitle("Animation");
        this.setSize(300, 150);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLocationRelativeTo(null);
 
        container.setBackground(Color.white);
        container.setLayout(new BorderLayout());
        
        try{
                  MaskFormatter tel = new MaskFormatter("**-**-**-**-**");
                  jtf = new JFormattedTextField(tel);
        }catch(ParseException e){
               e.printStackTrace();
        }
 
        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);
                
        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(jtf.getText().matches("^0[0-689](-[\\d]{2}){4}$")){
                       System.out.println("Numéro de téléphone OK ! !");
                }
                else{
                       System.out.println("Numéro de téléphone PAS OK ! !");
                }
        }  
    }
}


Ainsi que deux captures d'écran afin de bien vous montrer le résultat :


Vous pouvez voir que c'est très simple à utiliser...
Je profite de cet aparté sur les regex afin d'introduire une autre méthode : replaceAll(String regex, String remplacement) .
Grâce à cette dernière, vous pourrez changer tous les caractères, ou chaînes de caractères correspondant à la regex passée en premier paramètre par la chaîne passée en deuxième paramètre.

Si nous appliquons ceci à notre exemple, en partant du principe que, si la saisie du numéro de téléphone est erronée, on remplace tous les caractères par des zéros, cela nous donne :

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
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.text.ParseException;
 
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.text.MaskFormatter;
 
 
public class Fenetre extends JFrame {
        
    private JPanel container = new JPanel();
    private JFormattedTextField jtf;
    private JFormattedTextField jtf2;
    private JLabel label = new JLabel("Téléphone FR   ");
    private JButton b = new JButton ("OK");
    
    /**
     * Constructeur de l'objet 
     */
    public Fenetre(){
        
        this.setTitle("Animation");
        this.setSize(300, 150);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLocationRelativeTo(null);
 
        container.setBackground(Color.white);
        container.setLayout(new BorderLayout());
        
        try{
                  MaskFormatter tel = new MaskFormatter("**-**-**-**-**");
                  jtf = new JFormattedTextField(tel);
        }catch(ParseException e){
               e.printStackTrace();
        }
 
        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);
                
        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(jtf.getText().matches("^0[0-689](-[\\d]{2}){4}$")){
                       System.out.println("Numéro de téléphone OK ! !");
                }
                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);
                }
        }  
    }
}


Et le résultat :


Je pense que cette méthode pourrait vous être utile : on ne sait jamais...

Maintenant, nous allons voir comment avoir le même résultat avec l'API regex.

Les regex et l'API regex



Avec cette méthode, nous allons utiliser deux nouveaux objets :
  • un objet Pattern qui va contenir notre regex et qui va retourner un objet contenant le résultat de la comparaison ;
  • l'objet Matcher qui est le résultat de la comparaison de la regex avec la chaîne à tester.


Vous pourrez voir que ces objets sont très simples à utiliser.
L'utilisation de l'objet Pattern se fait comme ceci :
Pattern pattern = Pattern.compile("^0[0-689](-[\\d]{2}){4}$");

Cette instruction déclare et initialise notre objet Pattern, celui-ci est maintenant prêt à tester des chaînes de caractères !

Le test d'une chaîne par rapport à une regex via l'objet Pattern se fait grâce à la méthode matcher(String string) : il ne s'agit pas de la regex en paramètre, mais de la chaîne à tester !

Comme je vous l'ai dit plus haut, la comparaison via l'objet Pattern renvoie un objet Matcher qui, lui, contient le résultat du test (vrai ou faux) que nous pourrons récupérer grâce à la méthode matches() .

Voici un exemple simple :

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
String[] tab = {"abcdef", "16464","1v"};
//Regex qui vérifie si la chaîne ne contient que des chiffres
Pattern pattern = Pattern.compile("\\d+");
               
for(String str : tab){
        Matcher matcher = pattern.matcher(str);       
        System.out.print("Teste sur '"+str+"' : ");
        //On regarde le résultat
        if(matcher.matches())
               System.out.println("OK ! ! ");
        else
               System.out.println("PAS OK ! ! ");
}


Et voilà le résultat :






Rien de plus simple, n'est-ce pas ?
On voit bien que le résultat est le même, mais... l'intérêt ?

Je vais y venir, mais avant de vous expliquer pourquoi il est intéressant de passer par l'objet Pattern, vous devez savoir que vous pouvez ne pas utiliser l'objet Matcher.

Je vois bien que vous êtes un peu dans le flou...
Reprenez ce que je vous ai dit plus haut : l'objet Pattern retourne un objet Matcher.
Par conséquent, vous pouvez gagner un peu de mémoire en ne déclarant pas d'objet Matcher mais en vous servant de celui que vous retourne l'objet Pattern !

Voilà le code précédent mettant en oeuvre cette démarche :
Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
String[] tab = {"abcdef", "16464","1v"};
//Regex qui vérifie si la chaîne ne contient que des chiffres
Pattern pattern = Pattern.compile("\\d+");
               
for(String str : tab){
        System.out.print("Teste sur '"+str+"' : ");
        //On regarde le résultat, et plus besoin d'instancier un objet Matcher
        if(pattern.matcher(str).matches())
               System.out.println("OK ! ! ");
        else
               System.out.println("PAS OK ! ! ");
}


Je ne vous mets pas de capture d'écran car elle est identique à la précédente !
Tu ne voudrais pas nous expliquer ça ?

Bien sûr...

En fait, repensez à la pile d'exécution lorsque nous avons abordé les threads.
Ici, c'est la même chose. L'instruction pattern.matcher(str).matches() se découpe en deux.
Lors de l'exécution, la JVM va lire cette ligne, elle voit qu'il y a plusieurs appels de méthode : par conséquent, elle va invoquer celle qui doit être exécutée en premier, faire ce qu'elle a à faire, puis passer à la suivante...

Voilà un schéma résumant la situation :

La flèche indique le sens dans lequel la JVM va lire l'instruction et l'exécuter.
  • Elle va lire le pattern.matcher(str) qui, comme je vous l'ai déjà dit, retourne un objet Matcher. Étape 1.
  • Ensuite, elle va exécuter la méthode matches() qui est une méthode de l'objet Matcher. Étape 2.

Lors de l'étape 2, c'est comme si vous aviez un objet Matcher à la place de l'instruction correspondant à l'étape 1... La méthode matches() peut donc être invoquée !

Ainsi vous gagnez en objets, en lignes de codes et en mémoire...

Maintenant, la réponse à la question que vous vous posez :
Pourquoi utiliser l'objet Pattern alors que l'objet String gère les regex ?


En fait, les deux méthodes sont équivalentes...
C'est vrai que dans notre exemple, nous ne contrôlons qu'un champ. Mais ce ne sera peut-être pas toujours le cas...

Imaginez-vous en train de développer un progiciel de gestion avec, sur une de ses IHM, 35 champs de saisie qui doivent contenir des codes spécifiques à une norme... La solution des regex semble la plus optimisée mais vous n'allez pas répéter la regex pour tous les contrôles de tous les champs ! !
Le jour où votre chef va vous demander de mettre à jour ladite expression car un nouveau code vient de faire son apparition, vous allez sûrement oublier un ou plusieurs champs !

Le fait d'utiliser un objet Pattern, dans ce cas, permet de centraliser la donnée qui va vous servir à contrôler vos champs et, au lieu de faire X modifications, vous n'avez qu'à changer l'objet Pattern.

Mais il y a une autre alternative



Vous pouvez aussi stocker votre regex dans un objet de type String et utiliser ce dernier dans tous vos contrôles, en utilisant la méthode matches(String regex) . Le but final étant de centraliser les données dont vous vous servirez pour faire vos contrôles et que celles-ci soient facilement modifiables sans risque d'oubli.

En bref, ces deux méthodes sont équivalentes.
Je vous ai un peu induits en erreur, mais il était important que vous connaissiez l'API regex.

Vous devez savoir tout de même que lorsque vous utilisez la méthode matches(String regex) de l'objet String , celui-ci fait appel à l'objet Pattern dans cette méthode...
De même, lorsque vous utilisez la méthode replaceAll(String regex, String remplacement) , celle-ci invoque l'expression Pattern.compile(regex).matcher(str).replaceAll(repl) .

Pour finir sur l'utilisation des regex



Vous pouvez utiliser la méthode qui vous convient, mais gardez en tête qu'il faut que vos contrôles soient facilement modifiables !

Bon, vous venez de voir comment on peut gérer les saisies après les avoir tapées. Maintenant, je vous propose de voir comment intercepter les saisies des utilisateurs avant que votre composant ne soit affecté de quelque valeur que ce soit !

Contrôle du clavier : l'interface KeyListener

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 -
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 -
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 -
1
2
3
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 -
1
2
3
4
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 -
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...

Ce qu'il faut retenir

  • Par défaut, un JTextField accepte tous types de caractères.
  • Un JFormattedTextField est, pour simplifier, un JTextField plus restrictif.
  • On peut restreindre la saisie d'un de ces objets en utilisant l'objet MaskFormatter.
  • Pour contrôler les événements clavier, l'utilisation d'une implémentation de l'interface KeyListener est de mise.
  • Vous pouvez utiliser une classe dérivée de KeyAdapter à la place d'une implémentation de KeyListener.
  • Une classe anonyme est propre à un objet.
  • Une classe anonyme est automatiquement déclarée final : donc pas d'héritage possible.
  • Vous pouvez utiliser les regex avec l'objet String ou avec l'objet Pattern.
Vous avez vu que cet objet est très simple à utiliser...
La plupart des composants que vous pouvez utiliser dans un formulaire de base ont été vus.
Je vous propose donc de faire un détour vers ce qu'on appelle les Applets.


0 comments to "LECON 309"

Post a Comment

Powered by Blogger.

About This Blog

Aller au debut de la page