LECON 305


Interaction bouton(s) - application
Nous y voilà !
Dans ce chapitre, votre objet Bouton pourra enfin communiquer avec votre application !
Je pense tout de même que le chapitre précédent à dû vous plaire...

Nous allons voir comment faire, mais aussi qu'il y a plusieurs façons de faire...
Ensuite, il ne tiendra qu'à vous de bien choisir...

En avant, moussaillons.
Sommaire du chapitre :
  • Déclencher une action : l'interface ActionListener
  • Parlez avec votre classe intérieure
  • Contrôler votre animation : lancement et arrêt
  • Cadeau : votre bouton personnalisé optimisé !
  • Ce qu'il faut retenir

Déclencher une action : l'interface ActionListener

Tout est dans le titre !
Afin de gérer les différentes actions à effectuer selon le bouton sur lequel on clique, nous allons utiliser l'interface ActionListener.

Cependant, nous n'allons pas implémenter cette dernière dans notre classe Bouton, mais dans notre classe Fenetre... Le but étant de faire en sorte que lorsque nous cliquons sur notre bouton, il se passe quelque chose dans notre application comme changer un état, une variable, faire une incrémentation... Enfin n'importe quelle action !

Comme je vous l'ai expliqué dans la partie précédente, lorsque nous faisons addMouseListener, nous prévenons l'objet observé qu'un objet doit être mis au courant ! Ici, nous voulons que ce soit notre application, notre Fenetre, qui écoute notre Bouton, le but final étant de pouvoir lancer ou d'arrêter l'animation de notre Panneau.

Avant d'en arriver là, nous allons faire plus simple. Nous allons voir dans un premier temps l'implémentation de l'interface ActionListener. Afin de vous montrer toute la puissance de cette interface, nous allons utiliser un nouvel objet présent dans le package javax.swing : le JLabel.
Cet objet est en fait comme une étiquette, il est spécialisé dans l'affichage de texte ou d'image... Il est donc parfait pour notre premier exemple !

Pour l'instanciation ou l'initialisation, il fonctionne un peu comme le JButton, voyez plutôt :

Code : Java -
1
2
3
4
JLabel label1 = new JLabel();
label1.setText("mon premier JLabel");
//Ou encore
JLabel label2 = new JLabel("Mon deuxième JLabel");


Créez une variable d'instance de type JLabel - appelons-la label - initialisez-la avec le texte qui vous plaît, puis ajoutez-la avec votre contentPane en BorderLayout.NORTH.

Voici le résultat :


Et le 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
public class Fenetre extends JFrame {
 
    private Panneau pan = new Panneau();
    private Bouton bouton = new Bouton("mon bouton");
    private JPanel container = new JPanel();
    private JLabel label = new JLabel("Le JLabel");
   
    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());
            container.add(pan, BorderLayout.CENTER);
            container.add(bouton, BorderLayout.SOUTH);
            container.add(label, BorderLayout.NORTH);
           
            this.setContentPane(container);
            this.setVisible(true);
           
            go();
    }
        
    //...
}


Vous pouvez voir le texte en haut à gauche... L'alignement par défaut de cet objet est à gauche mais vous pouvez changer quelques paramètres, comme :
  • l'alignement
  • la police à utiliser
  • la couleur de police
  • ...


Voilà un code qui met tout ceci en pratique, et son aperçu.

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
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());
            container.add(pan, BorderLayout.CENTER);
            container.add(bouton, BorderLayout.SOUTH);
            
            //Définition d'une police d'écriture
            Font police = new Font("Tahoma", Font.BOLD, 16 );
            //On applique celle-ci aux JLabel
            label.setFont(police);
            //On change la couleur de police
            label.setForeground(Color.blue);
            //Et on change l'alignement du texte grâce aux attributs static de la classe JLabel
            label.setHorizontalAlignment(JLabel.CENTER);
            
            container.add(label, BorderLayout.NORTH);
           
            this.setContentPane(container);
            this.setVisible(true);
           
            go();
    }


Aperçu :


Maintenant que notre étiquette est exactement comme nous le voulons, nous allons pouvoir implémenter l'interface ActionListener.
Lorsque vous avez implémenté les méthodes de l'interface, vous vous apercevez que celle-ci n'en contient qu'une seule !

