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.
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 -
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 :
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
- ...
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 !
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 !
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 -
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.
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 -
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 !
Vous tenez toujours bon ?
À tout de suite pour votre premier TP en événementiel !
// //
0
comments
//
0 comments to "LECON 305"
Powered by Blogger.
Post a Comment