Code : Java -


Nous allons maintenant prévenir notre objet Bouton que notre objet Fenetre l'écoute ! Vous l'avez deviné, nous ajoutons notre objet Fenetre à la liste des objets qui écoutent notre Bouton grâce à la méthode addActionListener(ActionListener obj) invoquée sur la variable bouton. Ajoutez cette instruction dans le constructeur, en passant this en paramètre (c'est notre Fenetre qui écoute notre bouton...).

Une fois ceci fait, nous allons changer le texte de notre JLabel dans la méthode actionPerformed ; en fait, nous allons compter combien de fois on clique sur notre bouton... Pour cela, nous ajoutons une variable d'instance de type int dans notre classe : appelons-la compteur. Dans la méthode actionPerformed, nous allons incrémenter ce compteur et afficher son contenu dans notre étiquette.

Voici le code de notre objet mis à jour :

Code : Java -
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
 
public class Fenetre extends JFrame implements ActionListener{
 
    private Panneau pan = new Panneau();
    private Bouton bouton = new Bouton("mon bouton");
    private JPanel container = new JPanel();
    private JLabel label = new JLabel("Le JLabel");
    /**
     * Compteur de clics !
     */
    private int compteur = 0;
   
    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());
            container.add(pan, BorderLayout.CENTER);
            
            //On ajoute notre Fenetre à la liste des auditeurs de notre Bouton
            bouton.addActionListener(this);
            
            container.add(bouton, BorderLayout.SOUTH);
            
            //Définition d'une police d'écriture
            Font police = new Font("Tahoma", Font.BOLD, 16 );
            //On applique celle-ci aux JLabel
            label.setFont(police);
            //On change la couleur de police
            label.setForeground(Color.blue);
            //Et on change l'alignement du texte grâce aux attributs static de la classe JLabel
            label.setHorizontalAlignment(JLabel.CENTER);
            
            container.add(label, BorderLayout.NORTH);
           
            this.setContentPane(container);
            this.setVisible(true);
           
            go();
    }
        
        private void go(){
        
        //Les coordonnées de départ de notre rond
        int x = pan.getPosX(), y = pan.getPosY();
        //Le booléen pour savoir si on recule ou non sur l'axe X
        boolean backX = false;
        //Le booléen pour savoir si on recule ou non sur l'axe Y
        boolean backY = false;
       
        //Pour cet exemple, j'utilise une boucle while
        //Vous verrez qu'elle fonctionne très bien
        while(true){
               
                //Si la coordonnée x est inférieure à 1, on avance
                if(x < 1)backX = false;
                //Si la coordonnée x est supérieure à la taille du Panneau
                //moins la taille du rond on avance
                if(x > pan.getWidth()-50)backX = true;
               
                //idem pour l'axe Y
                if(y < 1)backY = false;
                if(y > pan.getHeight()-50)backY = true;
               
                //Si on avance, on incrémente la coordonnée
                if(!backX)
                        pan.setPosX(++x);
                //Sinon on décrémente
                else
                        pan.setPosX(--x);
               
                //Idem pour l'axe Y
                if(!backY)
                        pan.setPosY(++y);
                else
                        pan.setPosY(--y);
                       
                //On redessine notre Panneau
                pan.repaint();
               
                //Comme on dit : la pause s'impose ! Ici, 3 centièmes de secondes
                try {
                        Thread.sleep(3);
                } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        }
       
}
 
//*******************************************************************************
//                             LA VOILAAAAAAAAAAAAAA
//*******************************************************************************
        /**
         * C'est la méthode qui sera appelée lors d'un clic sur notre bouton
         */
        public void actionPerformed(ActionEvent arg0) {
                //Lorsque nous cliquons sur notre bouton, on met à jour le JLabel
                this.compteur++;
                label.setText("Vous avez cliqué " + this.compteur + " fois");
        }
        
}


Et le résultat :


On commence à faire du sérieux, là ! !
Mais attendez, on ne fait que commencer... Eh oui ! Nous allons maintenant ajouter un deuxième bouton à notre Fenetre, à côté de notre premier bouton (vous êtes libres d'utiliser la classe personnalisée ou un JButton) ! Personnellement, je vais utiliser des boutons normaux maintenant ; en effet, la façon dont on écrit le nom de notre bouton, dans notre classe personnalisée, n'est pas assez souple et donc l'affichage peut être décevant...

Bref, nous avons maintenant deux boutons écoutés par notre objet Fenetre.
Vous devez créer un deuxième JPanel qui va contenir nos deux boutons et insérer celui-ci dans le contentPane en BorderLayout.SOUTH.
Si vous tentez de mettre deux composants au même endroit avec un BorderLayout, seul le dernier composant ajouté apparaîtra ! Eh oui, le composant prend toute la place dans un BorderLayout !


Voilà notre nouveau code :

Code : Java -
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
 
public class Fenetre extends JFrame implements ActionListener{
 
    private Panneau pan = new Panneau();
    private JButton bouton = new JButton("bouton 1");
    private JButton bouton2 = new JButton("bouton 2");
    private JPanel container = new JPanel();
    private JLabel label = new JLabel("Le JLabel");
    /**
     * Compteur de clics !
     */
    private int compteur = 0;
   
    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());
            container.add(pan, BorderLayout.CENTER);
            
            //On ajoute notre Fenetre à la liste des auditeurs de notre Bouton
            bouton.addActionListener(this);
            bouton2.addActionListener(this);
            
            JPanel south = new JPanel();
            south.add(bouton);
            south.add(bouton2);
            container.add(south, BorderLayout.SOUTH);
            
            
            //Définition d'une police d'écriture
            Font police = new Font("Tahoma", Font.BOLD, 16 );
            //On applique celle-ci aux JLabel
            label.setFont(police);
            //On change la couleur de police
            label.setForeground(Color.blue);
            //Et on change l'alignement du texte grâce aux attributs static de la classe JLabel
            label.setHorizontalAlignment(JLabel.CENTER);
            
            container.add(label, BorderLayout.NORTH);
           
            this.setContentPane(container);
            this.setVisible(true);
           
            go();
    }
        
    //...
 
//*******************************************************************************
//                             LA VOILAAAAAAAAAAAAAA
//*******************************************************************************
        /**
         * C'est la méthode qui sera appelée lors d'un clic sur notre bouton
         */
        public void actionPerformed(ActionEvent arg0) {
                //Lorsque nous cliquons sur notre bouton, on met à jour le JLabel
                this.compteur++;
                label.setText("Vous avez cliqué " + this.compteur + " fois");
        }
        
}


Et le résultat :



Le problème maintenant est :
comment faire faire deux choses différentes dans la méthode actionPerformed ?

En effet ! Si nous laissons la méthode actionPerformed telle qu'elle est, les deux boutons auront la même action lorsque nous cliquerons dessus. Essayez, et vous verrez !

Il existe un moyen de savoir qui a déclenché l'événement, en utilisant l'objet passé en paramètre dans la méthode actionPerformed. Nous allons utiliser la méthode getSource() de cet objet pour connaître le nom de l'instance qui a généré l'événement.
Testez la méthode actionPerformed suivante, et voyez le résultat :

Code : Java -
1
2
3
4
5
6
7
8
public void actionPerformed(ActionEvent arg0) {
                
                if(arg0.getSource() == bouton)
                        label.setText("Vous avez cliqué sur le bouton 1");
                
                if(arg0.getSource() == bouton2)
                        label.setText("Vous avez cliqué sur le bouton 2");
        }


Résultat :


Vous pouvez constater que notre code fonctionne très bien ! Mais cette approche n'est pas très orientée objet... Si vous avez une multitude de boutons sur votre IHM... vous allez avoir une méthode actionPerformed très chargée !
Nous pourrions créer deux objets à part, chacun écoutant un bouton, dont le rôle serait de faire un traitement précis par bouton... Cependant, si dans nos traitements nous avons besoin de modifier des données internes à la classe contenant nos boutons, il faudrait passer ces données (ou objets) à cet objet... Pas terrible non plus.
On commence à te connaître, maintenant ! Tu as une idée derrière la tête...

Je suis démasqué !
Il existe en Java un type de classe particulière. Voyons ça tout de suite !

Parlez avec votre classe intérieure

En Java, vous pouvez faire ce qu'on appelle des classes internes.
Ceci consiste à déclarer une classe dans une classe ! Je sais, ça paraît tordu mais vous allez voir que c'est très pratique.

En effet, ces classes possèdent tous les avantages des classes normales, héritant d'une super-classe ou implémentant une interface, elles bénéficieront donc des bénéfices du polymorphisme et de la covariance des variables !
En plus, elles ont l'avantage d'avoir accès aux attributs de la classe dans laquelle elles sont déclarées !

Dans le cas qui nous intéresse, ceci permet de faire une implémentation de l'interface ActionListener, détachée de notre classe Fenetre, mais pouvant utiliser ses attributs !
La déclaration d'une telle classe se fait exactement comme une classe normale, sauf qu'elle est dans une autre classe... Ce qui donne ceci :

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class MaClasseExterne{
 
     public MaClasseExterne(){
          //....
     }
 
     class MaClassInterne{
 
          public MaClassInterne(){
               //...
          }
     }
}


Grâce à ceci, nous allons pouvoir faire une classe spécialisée dans l'écoute de composants et qui a un travail précis à faire ! Dans notre exemple, nous allons juste faire deux classes internes implémentant chacune l'interface ActionListener ; elles redéfiniront donc la méthode actionPerformed :
  • BoutonListener : qui écoutera le premier bouton
  • Bouton2Listener : qui écoutera le second !

Une fois que ceci est fait, il ne nous reste plus qu'à dire à chaque bouton : "qui l'écoute", avec la méthode addActionListener.

Voici la classe Fenetre mise à jour :

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
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
 
public class Fenetre extends JFrame{
 
    private Panneau pan = new Panneau();
    private JButton bouton = new JButton("bouton 1");
    private JButton bouton2 = new JButton("bouton 2");
    private JPanel container = new JPanel();
    private JLabel label = new JLabel("Le JLabel");
    private int compteur = 0;
   
    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());
            container.add(pan, BorderLayout.CENTER);
            
            //Ce sont maintenant nos classes internes qui écoutent nos boutons 
            bouton.addActionListener(new BoutonListener());
            bouton2.addActionListener(new Bouton2Listener());
            
            JPanel south = new JPanel();
            south.add(bouton);
            south.add(bouton2);
            container.add(south, BorderLayout.SOUTH);
            
            Font police = new Font("Tahoma", Font.BOLD, 16 );
            label.setFont(police);
            label.setForeground(Color.blue);
            label.setHorizontalAlignment(JLabel.CENTER);
            
            container.add(label, BorderLayout.NORTH);
            this.setContentPane(container);
            this.setVisible(true);
           
            go();
    }
        
        private void go(){
        
        //Les coordonnées de départ de notre rond
        int x = pan.getPosX(), y = pan.getPosY();
        //Le booléen pour savoir si on recule ou non sur l'axe X
        boolean backX = false;
        //Le booléen pour savoir si on recule ou non sur l'axe Y
        boolean backY = false;
       
        //Pour cet exemple, j'utilise une boucle while
        //Vous verrez qu'elle fonctionne très bien
        while(true){
               
            if(x < 1)backX = false;
            if(x > pan.getWidth()-50)backX = true;               
            if(y < 1)backY = false;
            if(y > pan.getHeight()-50)backY = true;
            if(!backX)pan.setPosX(++x);
            else pan.setPosX(--x);
            if(!backY) pan.setPosY(++y);
            else pan.setPosY(--y);
            pan.repaint();
 
            try {
                    Thread.sleep(3);
            } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
            }
        }
       
        }
 
 
        /**
         * classe qui écoute notre bouton
         */
        class BoutonListener  implements ActionListener{
 
                /**
                 * Redéfinition de la méthode actionPerformed
                 */
                public void actionPerformed(ActionEvent arg0) {
                        label.setText("Vous avez cliqué sur le bouton 1");                     
                }
                
        }
        
        /**
         * classe qui écoute notre bouton2
         */
        class Bouton2Listener  implements ActionListener{
 
                /**
                 * Redéfinition de la méthode actionPerformed
                 */
                public void actionPerformed(ActionEvent e) {
                        label.setText("Vous avez cliqué sur le bouton 2");                     
                }
                
        }
                
        
        
}


Et le résultat est parfait :

Vous pouvez même constater que nos classes internes ont accès aux attributs déclarés private dans notre classe Fenetre !


Nous n'avons plus à nous soucier de qui a déclenché l'événement maintenant, car nous avons une classe qui écoute chaque bouton ! Nous pouvons souffler un peu, une grosse épine vient de nous être retirée du pied.
Vous le savez, mais vous pouvez faire écouter votre bouton par plusieurs classes... Il vous suffit d'ajouter les classes qui écoutent le boutons avec addActionListener.

Eh oui, faites le test...
Créez une troisième classe interne, peu importe son nom (moi, je l'appelle Bouton3Listener), implémentez l'interface ActionListener dans celle-ci et contentez-vous de faire un simple System.out.println dans la méthode actionPerformed. N'oubliez pas d'ajouter cette dernière à la liste des classes qui écoutent votre bouton (n'importe lequel des deux... Moi, j'ai choisi le premier).

Je ne vous donne que le code ajouté :

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
//Les imports
public class Fenetre extends JFrame{
    
     //Les variables d'instance...
      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());
            container.add(pan, BorderLayout.CENTER);
            
            //Première classe écoutant mon bouton 
            bouton.addActionListener(new BoutonListener());
            //Deuxième classe écoutant mon bouton
            bouton.addActionListener(new Bouton3Listener());
            
            bouton2.addActionListener(new Bouton2Listener());
            
            JPanel south = new JPanel();
            south.add(bouton);
            south.add(bouton2);
            container.add(south, BorderLayout.SOUTH);
            
            Font police = new Font("Tahoma", Font.BOLD, 16 );
            label.setFont(police);
            label.setForeground(Color.blue);
            label.setHorizontalAlignment(JLabel.CENTER);
            
            container.add(label, BorderLayout.NORTH);
            this.setContentPane(container);
            this.setVisible(true);
           
            go();
    }
 
    //...
 
        class Bouton3Listener  implements ActionListener{
 
                /**
                 * Redéfinition de la méthode actionPerformed
                 */
                public void actionPerformed(ActionEvent e) {
                        System.out.println("Ma classe interne numéro 3 écoute bien !");               
                }
                
        }
}


Et le résultat :


Les classes internes sont vraiment des classes à part entière ! Elles peuvent aussi être héritées d'une super-classe... De ce fait, c'est presque comme si nous avions de l'héritage multiple, ça n'en est pas... Mais ça y ressemble. Donc, ce code est valide :

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class MaClasseExterne extends JFrame{
 
     public MaClasseExterne(){
          //....
     }
 
     class MaClassInterne extends JPanel{
 
          public MaClassInterne(){
               //...
          }
     }
 
     class MaClassInterne2 extends JButton{
 
          public MaClassInterne(){
               //...
          }
     }
 
}

Rien ne vous empêche de faire de votre classe Panneau une classe interne !


Vous voyez bien que ce genre de classes peuvent s'avérer très utile...
Bon. Nous avons réglé le problème d'implémentation, nous avons deux boutons qui sont écoutés, il ne nous reste plus qu'à lancer et arrêter notre animation avec ceux-ci !

Contrôler votre animation : lancement et arrêt

Nous attaquons la dernière ligne droite de ce chapitre.
Donc, pour réussir à gérer le lancement et l'arrêt de notre animation, nous allons devoir modifier un peu le code de notre classe Fenetre.
Il va falloir changer le libellé de ceux-ci, le premier affichera Go et le deuxième Stop et, pour éviter d'arrêter l'animation alors que celle-ci n'est pas lancée et ne pas l'animer alors qu'elle l'est déjà, nous allons tantôt activer / désactiver les boutons. Je m'explique.
  • Au lancement, l'animation est lancée. Le bouton Go ne sera pas cliquable alors que Stop, oui.
  • Si l'animation est stoppée, le bouton Stop ne sera plus cliquable et le bouton Go le sera.

Tu ne nous as pas dit comment on fait ça !

Je sais... Mais je vous cache encore pas mal de choses...
Ne vous inquiétez pas, c'est très simple à faire ; il existe une méthode pour faire ça :

Code : Java -
1
2
3
JButton bouton = new JButton("bouton");
bouton.setEnabled(false); // Votre bouton n'est plus cliquable !
bouton.setEnabled(true); // Votre bouton de nouveau cliquable !


J'ajouterais que vous pouvez faire pas mal de choses avec ces objets ! Soyez curieux et testez les méthodes de ces objets, allez faire un tour sur le site de Sun Microsystems, fouillez, fouinez...
L'une des méthodes qui s'avère souvent utile et qui est utilisable pour tous ces objets (et les objets que nous verrons plus tard) est la méthode de gestion de dimension. Il ne s'agit pas de la méthode setSize(), mais la méthode setPreferredSize(Dimension preferredSize). Cette méthode prend un objet Dimension en paramètre qui lui, prend deux entiers en paramètre.

Voilà un exemple :

Code : Java -
1
bouton.setPreferredSize(new Dimension(150, 120));


Ce qui donne sur notre application :


Retournons à nos moutons...
Afin de bien gérer notre animation, il va falloir améliorer notre méthode go().
Nous allons sortir de cette méthode les deux entiers dont nous nous servions afin de recalculer les coordonnées de notre rond et, concernant la boucle infinie, elle doit dorénavant pouvoir être stoppée !
Pour réussir ceci, nous allons déclarer une variable d'instance de type booléen qui changera d'état selon le bouton sur lequel nous cliquerons, et nous utiliserons celui-ci comme paramètre de notre boucle.

Voici le code de notre classe Fenetre :

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
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
 
public class Fenetre extends JFrame{
 
    private Panneau pan = new Panneau();
    private JButton bouton = new JButton("Go");
    private JButton bouton2 = new JButton("Stop");
    private JPanel container = new JPanel();
    private JLabel label = new JLabel("Le JLabel");
    private int compteur = 0;
    private boolean animated = true;
    private boolean backX, backY;
    private int x,y ;
    
    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());
            container.add(pan, BorderLayout.CENTER);
            
            //Ce sont maintenant nos classes internes qui écoutent nos boutons 
            bouton.addActionListener(new BoutonListener()); 
            bouton.setEnabled(false);
            bouton2.addActionListener(new Bouton2Listener());
            
            JPanel south = new JPanel();
            south.add(bouton);
            south.add(bouton2);
            container.add(south, BorderLayout.SOUTH);
            
            Font police = new Font("Tahoma", Font.BOLD, 16 );
            label.setFont(police);
            label.setForeground(Color.blue);
            label.setHorizontalAlignment(JLabel.CENTER);
            
            container.add(label, BorderLayout.NORTH);
            this.setContentPane(container);
            this.setVisible(true);
            go();
            
    }
        
        private void go(){
        //Les coordonnées de départ de notre rond
        x = pan.getPosX();
        y = pan.getPosY();
        //Pour cet exemple, j'utilise une boucle while
        //Vous verrez qu'elle fonctionne très bien
        while(this.animated){
                
            if(x < 1)backX = false;
            if(x > pan.getWidth()-50)backX = true;               
            if(y < 1)backY = false;
            if(y > pan.getHeight()-50)backY = true;
            
            
            if(!backX)pan.setPosX(++x);
            else pan.setPosX(--x);
            if(!backY) pan.setPosY(++y);
            else pan.setPosY(--y);
            pan.repaint();
 
            try {
                    Thread.sleep(3);
            } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
            }
        }       
        }
 
 
        /**
         * classe qui écoute notre bouton
         */
        class BoutonListener implements ActionListener{
 
                /**
                 * Redéfinitions de la méthode actionPerformed
                 */
                public void actionPerformed(ActionEvent arg0) {
                        animated = true;
                        bouton.setEnabled(false);
                        bouton2.setEnabled(true);
                        go();
                }
                
        }
        
        /**
         * classe qui écoute notre bouton2
         */
        class Bouton2Listener  implements ActionListener{
 
                /**
                 * Redéfinitions de la méthode actionPerformed
                 */
                public void actionPerformed(ActionEvent e) {
                        animated = false;       
                        bouton.setEnabled(true);
                        bouton2.setEnabled(false);
                }
                
        }       
}


À l'exécution, vous pouvez voir :
  • que le bouton Go n'est pas cliquable mais que l'autre l'est
  • que l'animation se lance
  • lorsque vous cliquez sur Stop, que l'animation s'arrête
  • que le bouton Go devient cliquable
  • et lors de votre tentative, que l'animation ne se lance pas !

Comment ça se fait ?

Comme je vous l'ai dit dans le chapitre sur les conteneurs, notre application, au démarrage de celle-ci, lance un thread : le processus principal de votre programme !
Au démarrage, l'animation est lancée, celle-ci dans le même thread que notre objet Fenetre.
Lorsque nous lui demandons de s'arrêter, aucun souci : les ressources mémoire sont libérées, on sort de la boucle infinie et l'application continue son court !
Mais lorsque nous redemandons à l'animation de se lancer... L'instruction dans la méthode actionPerformed appelle la méthode go et, vu que nous sommes dans une boucle infinie : on reste dans la méthode go et on ne sort plus de la méthode actionPerformed de notre bouton !

Voici l'explication de ce phénomène



Java gère les appels aux méthodes selon ce qu'on appelle vulgairement : La pile !

Pour vous expliquer ceci, nous allons prendre un exemple tout bête ; regardez cet objet :

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
public class TestPile {
 
        public TestPile(){
                System.out.println("Début constructeur");
                methode1();
                System.out.println("Fin constructeur");
        }
        
        public void methode1(){
                System.out.println("Début méthode 1");
                methode2();
                System.out.println("Fin méthode 1");
        }
        
        public void methode2(){
                System.out.println("Début méthode 2");
                methode3();
                System.out.println("Fin méthode 2");
        }
        
        public void methode3(){
                System.out.println("Début méthode 3");
                System.out.println("Fin méthode 3");
        }
        
}


Si vous instanciez cet objet, vous obtiendrez dans votre console ceci :


Je suppose que vous avez remarqué, avec stupéfaction, que l'ordre des instructions est un peu bizarre !
Voici ce qu'il s'est passé :
  • à l'instanciation, notre objet appelle la méthode1 ;
  • cette dernière invoque la méthode2 ;
  • celle-ci utilise la méthode3 et une fois terminée, la JVM retourne dans la méthode2 ;
  • celle-ci terminée, on remonte à la fin de la méthode1... jusqu'à la dernière instruction appelante : le contructeur.

Lors de tous les appels, on dit que la JVM empile les invocations sur la pile ! Et, une fois la dernière méthode empilée terminée, la JVM dépile celle-ci !


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


Dans notre programme, imaginez que la méthode actionPerformed est représentée, dans le schéma ci-dessus, par méthode2 et que notre méthode go soit représentée par méthode3. Lorsque nous entrons dans méthode3, nous entrons dans une boucle infinie ! La conséquence directe : on ne ressort jamais de cette méthode et la JVM ne dépile plus !
Comment faire, alors ?

Nous allons utiliser une nouveau thread ! En gros, grâce à ceci, la méthode go se trouvera dans une pile à part !
Attends... On arrive pourtant à arrêter l'animation alors qu'elle est dans une boucle infinie ? Pourquoi ?

Tout simplement parce que nous demandons de faire une bête initialisation de variable dans la gestion de notre événement ! Si vous faites une deuxième méthode comprenant une boucle infinie et que vous l'invoquez lors du clic sur Stop, vous aurez exactement le même problème !

Je ne vais pas m'éterniser là-dessus tout de suite... Nous allons voir ça au prochain chapitre !
Mais avant, je pense qu'un TP serait le bienvenu... Histoire de réviser un peu !
Vous êtes d'accord ? Alors, direction le topo, le QCM, et en avant pour un TP !

Cadeau : votre bouton personnalisé optimisé !

Vu que je vous ai fait un peu saliver pour rien... je vous donne le code java d'un bouton corrigeant le problème d'écriture du libellé. Je ne ferai pas trop de commentaire à son sujet, tout est dans le 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
 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
128
129
130
131
132
133
134
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.io.IOException;
 
import javax.imageio.ImageIO;
import javax.swing.JButton;
 
 
public class Bouton extends JButton implements MouseListener{
 
     private String name;
     private Image img;
         
     public Bouton(String str){
             super(str);
             this.name = str;
             
             try {
                   img = ImageIO.read(new File("fondBouton.png"));
             } catch (IOException e) {
                   // TODO Auto-generated catch block
                   e.printStackTrace();
             }
                        
             this.addMouseListener(this);
     }
    
     public void paintComponent(Graphics g){
                 
             Graphics2D g2d = (Graphics2D)g;
            
             GradientPaint gp = new GradientPaint(0, 0, Color.blue, 0, 20, Color.cyan, true);
             g2d.setPaint(gp);
            // g2d.fillRect(0, 0, this.getWidth(), this.getHeight());
             g2d.drawImage(img, 0, 0, this.getWidth(), this.getHeight(), this);
             
             g2d.setColor(Color.black);
             
             //Objet qui permet de connaître les propriétés d'une police, dont la taille !
             FontMetrics fm = g2d.getFontMetrics();
             //Hauteur de la police d'écriture
             int height = fm.getHeight();
             //Largeur totale de la chaîne passée en paramètre
             int width = fm.stringWidth(this.name);
             
             //On calcule donc la position du texte dans le bouton, et le tour est joué !
             g2d.drawString(this.name, this.getWidth() / 2 - (width / 2), (this.getHeight() / 2) + (height / 4));
            
     }
 
        @Override
        public void mouseClicked(MouseEvent event) {
                 //Pas utile d'utiliser cette méthode ici                      
        }
 
        @Override
        public void mouseEntered(MouseEvent event) {
                
                //Nous changeons le fond en jaune pour notre image lors du survol
                //avec le fichier fondBoutonHover.png
                try {
                         img = ImageIO.read(new File("fondBoutonHover.png"));
                } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
                
        }
 
        @Override
        public void mouseExited(MouseEvent event) {
 
                //Nous changeons le fond en vert pour notre image lorsqu'on quitte le bouton
                //avec le fichier fondBouton.png
                try {
                         img = ImageIO.read(new File("fondBouton.png"));
                } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
                
        }
 
        @Override
        public void mousePressed(MouseEvent event) {
 
                //Nous changeons le fond en orangé pour notre image lors du clic gauche
                //avec le fichier fondBoutonClic.png
                try {
                         img = ImageIO.read(new File("fondBoutonClic.png"));
                } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
                
        }
 
        @Override
        public void mouseReleased(MouseEvent event) {
                
                //Nous changeons le fond en orangé pour notre image 
                //lorsqu'on relâche le clic 
                //avec le fichier fondBoutonHover.png   
                
                //Si on est à l'extérieur de l'objet, on dessine le fond par défaut
                if(event.getY() > 0)
                {
                        try {
                                 img = ImageIO.read(new File("fondBoutonHover.png"));
                        } catch (IOException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                        }
                }
                //Sinon on met le fond jaune, la souris est encore dessus...
                else
                {
                        try {
                                 img = ImageIO.read(new File("fondBouton.png"));
                        } catch (IOException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                        }
                }               
        }
        
}


Essayez et vous verrez !

Ce qu'il faut retenir

  • L'interface utilisée dans ce chapitre est : ActionListener. Celle-ci est présente dans le package java.awt.
  • La méthode de cette interface à implémenter est actionPerformed.
  • Lorsque vous avez plusieurs composants pouvant être écoutés, préférez faire un objet écouteur par composants.
  • Vous pouvez, et c'est conseillé, utiliser des classes internes pour écouter vos composants.
  • Une classe interne est une classe à l'intérieur d'une classe.
  • Une telle classe a accès à toutes les données et méthodes de sa classe externe.
  • Ce type de classe peut être déclarée private.
  • La JVM traite les méthodes appelées par une pile de méthode, définissant ainsi l'ordre d'exécution de celles-ci.
  • Une méthode est empilée à l'invocation de celle-ci, mais n'est dépilée que lorsque toutes ses intructions sont terminées !
  • Vous connaissez la méthode permettant d'écrire dans un JLabel, vous pouvez aussi récupérer le texte d'un tel objet en utilisant la méthode getText().

Une classe interne privée ?

Oui, parce que vous pouvez créer une instance de la classe interne à l'extérieur de sa classe externe, comme ceci :
Code : Java -
1
2
Fenetre.BoutonListener boutonL;
boutonL = new Fenetre().new BoutonListener();


Par contre, vous ne pouvez pas déclarer de variable static dans une classe interne... Mais une classe interne peut être déclarée static.
Encore un chapitre de bouclé !
Vous tenez toujours bon ?

À tout de suite pour votre premier TP en événementiel !


0 comments to "LECON 305"

Post a Comment

Powered by Blogger.

About This Blog

Aller au debut de la page