Licence CC BY-NC-SA

Les menus et boîtes de dialogue

Ce contenu est obsolète. Il peut contenir des informations intéressantes mais soyez prudent avec celles-ci.

Voici deux éléments très utiles à l'élaboration de programmes offrant plusieurs fonctionnalités que nous allons voir ici. Ces deux types d'objets se retrouvent souvent dans les différentes applications que vous pourrez trouver sur le Net. Vous verrez que la manière d'utiliser les menus ressemble beaucoup à celle que vous avez déjà vue et qu'il suffira de se familiariser avec l'objet pour pouvoir faire des choses sympa. Quant à l'utilisation de boîtes de dialogue, c'est un peu particulier, mais tout aussi simple.

Les boîtes de dialogue

Les boîtes de dialogue, c'est certain, vous connaissez ! Cependant, afin de nous assurer que nous parlons de la même chose, voici une petite description de ce qu'est une boîte de dialogue. Il s'agit d'une petite fenêtre pouvant servir à plusieurs choses :

  • afficher une information (message d'erreur, d'avertissement…) ;
  • demander une validation, une réfutation ou une annulation ;
  • demander à l'utilisateur de saisir une information dont le système a besoin ;
  • etc.

Vous pouvez voir qu'elles peuvent servir à beaucoup de choses. Il faut toutefois les utiliser avec parcimonie : il est assez pénible pour l'utilisateur qu'une application ouvre une boîte de dialogue à chaque notification, car toute boîte ouverte doit être fermée ! Pour ce point je vous laisse seuls juges de leur utilisation.

Les boîtes d'information

L'objet que nous allons utiliser tout au long de ce chapitre est le JOptionPane, un objet assez complexe au premier abord, mais fort pratique.

La figure suivante vous montre à quoi ressemblent des boîtes de dialogues « informatives ».

Exemples de boîtes de dialogue

Ces boîtes ne sont pas destinées à participer à de quelconques opérations : elles affichent juste un message à l'attention de l'utilisateur. Voici le code utilisé pour obtenir ces boîtes :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
JOptionPane jop1, jop2, jop3;

//Boîte du message d'information
jop1 = new JOptionPane();
jop1.showMessageDialog(null, "Message informatif", "Information", JOptionPane.INFORMATION_MESSAGE);

//Boîte du message préventif
jop2 = new JOptionPane();
jop2.showMessageDialog(null, "Message préventif", "Attention", JOptionPane.WARNING_MESSAGE);

//Boîte du message d'erreur
jop3 = new JOptionPane();
jop3.showMessageDialog(null, "Message d'erreur", "Erreur", JOptionPane.ERROR_MESSAGE);

Ces trois boîtes ne s'affichent pas en même temps, tout simplement parce qu'en Java (mais aussi dans les autres langages), les boîtes de dialogue sont dites modales. Cela signifie que lorsqu'une boîte fait son apparition, celle-ci bloque toute interaction avec un autre composant, et ceci tant que l'utilisateur n'a pas mis fin au dialogue !

Maintenant, voyons de plus près comment construire un tel objet. Ici, nous avons utilisé la méthode showMessageDialog(Component parentComponent, String message, String title, int messageType); où :

  • Component parentComponent : correspond au composant parent ; ici, il n'y en a aucun, nous mettons donc null.
  • String message : permet de renseigner le message à afficher dans la boîte de dialogue.
  • String title : permet de donner un titre à l'objet.
  • int messageType : permet de savoir s'il s'agit d'un message d'information, de prévention ou d'erreur. Vous avez sans doute remarqué que, mis à part le texte et le titre, seul ce champ variait entre nos trois objets !

Il existe deux autres méthodes showMessageDialog() pour cet objet : une qui prend deux paramètres en moins (le titre et le type de message), et une qui prend un paramètre en plus (l'icône à utiliser).

Je pense qu'il est inutile de détailler la méthode avec les paramètres en moins, mais voici des exemples de boîtes avec des icônes définies par nos soins.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;

public class Test {
  public static void main(String[] args) {      
    JOptionPane jop1, jop2, jop3;      
    jop1 = new JOptionPane();
    ImageIcon img = new ImageIcon("images/information.png");
    jop1.showMessageDialog(null, "Message informatif", "Information", JOptionPane.INFORMATION_MESSAGE, img);      
    jop2 = new JOptionPane();
    img = new ImageIcon("images/warning.png");
    jop2.showMessageDialog(null, "Message préventif", "Attention", JOptionPane.WARNING_MESSAGE, img);      
    jop3 = new JOptionPane();
    img = new ImageIcon("images/erreur.png");
    jop3.showMessageDialog(null, "Message d'erreur", "Erreur", JOptionPane.ERROR_MESSAGE, img);            
  }
}

Ces images ont été trouvées sur Google puis rangées dans un dossier « images » à la racine du projet Eclipse. Je vous invite à télécharger vos propres images et à faire vos tests. Vous remarquerez aussi l'emploi de l'objet ImageIcon, qui lit le fichier image à l'emplacement spécifié dans son constructeur. La figure suivante représente le résultat obtenu.

Images personnalisées dans des boîtes de dialogue

Ce type de boîte est très utile pour signaler à l'utilisateur qu'une opération s'est terminée ou qu'une erreur est survenue. L'exemple le plus simple qui me vient en tête est le cas d'une division par zéro : on peut utiliser une boîte de dialogue dans le bloc catch.

Voici les types de boîtes que vous pouvez afficher (ces types restent valables pour tout ce qui suit) :

  • JOptionPane.ERROR_MESSAGE
  • JOptionPane.INFORMATION_MESSAGE
  • JOptionPane.PLAIN_MESSAGE
  • JOptionPane.QUESTION_MESSAGE
  • JOptionPane.WARNING_MESSAGE

Je pense que vous voyez désormais l'utilité de telles boîtes de dialogue. Nous allons donc poursuivre avec les boîtes de confirmation.

Les boîtes de confirmation

Comme leur nom l'indique, ces dernières permettent de valider, d'invalider ou d'annuler une décision. Nous utiliserons toujours l'objet JOptionPane, mais ce sera cette fois avec la méthode showConfirmDialog(), une méthode qui retourne un entier correspondant à l'option que vous aurez choisie dans cette boîte :

  • Yes ;
  • No ;
  • Cancel.

Comme exemple, nous pouvons prendre notre animation dans sa version la plus récente. Nous pourrions utiliser une boîte de confirmation lorsque nous cliquons sur l'un des boutons contrôlant l'animation (Go ou Stop).

Voici les modifications de notre classe Fenetre :

 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
//Les autres imports n'ont pas changé
import javax.swing.JOptionPane;

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("Choix de la forme");
  private int compteur = 0;
  private boolean animated = true;
  private boolean backX, backY;
  private int x,y ;
  private Thread t;
  private JComboBox combo = new JComboBox();

  private JCheckBox morph = new JCheckBox("Morphing");

  public Fenetre(){
    //Rien de changé ici
  }

  private void go(){
    //Cette méthode n'a pas changé non plus
  }

  public class BoutonListener implements ActionListener{
    public void actionPerformed(ActionEvent arg0) {     
      JOptionPane jop = new JOptionPane();      
      int option = jop.showConfirmDialog(null, 
        "Voulez-vous lancer l'animation ?", 
        "Lancement de l'animation", 
        JOptionPane.YES_NO_OPTION, 
        JOptionPane.QUESTION_MESSAGE);

      if(option == JOptionPane.OK_OPTION){
        animated = true;
        t = new Thread(new PlayAnimation());
        t.start();
        bouton.setEnabled(false);
        bouton2.setEnabled(true);       
      }
    }    
  }

  class Bouton2Listener  implements ActionListener{
    public void actionPerformed(ActionEvent e) {
      JOptionPane jop = new JOptionPane();      
      int option = jop.showConfirmDialog(null, 
        "Voulez-vous arrêter l'animation ?",
        "Arrêt de l'animation", 
        JOptionPane.YES_NO_CANCEL_OPTION, 
        JOptionPane.QUESTION_MESSAGE);

      if(option != JOptionPane.NO_OPTION && 
      option != JOptionPane.CANCEL_OPTION && 
      option != JOptionPane.CLOSED_OPTION){
        animated = false;   
        bouton.setEnabled(true);
        bouton2.setEnabled(false);
      }
    }    
  } 

  class PlayAnimation implements Runnable{
    public void run() {
      go();     
    }    
  }

  class FormeListener implements ActionListener{
    //Rien de changé
  }

  class MorphListener implements ActionListener{
    //Rien de changé
  } 
}

Les instructions intéressantes se trouvent ici :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//…
JOptionPane jop = new JOptionPane();            
int option = jop.showConfirmDialog(null, "Voulez-vous lancer l'animation ?", "Lancement de l'animation", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);

if(option == JOptionPane.OK_OPTION){
  animated = true;
  t = new Thread(new PlayAnimation());
  t.start();
  bouton.setEnabled(false);
  bouton2.setEnabled(true);         
}
//…
//…
JOptionPane jop = new JOptionPane();            
int option = jop.showConfirmDialog(null, "Voulez-vous arrêter l'animation ?", "Arrêt de l'animation", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);

if(option != JOptionPane.NO_OPTION && 
   option != JOptionPane.CANCEL_OPTION && 
   option != JOptionPane.CLOSED_OPTION){
     animated = false;  
     bouton.setEnabled(true);
     bouton2.setEnabled(false);
}

Voyons ce qu'il se passe ici :

  • nous initialisons notre objet JOptionPane : rien d'étonnant ;
  • en revanche, plutôt que d'afficher directement la boîte, nous affectons le résultat que renvoie la méthode showConfirmDialog() à une variable de type int ;
  • nous nous servons de cette variable afin de savoir quel bouton a été cliqué (oui ou non).

En fait, lorsque vous cliquez sur l'un des deux boutons présents dans cette boîte, vous pouvez affecter une valeur de type int :

  • correspondant à l'entier JOptionPane.OK_OPTION, qui vaut 0 (JOptionPane.YES_OPTION a la même valeur);
  • correspondant à l'entier JOptionPane.NO_OPTION, qui vaut 1 ;
  • correspondant à l'entier JOptionPane.CANCEL_OPTION pour la boîte apparaissant lors du clic sur « Stop », qui vaut 2 ;
  • correspondant à l'entier JOptionPane.CLOSED_OPTION pour la même boîte que ci-dessus et qui vaut -1.

En effectuant un test sur la valeur de notre entier, nous pouvons en déduire le bouton sur lequel on a cliqué et agir en conséquence ! La figure suivante représente deux copies d'écran du résultat obtenu.

JOptionPane avec notre animation

Les boîtes de saisie

Je suis sûr que vous avez deviné à quoi peuvent servir ces boîtes. Oui, tout à fait, nous allons pouvoir y saisir du texte ! Et mieux encore : nous pourrons même obtenir une boîte de dialogue qui propose des choix dans une liste déroulante. Vous savez déjà que nous allons utiliser l'objet JOptionPane, et les plus curieux d'entre vous ont sûrement dû jeter un œil aux autres méthodes proposées par cet objet… Ici, nous allons utiliser la méthode showInputDialog(Component parent, String message, String title, int messageType), qui retourne une chaîne de caractères.

Voici un code la mettant en œuvre et la figure suivante représentant son résultat :

1
2
3
4
5
6
7
8
9
import javax.swing.JOptionPane;

public class Test {
  public static void main(String[] args) {
    JOptionPane jop = new JOptionPane(), jop2 = new JOptionPane();
    String nom = jop.showInputDialog(null, "Veuillez décliner votre identité !", "Gendarmerie nationale !", JOptionPane.QUESTION_MESSAGE);
    jop2.showMessageDialog(null, "Votre nom est " + nom, "Identité", JOptionPane.INFORMATION_MESSAGE);
  }
}

Exemples de boîtes de saisie

Rien d'extraordinaire… Maintenant, voyons comment on intègre une liste dans une boîte de ce genre. Vous allez voir, c'est simplissime !

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import javax.swing.JOptionPane;

public class Test {
  public static void main(String[] args) {
    String[] sexe = {"masculin", "féminin", "indéterminé"};
    JOptionPane jop = new JOptionPane(), jop2 = new JOptionPane();
    String nom = (String)jop.showInputDialog(null, 
      "Veuillez indiquer votre sexe !",
      "Gendarmerie nationale !",
      JOptionPane.QUESTION_MESSAGE,
      null,
      sexe,
      sexe[2]);
    jop2.showMessageDialog(null, "Votre sexe est " + nom, "Etat civil", JOptionPane.INFORMATION_MESSAGE);
  }
}

Ce code a pour résultat la figure suivante.

Liste dans une boîte de dialogue

Voici un petit détail des paramètres utilisés dans cette méthode :

  • les quatre premiers, vous connaissez ;
  • le deuxième null correspond à l'icône que vous souhaitez passer ;
  • ensuite, vous devez passer un tableau de String afin de remplir la combo (l'objet JComboBox) de la boîte ;
  • le dernier paramètre correspond à la valeur par défaut de la liste déroulante.

Cette méthode retourne un objet de type Object, comme si vous récupériez la valeur directement dans la combo ! Du coup, n'oubliez pas de faire un cast.

Voici maintenant une variante de ce que vous venez de voir : nous allons utiliser ici la méthode showOptionDialog(). Celle-ci fonctionne à peu près comme la méthode précédente, sauf qu'elle prend un paramètre supplémentaire et que le type de retour n'est pas un objet mais un entier.

Ce type de boîte propose un choix de boutons correspondant aux éléments passés en paramètres (tableau de String) au lieu d'une combo ; elle prend aussi une valeur par défaut, mais retourne l'indice de l'élément dans la liste au lieu de l'élément lui-même.

Je pense que vous vous y connaissez assez pour comprendre le code suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import javax.swing.JOptionPane;

public class Test {
  public static void main(String[] args) {
    String[] sexe = {"masculin", "féminin", "indéterminé"};
    JOptionPane jop = new JOptionPane(), jop2 = new JOptionPane();
    int rang = jop.showOptionDialog(null, 
      "Veuillez indiquer votre sexe !",
      "Gendarmerie nationale !",
      JOptionPane.YES_NO_CANCEL_OPTION,
      JOptionPane.QUESTION_MESSAGE,
      null,
      sexe,
      sexe[2]);
    jop2.showMessageDialog(null, "Votre sexe est " + sexe[rang], "Etat civil", JOptionPane.INFORMATION_MESSAGE);
  }
}

Ce qui nous donne la figure suivante.

Boîte multi-boutons

Voilà, vous en avez terminé avec les boîtes de saisie. Cependant, vous avez dû vous demander s'il n'était pas possible d'ajouter des composants à ces boîtes. C'est vrai : vous pourriez avoir besoin de plus de renseignements, sait-on jamais… Je vous propose donc de voir comment créer vos propres boîtes de dialogue !

Des boîtes de dialogue personnalisées

Je me doute que vous êtes impatients de faire vos propres boîtes de dialogue. Comme il est vrai que dans certains cas, vous en aurez besoin, allons-y gaiement ! Je vais vous révéler un secret bien gardé : les boîtes de dialogue héritent de la classe JDialog. Vous avez donc deviné que nous allons créer une classe dérivée de cette dernière.

Commençons par créer un nouveau projet. Créez une nouvelle classe dans Eclipse, appelons-la ZDialog, faites-la hériter de la classe citée précédemment, et mettez-y le code suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import javax.swing.JDialog;
import javax.swing.JFrame;

public class ZDialog extends JDialog {
  public ZDialog(JFrame parent, String title, boolean modal){
    //On appelle le construteur de JDialog correspondant
    super(parent, title, modal);
    //On spécifie une taille
    this.setSize(200, 80);
    //La position
    this.setLocationRelativeTo(null);
    //La boîte ne devra pas être redimensionnable
    this.setResizable(false);
    //Enfin on l'affiche
    this.setVisible(true);
    //Tout ceci ressemble à ce que nous faisons depuis le début avec notre JFrame.
  }
}

Maintenant, créons une classe qui va tester notre ZDialog :

 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
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;

public class Fenetre extends JFrame {
  private JButton bouton = new JButton("Appel à la ZDialog");

  public Fenetre(){
    this.setTitle("Ma JFrame");
    this.setSize(300, 100);
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setLocationRelativeTo(null);
    this.getContentPane().setLayout(new FlowLayout());
    this.getContentPane().add(bouton);
    bouton.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent arg0) {
        ZDialog zd = new ZDialog(null, "Coucou les ZérOs", true);
      }
    });

    this.setVisible(true);
  }

  public static void main(String[] main){
    Fenetre fen = new Fenetre();
  } 
}

La figure suivante vous présente le résultat ; bon, c'est un début.

Votre première boîte personnalisée

Je pense que vous avez deviné le rôle des paramètres du constructeur, mais je vais tout de même les expliciter :

  • JFrame Parent correspond à l'objet parent ;
  • String title correspond au titre de notre boîte ;
  • boolean modal correspond à la modalité ; true : boîte modale, false : boîte non modale.

Rien de compliqué… Il est donc temps d'ajouter des composants à notre objet. Par contre, vous conviendrez que si nous prenons la peine de construire un tel composant, nous attendons plus qu'une simple réponse à une question ouverte (oui/non), une chaîne de caractères ou encore un choix dans une liste… Nous en voulons bien plus ! Plusieurs saisies, avec plusieurs listes en même temps !

Vous avez vu que nous devrons récupérer les informations choisies dans certains cas, mais pas dans tous : nous allons donc devoir déterminer ces différents cas, ainsi que les choses à faire.

Partons du fait que notre boîte comprendra un bouton OK et un bouton Annuler : dans le cas où l'utilisateur clique sur OK, on récupère les informations, si l'utilisateur clique sur Annuler, on ne récupère rien. Et il faudra aussi tenir compte de la modalité de notre boîte : la méthode setVisible(false); met fin au dialogue ! Ceci signifie également que le dialogue s'entame au moment où l'instruction setVisible(true); est exécutée. C'est pourquoi nous allons sortir cette instruction du constructeur de l'objet et la mettre dans une méthode à part.

Maintenant, il faut que l'on puisse indiquer à notre boîte de renvoyer les informations ou non. C'est pour cela que nous allons utiliser un booléen - appelons-le sendData - initialisé à false, mais qui passera à true si on clique sur OK.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//Cas où notre ZDialog renverra le contenu
//D'un JTextField nommé jtf
public String showZDialog(){
  this.sendData = false;
  //Début du dialogue
  this.setVisible(true);
  //Le dialogue prend fin
  //Si on a cliqué sur OK, on envoie, sinon on envoie une chaîne vide !
 return (this.sendData)? jtf.getText() : "";
}

Il nous reste un dernier point à gérer.

Comment récupérer les informations saisies dans notre boîte depuis notre fenêtre, vu que nous voulons obtenir plusieurs informations ?

C'est vrai qu'on ne peut retourner qu'une valeur à la fois. Mais il peut y avoir plusieurs solutions à ce problème :

  • Dans le cas où nous n'avons qu'un composant, nous pouvons adapter la méthode showZDialog() au type de retour du composant utilisé.
  • Dans notre cas, nous voulons plusieurs composants, donc plusieurs valeurs. Vous pouvez :
    • retourner une collection de valeurs (ArrayList) ;
    • faire des accesseurs dans votre ZDialog ;
    • créer un objet dont le rôle est de collecter les informations dans votre boîte et de retourner cet objet ;
    • etc.

Nous allons opter pour un objet qui collectera les informations et que nous retournerons à la fin de la méthode showZDialog(). Avant de nous lancer dans sa création, nous devons savoir ce que nous allons mettre dans notre boîte… J'ai choisi de vous faire programmer une boîte permettant de spécifier les caractéristiques d'un personnage de jeu vidéo :

  • son nom (un champ de saisie) ;
  • son sexe (une combo) ;
  • sa taille (un champ de saisie) ;
  • sa couleur de cheveux (une combo) ;
  • sa tranche d'âge (des boutons radios).

Pour ce qui est du placement des composants, l'objet JDialog se comporte exactement comme un objet JFrame (BorderLayout par défaut, ajout d'un composant au conteneur…).

Nous pouvons donc créer notre objet contenant les informations de notre boîte de dialogue, je l'ai appelé ZDialogInfo.

 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 ZDialogInfo {
  private String nom, sexe, age, cheveux, taille;

  public ZDialogInfo(){}
  public ZDialogInfo(String nom, String sexe, String age, String cheveux, String taille){
    this.nom = nom;
    this.sexe = sexe;
    this.age = age;
    this.cheveux = cheveux;
    this.taille = taille;
  }

  public String toString(){
    String str;
    if(this.nom != null && this.sexe != null && this.taille != null && this.age != null && this.cheveux != null){
      str = "Description de l'objet InfoZDialog";
      str += "Nom : " + this.nom + "\n";
      str += "Sexe : " + this.sexe + "\n";
      str += "Age : " + this.age + "\n";
      str += "Cheveux : " + this.cheveux + "\n";
      str += "Taille : " + this.taille + "\n";
    }
    else{
      str = "Aucune information !";
    }
    return str;
  }
}

L'avantage avec cette méthode, c'est que nous n'avons pas à nous soucier d'une éventuelle annulation de la saisie : l'objet d'information renverra toujours quelque chose.

Voici le code source de notre boîte perso :

  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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.ButtonGroup;
import javax.swing.JTextField;

public class ZDialog extends JDialog {
  private ZDialogInfo zInfo = new ZDialogInfo();
  private boolean sendData;
  private JLabel nomLabel, sexeLabel, cheveuxLabel, ageLabel, tailleLabel,taille2Label, icon;
  private JRadioButton tranche1, tranche2, tranche3, tranche4;
  private JComboBox sexe, cheveux;
  private JTextField nom, taille;

  public ZDialog(JFrame parent, String title, boolean modal){
    super(parent, title, modal);
    this.setSize(550, 270);
    this.setLocationRelativeTo(null);
    this.setResizable(false);
    this.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
    this.initComponent();
  }

  public ZDialogInfo showZDialog(){
    this.sendData = false;
    this.setVisible(true);      
    return this.zInfo;      
  }

  private void initComponent(){
    //Icône
    icon = new JLabel(new ImageIcon("images/icone.jpg"));
    JPanel panIcon = new JPanel();
    panIcon.setBackground(Color.white);
    panIcon.setLayout(new BorderLayout());
    panIcon.add(icon);

    //Le nom
    JPanel panNom = new JPanel();
    panNom.setBackground(Color.white);
    panNom.setPreferredSize(new Dimension(220, 60));
    nom = new JTextField();
    nom.setPreferredSize(new Dimension(100, 25));
    panNom.setBorder(BorderFactory.createTitledBorder("Nom du personnage"));
    nomLabel = new JLabel("Saisir un nom :");
    panNom.add(nomLabel);
    panNom.add(nom);

    //Le sexe
    JPanel panSexe = new JPanel();
    panSexe.setBackground(Color.white);
    panSexe.setPreferredSize(new Dimension(220, 60));
    panSexe.setBorder(BorderFactory.createTitledBorder("Sexe du personnage"));
    sexe = new JComboBox();
    sexe.addItem("Masculin");
    sexe.addItem("Féminin");
    sexe.addItem("Indéterminé");
    sexeLabel = new JLabel("Sexe : ");
    panSexe.add(sexeLabel);
    panSexe.add(sexe);

    //L'âge 
    JPanel panAge = new JPanel();
    panAge.setBackground(Color.white);
    panAge.setBorder(BorderFactory.createTitledBorder("Age du personnage"));
    panAge.setPreferredSize(new Dimension(440, 60));
    tranche1 = new JRadioButton("15 - 25 ans");
    tranche1.setSelected(true);
    tranche2 = new JRadioButton("26 - 35 ans");
    tranche3 = new JRadioButton("36 - 50 ans");
    tranche4 = new JRadioButton("+ de 50 ans");
    ButtonGroup bg = new ButtonGroup();
    bg.add(tranche1);
    bg.add(tranche2);
    bg.add(tranche3);
    bg.add(tranche4);
    panAge.add(tranche1);
    panAge.add(tranche2);
    panAge.add(tranche3);
    panAge.add(tranche4);

    //La taille
    JPanel panTaille = new JPanel();
    panTaille.setBackground(Color.white);
    panTaille.setPreferredSize(new Dimension(220, 60));
    panTaille.setBorder(BorderFactory.createTitledBorder("Taille du personnage"));
    tailleLabel = new JLabel("Taille : ");
    taille2Label = new JLabel(" cm");
    taille = new JTextField("180");
    taille.setPreferredSize(new Dimension(90, 25));
    panTaille.add(tailleLabel);
    panTaille.add(taille);
    panTaille.add(taille2Label);

    //La couleur des cheveux
    JPanel panCheveux = new JPanel();
    panCheveux.setBackground(Color.white);
    panCheveux.setPreferredSize(new Dimension(220, 60));
    panCheveux.setBorder(BorderFactory.createTitledBorder("Couleur de cheveux du personnage"));
    cheveux = new JComboBox();
    cheveux.addItem("Blond");
    cheveux.addItem("Brun");
    cheveux.addItem("Roux");
    cheveux.addItem("Blanc");
    cheveuxLabel = new JLabel("Cheveux");
    panCheveux.add(cheveuxLabel);
    panCheveux.add(cheveux);

    JPanel content = new JPanel();
    content.setBackground(Color.white);
    content.add(panNom);
    content.add(panSexe);
    content.add(panAge);
    content.add(panTaille);
    content.add(panCheveux);

    JPanel control = new JPanel();
    JButton okBouton = new JButton("OK");

    okBouton.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent arg0) {        
        zInfo = new ZDialogInfo(nom.getText(), (String)sexe.getSelectedItem(), getAge(), (String)cheveux.getSelectedItem() ,getTaille());
        setVisible(false);
      }

      public String getAge(){
        return (tranche1.isSelected()) ? tranche1.getText() : 
               (tranche2.isSelected()) ? tranche2.getText() : 
               (tranche3.isSelected()) ? tranche3.getText() : 
               (tranche4.isSelected()) ? tranche4.getText() : 
                tranche1.getText();  
      }

      public String getTaille(){
        return (taille.getText().equals("")) ? "180" : taille.getText();
      }      
    });

    JButton cancelBouton = new JButton("Annuler");
    cancelBouton.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent arg0) {
        setVisible(false);
      }      
    });

    control.add(okBouton);
    control.add(cancelBouton);

    this.getContentPane().add(panIcon, BorderLayout.WEST);
    this.getContentPane().add(content, BorderLayout.CENTER);
    this.getContentPane().add(control, BorderLayout.SOUTH);
  }  
}

J'ai ajouté une image, mais vous n'y êtes nullement obligés ! Voici le code source permettant de tester cette boîte :

 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
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

public class Fenetre extends JFrame {
  private JButton bouton = new JButton("Appel à la ZDialog");

  public Fenetre(){      
    this.setTitle("Ma JFrame");
    this.setSize(300, 100);
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setLocationRelativeTo(null);      
    this.getContentPane().setLayout(new FlowLayout());
    this.getContentPane().add(bouton);
    bouton.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent arg0) {
        ZDialog zd = new ZDialog(null, "Coucou les ZérOs", true);
        ZDialogInfo zInfo = zd.showZDialog(); 
        JOptionPane jop = new JOptionPane();
        jop.showMessageDialog(null, zInfo.toString(), "Informations personnage", JOptionPane.INFORMATION_MESSAGE);
      }         
    });      
    this.setVisible(true);      
  }

  public static void main(String[] main){
    Fenetre fen = new Fenetre();
  }   
}

Ce qu'on obtient est montré à la figure suivante.

Différentes copies d'écran de test

Voilà : nous venons de voir comment utiliser des boîtes de dialogue. En route pour l'utilisation des menus, à présent !

Les menus

Faire son premier menu

Vous vous rappelez que j'ai mentionné qu'une MenuBar fait partie de la composition de l'objet JFrame. Le moment est venu pour vous d'utiliser un composant de ce genre. Néanmoins, celui-ci appartient au package java.awt. Dans ce chapitre nous utiliserons son homologue, l'objet JMenuBar, issu dans le package javax.swing. Pour travailler avec des menus, nous aurons besoin :

  • de l'objet JMenu, le titre principal d'un point de menu (Fichier, Édition…) ;
  • d'objets JMenuItem, les éléments composant nos menus.

Afin de permettre des interactions avec nos futurs menus, nous allons devoir implémenter l'interface ActionListener que vous connaissez déjà bien. Ces implémentations serviront à écouter les objets JMenuItem : ce sont ces objets qui déclencheront l'une ou l'autre opération. Les JMenu, eux, se comportent automatiquement : si on clique sur un titre de menu, celui-ci se déroule tout seul et, dans le cas où nous avons un tel objet présent dans un autre JMenu, une autre liste se déroulera toute seule !

Je vous propose d'enlever tous les composants (boutons, combos, etc.) de notre animation et de gérer tout cela par le biais d'un menu.

Avant de nous lancer dans cette tâche, voici une application de tout cela, histoire de vous familiariser avec les concepts et leur syntaxe.

 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
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JRadioButtonMenuItem;

public class ZFenetre extends JFrame {
  private JMenuBar menuBar = new JMenuBar();
  private JMenu test1 = new JMenu("Fichier");
  private JMenu test1_2 = new JMenu("Sous ficher");
  private JMenu test2 = new JMenu("Edition");

  private JMenuItem item1 = new JMenuItem("Ouvrir");
  private JMenuItem item2 = new JMenuItem("Fermer");
  private JMenuItem item3 = new JMenuItem("Lancer");
  private JMenuItem item4 = new JMenuItem("Arrêter");

  private JCheckBoxMenuItem jcmi1 = new JCheckBoxMenuItem("Choix 1");
  private JCheckBoxMenuItem jcmi2 = new JCheckBoxMenuItem("Choix 2");

  private JRadioButtonMenuItem jrmi1 = new JRadioButtonMenuItem("Radio 1");
  private JRadioButtonMenuItem jrmi2 = new JRadioButtonMenuItem("Radio 2");

  public static void main(String[] args){
    ZFenetre zFen = new ZFenetre();
  }

  public ZFenetre(){
    this.setSize(400, 200);
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setLocationRelativeTo(null);

    //On initialise nos menus      
    this.test1.add(item1);

    //On ajoute les éléments dans notre sous-menu
    this.test1_2.add(jcmi1);
    this.test1_2.add(jcmi2);
    //Ajout d'un séparateur
    this.test1_2.addSeparator();
    //On met nos radios dans un ButtonGroup
    ButtonGroup bg = new ButtonGroup();
    bg.add(jrmi1);
    bg.add(jrmi1);
    //On présélectionne la première radio
    jrmi1.setSelected(true);

    this.test1_2.add(jrmi1);
    this.test1_2.add(jrmi2);

    //Ajout du sous-menu dans notre menu
    this.test1.add(this.test1_2);
    //Ajout d'un séparateur
    this.test1.addSeparator();
    item2.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent arg0) {
        System.exit(0);
      }        
    });
    this.test1.add(item2);  
    this.test2.add(item3);
    this.test2.add(item4);

    //L'ordre d'ajout va déterminer l'ordre d'apparition dans le menu de gauche à droite
    //Le premier ajouté sera tout à gauche de la barre de menu et inversement pour le dernier
    this.menuBar.add(test1);
    this.menuBar.add(test2);
    this.setJMenuBar(menuBar);
    this.setVisible(true);
  }
}

L'action attachée au JMenutItem Fermer permet de quitter l'application. Ce que donne le code est affiché à la figure suivante.

Premier menu

Vous voyez qu'il n'y a rien de difficile dans l'élaboration d'un menu. Je vous propose donc d'en créer un pour notre animation. Allons-y petit à petit : nous ne gérerons les événements que par la suite. Pour le moment, nous allons avoir besoin :

  • d'un menu Animation pour lancer, interrompre (par défaut à setEnabled(false)) ou quitter l'animation ;
  • d'un menu Forme afin de sélectionner le type de forme utiliser (sous-menu + une radio par forme) et de permettre d'activer le mode morphing (case à cocher) ;
  • d'un menu À propos avec un joli « ? » qui va ouvrir une boîte de dialogue.

N'effacez surtout pas les implémentations pour les événements : retirez seulement les composants qui les utilisent. Ensuite, créez votre menu !

Voici un code qui ne devrait pas trop différer de ce que vous avez écrit :

  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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;

public class Fenetre extends JFrame{
  private Panneau pan = new Panneau();
  private JPanel container = new JPanel();
  private int compteur = 0;
  private boolean animated = true;
  private boolean backX, backY;
  private int x,y ;
  private Thread t;

  private JMenuBar menuBar = new JMenuBar();

  private JMenu animation = new JMenu("Animation"),
    forme = new JMenu("Forme"),
    typeForme = new JMenu("Type de forme"),
    aPropos = new JMenu("À propos");

  private JMenuItem lancer = new JMenuItem("Lancer l'animation"),
    arreter = new JMenuItem("Arrêter l'animation"),
    quitter = new JMenuItem("Quitter"),
    aProposItem = new JMenuItem("?");

  private JCheckBoxMenuItem morph = new JCheckBoxMenuItem("Morphing");
  private JRadioButtonMenuItem carre = new JRadioButtonMenuItem("Carré"),
    rond = new JRadioButtonMenuItem("Rond"),
    triangle = new JRadioButtonMenuItem("Triangle"),
    etoile = new JRadioButtonMenuItem("Etoile");

  private ButtonGroup bg = new ButtonGroup();

  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);

    this.setContentPane(container);
    this.initMenu();
    this.setVisible(true);            
  }

  private void initMenu(){
    //Menu animation
    animation.add(lancer);
    arreter.setEnabled(false);
    animation.add(arreter);
    animation.addSeparator();
    //Pour quitter l'application
    quitter.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent event){
        System.exit(0);
      }
    });
    animation.add(quitter);

    //Menu forme    
    bg.add(carre);
    bg.add(triangle);
    bg.add(rond);
    bg.add(etoile);

    typeForme.add(rond);
    typeForme.add(carre);    
    typeForme.add(triangle);
    typeForme.add(etoile);

    rond.setSelected(true);

    forme.add(typeForme);
    forme.add(morph);

    //Menu À propos
    aPropos.add(aProposItem);

    //Ajout des menus dans la barre de menus
    menuBar.add(animation);
    menuBar.add(forme);
    menuBar.add(aPropos);

    //Ajout de la barre de menus sur la fenêtre
    this.setJMenuBar(menuBar);
  }

  private void go(){
    //Rien n'a changé ici
  }

  public class BoutonListener implements ActionListener{
    public void actionPerformed(ActionEvent arg0) {
      JOptionPane jop = new JOptionPane();      
      int option = jop.showConfirmDialog(null, 
        "Voulez-vous lancer l'animation ?", 
        "Lancement de l'animation", 
        JOptionPane.YES_NO_OPTION, 
        JOptionPane.QUESTION_MESSAGE);

      if(option == JOptionPane.OK_OPTION){
        lancer.setEnabled(false);
        arreter.setEnabled(true);
        animated = true;
        t = new Thread(new PlayAnimation());
        t.start();     
      }
    }    
  }

  class Bouton2Listener  implements ActionListener{
    public void actionPerformed(ActionEvent e) {
      JOptionPane jop = new JOptionPane();      
      int option = jop.showConfirmDialog(null, 
        "Voulez-vous arrêter l'animation ?",
        "Arrêt de l'animation", 
        JOptionPane.YES_NO_CANCEL_OPTION, 
        JOptionPane.QUESTION_MESSAGE);

      if(option != JOptionPane.NO_OPTION && option != JOptionPane.CANCEL_OPTION && option != JOptionPane.CLOSED_OPTION){
        animated = false;
        //On remplace nos boutons par nos JMenuItem
        lancer.setEnabled(true);
        arreter.setEnabled(false);
      }
    }    
  }  

  class PlayAnimation implements Runnable{
    public void run() {
      go();     
    }    
  }  

  class FormeListener implements ActionListener{
    public void actionPerformed(ActionEvent e) {     
      //On commente cette ligne pour l'instant
      //****************************************
      //pan.setForme(combo.getSelectedItem().toString());
    }    
  }

  class MorphListener implements ActionListener{
    public void actionPerformed(ActionEvent e) {
      //Si la case est cochée, activation du mode morphing
      if(morph.isSelected())pan.setMorph(true);
        //Sinon rien !
      else pan.setMorph(false);
    }    
  }  
}

Vous devriez obtenir la figure suivante.

Notre menu et son animation

Il ne reste plus qu'à faire communiquer nos menus et notre animation ! Pour cela, rien de plus simple, il suffit d'indiquer à nos MenuItem qu'on les écoute. En fait, cela revient à faire comme si nous cliquions sur des boutons (à l'exception des cases à cocher et des radios où, là, nous pouvons utiliser une implémentation d'ActionListener ou de ItemListener), nous utiliserons donc la première méthode.

Afin que l'application fonctionne bien, j'ai apporté deux modifications mineures dans la classe Panneau. J'ai ajouté une instruction dans une condition :

1
2
3
4
//J'ai ajouté :  || this.forme.equals("CARRÉ")
if(this.forme.equals("CARRE") || this.forme.equals("CARRÉ")){
  g.fillRect(posX, posY, 50, 50);
}

… ainsi, on accepte les deux graphies ! J'ai également ajouté un toUpperCase() :

1
2
3
public void setForme(String form){      
  this.forme = form.toUpperCase();
}

Ainsi, on s'assure que cette chaîne de caractères est en majuscules.

Voici le code de notre animation avec un beau menu pour tout contrôler :

  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
//Les imports

public class Fenetre extends JFrame{

  //La déclaration des variables reste inchangée

  public Fenetre(){
    //Le constructeur est inchangé
  }

  private void initMenu(){
    //Menu Animation    
    //Ajout du listener pour lancer l'animation
    lancer.addActionListener(new StartAnimationListener());
    animation.add(lancer);

    //Ajout du listener pour arrêter l'animation
    arreter.addActionListener(new StopAnimationListener());
    arreter.setEnabled(false);
    animation.add(arreter);

    animation.addSeparator();
    quitter.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent event){
        System.exit(0);
      }
    });
    animation.add(quitter);

    //Menu Forme

    bg.add(carre);
    bg.add(triangle);
    bg.add(rond);
    bg.add(etoile);

    //On crée un nouvel écouteur, inutile de créer 4 instances différentes
    FormeListener fl = new FormeListener();
    carre.addActionListener(fl);
    rond.addActionListener(fl);
    triangle.addActionListener(fl);
    etoile.addActionListener(fl);

    typeForme.add(rond);
    typeForme.add(carre);    
    typeForme.add(triangle);
    typeForme.add(etoile);

    rond.setSelected(true);

    forme.add(typeForme);

    //Ajout du listener pour le morphing
    morph.addActionListener(new MorphListener());
    forme.add(morph);

    //Menu À propos

    //Ajout de ce que doit faire le "?"
    aProposItem.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent arg0) {
        JOptionPane jop = new JOptionPane();
        ImageIcon img = new ImageIcon("images/cysboy.gif");        
        String mess = "Merci ! \n J'espère que vous vous amusez bien !\n";
        mess += "Je crois qu'il est temps d'ajouter des accélérateurs et des "+" mnémoniques dans tout ça…\n";
        mess += "\n Allez, GO les ZérOs !";        
        jop.showMessageDialog(null, mess, "À propos", JOptionPane.INFORMATION_MESSAGE, img);        
      }            
    });
    aPropos.add(aProposItem);

    //Ajout des menus dans la barre de menus
    menuBar.add(animation);
    menuBar.add(forme);
    menuBar.add(aPropos);

    //Ajout de la barre de menus sur la fenêtre
    this.setJMenuBar(menuBar);
  }

  private void go(){
    //Idem  
  }

  public class StartAnimationListener implements ActionListener{
    public void actionPerformed(ActionEvent arg0) {      
      //Idem
    }    
  }

  /**
   * Écouteur du menu Quitter
   * @author CHerby
   */
  class StopAnimationListener  implements ActionListener{
    public void actionPerformed(ActionEvent e) {      
      //Idem
    }    
  }  

  class PlayAnimation implements Runnable{
    public void run() {
      go();      
    }    
  }  

  /**
   * Écoute les menus Forme
   * @author CHerby
   */
  class FormeListener implements ActionListener{
    public void actionPerformed(ActionEvent e) {
      pan.setForme(((JRadioButtonMenuItem)e.getSource()).getText());
    }    
  }

  /**
   * Écoute le menu Morphing
   * @author CHerby
   */
  class MorphListener implements ActionListener{
    public void actionPerformed(ActionEvent e) {
      //Si la case est cochée, activation du mode morphing
      if(morph.isSelected()) pan.setMorph(true);
      //Sinon rien !
      else pan.setMorph(false);
    }    
  }  
}

Comme je l'ai indiqué dans le dialogue du menu À propos, je crois qu'il est temps d'ajouter des raccourcis clavier à notre application ! Vous êtes prêts ?

Les raccourcis clavier

À nouveau, il est très simple d'insérer des raccourcis clavier. Pour ajouter un « accélérateur » (raccourcis clavier des éléments de menu) sur un JMenu, nous appellerons la méthode setAccelerator(); et pour ajouter un mnémonique (raccourcis permettant de simuler le clic sur un point de menu) sur un JMenuItem, nous nous servirons de la méthode setMnemonic();.

Attribuons le mnémonique « A » au menu Animation, le mnémonique « F » pour le menu Forme et enfin « P » pour À Propos. Vous allez voir, c'est très simple : il vous suffit d'invoquer la méthode setMnemonic(char mnemonic); sur le JMenu que vous désirez.

Ce qui nous donne, dans notre cas :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
private void initMenu(){
  //Menu animation
  //Le début de la méthode reste inchangé

  //Ajout des menus dans la barre de menus et ajout de mnémoniques !
  animation.setMnemonic('A');
  menuBar.add(animation);

  forme.setMnemonic('F');
  menuBar.add(forme);

  aPropos.setMnemonic('P');
  menuBar.add(aPropos);   
  //Ajout de la barre de menus sur la fenêtre
  this.setJMenuBar(menuBar);
}

Nous avons à présent les lettres correspondant au mnémonique soulignées dans nos menus. Et il y a mieux : si vous tapez ALT + <la lettre>, le menu correspondant se déroule ! La figure suivante correspond à ce que j'obtiens.

Mnémonique sur votre menu

Sachez que vous pouvez aussi mettre des mnémoniques sur les objets JMenuItem. Je dois également vous dire qu'il existe une autre façon d'ajouter un mnémonique sur un JMenu (mais c'est uniquement valable avec un JMenu) : en passant le mnémonique en deuxième paramètre du constructeur de l'objet, comme ceci :

1
JMenu menu = new JMenu("Fichier", 'F'); //Ici, ce menu aura le mnémonique F

Oui, je sais, c'est simple, très simple, même. Pour ajouter des accélérateurs, c'est quasiment pareil, si ce n'est que nous devrons utiliser un nouvel objet : KeyStroke. Cet objet permet de déterminer la touche utilisée ou à utiliser. C'est grâce à cet objet que nous allons pouvoir construire des combinaisons de touches pour nos accélérateurs ! Nous allons commencer par attribuer un simple caractère comme accélérateur à notre JMenuItem Lancer en utilisant la méthode getKeyStroke(char caracter); de l'objet KeyStroke.

Ajoutez cette ligne de code au début de la méthode initMenu() (vous aurez besoin des packages javax.swing.KeyStroke et java.awt.event.ActionEvent) :

1
2
//Cette instruction ajoute l'accélérateur 'c' à notre objet
lancer.setAccelerator(KeyStroke.getKeyStroke('c'));

Testez votre application, un petit « c » est apparu à côté du menu Lancer. La figure suivante illustre le phénomène.

Un accélérateur sur votre menu

Appuyez sur la touche c de votre clavier : celle-ci a le même effet qu'un clic sur le menu « Lancer » !

Si vous mettez le caractère « C », vous serez obligés d'appuyer simultanément sur SHIFT + c ou d'activer la touche MAJ !

Si le principe est bon, dites-vous aussi que maintenant, presser la touche c lancera systématiquement votre animation ! C'est l'une des raisons pour laquelle les accélérateurs sont, en général, des combinaisons de touches du genre CTRL + c ou encore CTRL + SHIFT + S.

Pour cela, nous allons utiliser une méthode getKeyStroke() un peu différente : elle ne prendra pas le caractère de notre touche en argument, mais son code ainsi qu'une ou plusieurs touche(s) composant la combinaison. Pour obtenir le code d'une touche, nous utiliserons l'objet KeyEvent, un objet qui stocke tous les codes des touches !

Dans le code qui suit, je crée un accélérateur CTRL + L pour le menu Lancer et un accélérateur CTRL + SHIFT + A pour le menu Arrêter :

1
2
3
4
5
6
7
lancer.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, KeyEvent.CTRL_MASK));
animation.add(lancer);
//Ajout du listener pour arrêter l'animation
arreter.addActionListener(new StopAnimationListener());
arreter.setEnabled(false);
arreter.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_DOWN_MASK + KeyEvent.SHIFT_DOWN_MASK));
animation.add(arreter);

La figure suivante présente le résultat obtenu.

Combinaison de touches pour un accélérateur

J'imagine que vous êtes perturbés par KeyEvent.VK_L et les appels du même genre. En fait, la classe KeyEvent répertorie tous les codes de toutes les touches du clavier. Une grande majorité d'entre eux sont sous la forme VK_<le caractère ou le nom de la touche>. Lisez-le ainsi : Value of Key <nom de la touche>. À part certaines touches de contrôle comme CTRL, ALT, SHIFT, etc. vous pouvez facilement retrouver le code d'une touche grâce à cet objet !

Ensuite, vous avez dû remarquer qu'en tapant KeyEvent.CTRL_DOWN_MASK, Eclipse vous a proposé quasiment la même chose (figure suivante).

Versions différentes

Vous pouvez aisément voir qu'Eclipse vous dit que la version CTRL_DOWN_MASK est la plus récente et qu'il est vivement conseillé de l'utiliser ! Vous voilà donc avec un menu comprenant des mnémoniques et des accélérateurs. Il est maintenant temps de voir comment créer un menu contextuel !

Faire un menu contextuel

Vous avez déjà fait le plus dur, je suis sûr que vous n'allez pas tarder à vous en rendre compte. Nous allons simplement utiliser un autre objet, un JPopupMenu, dans lequel nous mettrons nos JMenuItem ou/et JMenu. Bon il faudra tout de même indiquer à notre menu contextuel comment et où s'afficher, mais vous verrez que c'est très simple. Maintenant que vous commencez à bien connaître les bases de la programmation événementielle, nous passons à la vitesse supérieure !

Les points importants de notre menu contextuel

  • Dans le cas d'opérations identiques à celles accessibles par le menu, nous devrons créer des objets qui s'étendent à ces deux menus.
  • Le menu contextuel ne doit s'afficher que dans la zone où l'animation s'exécute, pas dans le menu !
  • Il ne doit s'afficher que lorsqu'on fait un clic droit, et rien d'autre !

Nous allons mettre dans notre menu contextuel les actions « Lancer l'animation », « Arrêter l'animation » ainsi que deux nouveautés :

  • changer la couleur du fond de notre animation ;
  • changer la couleur de notre forme.

Avant d'implémenter les deux nouvelles fonctionnalités, nous allons travailler sur les deux premières.

Lorsque nous lancerons l'animation, nous devrons mettre les deux menus Lancer l'animation dans l'état setEnabled(false); et les deux menus Arrêter l'animation dans l'état setEnabled(true); (et pour l'arrêter, il faudra faire l'inverse).

Comme je vous l'ai dit plus haut, nous allons utiliser le même objet qui écoute pour les deux menus. Il nous faudra créer une véritable instance de ces objets et signaler à l'application que ces objets écoutent non seulement le menu du haut, mais aussi le menu contextuel.

Nous avons parfaitement le droit de le faire : plusieurs objets peuvent écouter un même composant et plusieurs composants peuvent avoir le même objet qui les écoute ! Vous êtes presque prêts à créer votre menu contextuel, il ne vous manque que ces informations :

  • comment indiquer à notre panneau quand et où afficher le menu contextuel ;
  • comment lui spécifier qu'il doit le faire uniquement suite à un clic droit.

Le déclenchement de l'affichage du pop-up doit se faire lors d'un clic de souris. Vous connaissez une interface qui gère ce type d'événement : l'interface MouseListener. Nous allons donc indiquer à notre panneau qu'un objet du type de cette interface va l'écouter !

Tout comme dans le chapitre sur les zones de saisie, il existe une classe qui contient toutes les méthodes de ladite interface : la classe MouseAdapter. Vous pouvez implémenter celle-ci afin de ne redéfinir que la méthode dont vous avez besoin ! C'est cette solution que nous allons utiliser.

Si vous préférez, vous pouvez utiliser l'événement mouseClicked, mais je pensais plutôt à mouseReleased(), pour une raison simple à laquelle vous n'avez peut-être pas pensé : si ces deux événements sont quasiment identiques, dans un certain cas, seul l'événement mouseClicked() sera appelé. Il s'agit du cas où vous cliquez sur une zone, déplacez votre souris en dehors de la zone tout en maintenant le clic et relâchez le bouton de la souris. C'est pour cette raison que je préfère utiliser la méthode mouseReleased(). Ensuite, pour préciser où afficher le menu contextuel, nous allons utiliser la méthode show(Component invoker, int x, int y); de la classe JPopupMenu :

  • Component invoker : désigne l'objet invoquant le menu contextuel, dans notre cas, l'instance de Panneau.
  • int x : coordonnée x du menu.
  • int y : coordonnée y du menu.

Souvenez-vous que vous pouvez déterminer les coordonnées de la souris grâce à l'objet passé en paramètre de la méthode mouseReleased(MouseEvent event).

Je suis sûr que vous savez comment vous y prendre pour indiquer au menu contextuel de s'afficher et qu'il ne vous manque plus qu'à détecter le clic droit. C'est là que l'objet MouseEvent va vous sauver la mise ! En effet, il possède une méthode isPopupTrigger() qui renvoie vrai s'il s'agit d'un clic droit. Vous avez toutes les cartes en main pour élaborer votre menu contextuel (rappelez-vous que nous ne gérons pas encore les nouvelles fonctionnalités).

Je vous laisse quelques instants de réflexion…

Vous avez fini ? Nous pouvons comparer nos codes ? Je vous invite à consulter le code ci-dessous (il ne vous montre que les nouveautés).

  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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//Les imports habituels
import javax.swing.JPopupMenu;

public class Fenetre extends JFrame{
  //Nos variables habituelles

  //La déclaration pour le menu contextuel 
  private JPopupMenu jpm = new JPopupMenu();
  private JMenu background = new JMenu("Couleur de fond");
  private JMenu couleur = new JMenu("Couleur de la forme");

  private JMenuItem launch = new JMenuItem("Lancer l'animation");      
  private JMenuItem stop = new JMenuItem("Arrêter l'animation");
  private JMenuItem  rouge = new JMenuItem("Rouge"),
    bleu = new JMenuItem("Bleu"),
    vert = new JMenuItem("Vert"),
    rougeBack = new JMenuItem("Rouge"),
    bleuBack = new JMenuItem("Bleu"),
    vertBack = new JMenuItem("Vert");

  //On crée des listeners globaux
  private StopAnimationListener stopAnimation=new StopAnimationListener();
  private StartAnimationListener startAnimation=new StartAnimationListener();

  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());

    //On initialise le menu stop
    stop.setEnabled(false);
    //On affecte les écouteurs
    stop.addActionListener(stopAnimation);
    launch.addActionListener(startAnimation);

    //On crée et on passe l'écouteur pour afficher le menu contextuel
    //Création d'une implémentation de MouseAdapter
    //avec redéfinition de la méthode adéquate
    pan.addMouseListener(new MouseAdapter(){
      public void mouseReleased(MouseEvent event){
        //Seulement s'il s'agit d'un clic droit
        //if(event.getButton() == MouseEvent.BUTTON3)
        if(event.isPopupTrigger()){       
          background.add(rougeBack);
          background.add(bleuBack);
          background.add(vertBack);

          couleur.add(rouge);
          couleur.add(bleu);
          couleur.add(vert);

          jpm.add(launch);
          jpm.add(stop);
          jpm.add(couleur);
          jpm.add(background);
          //La méthode qui va afficher le menu
          jpm.show(pan, event.getX(), event.getY());
        }
      }
    });

    container.add(pan, BorderLayout.CENTER);

    this.setContentPane(container);
    this.initMenu();
    this.setVisible(true);    

  }

  private void initMenu(){
    //Ajout du listener pour lancer l'animation
    //ATTENTION, LE LISTENER EST GLOBAL !!! 
    lancer.addActionListener(startAnimation);
    //On attribue l'accélerateur c
    lancer.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, KeyEvent.CTRL_MASK));
    animation.add(lancer);

    //Ajout du listener pour arrêter l'animation
    //LISTENER A CHANGER ICI AUSSI
    arreter.addActionListener(stopAnimation);
    arreter.setEnabled(false);
    arreter.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_DOWN_MASK + KeyEvent.SHIFT_DOWN_MASK));
    animation.add(arreter);

    //Le reste est inchangé
  }

  private void go(){
    //La méthode n'a pas changé    
  }

  public class StartAnimationListener implements ActionListener{
    public void actionPerformed(ActionEvent arg0) {
      JOptionPane jop = new JOptionPane();     
      int option = jop.showConfirmDialog(null, 
        "Voulez-vous lancer l'animation ?", 
        "Lancement de l'animation", 
        JOptionPane.YES_NO_OPTION, 
        JOptionPane.QUESTION_MESSAGE);

      if(option == JOptionPane.OK_OPTION){
        lancer.setEnabled(false);
        arreter.setEnabled(true);

        //On ajoute l'instruction pour le menu contextuel
        launch.setEnabled(false);
        stop.setEnabled(true);

        animated = true;
        t = new Thread(new PlayAnimation());
        t.start();     
      }
    }    
  }

  /**
  * Écouteur du menu Quitter
  * @author CHerby
  */
  class StopAnimationListener  implements ActionListener{

    public void actionPerformed(ActionEvent e) {
      JOptionPane jop = new JOptionPane();     
      int option = jop.showConfirmDialog(null, 
        "Voulez-vous arrêter l'animation ?",
        "Arrêt de l'animation", 
        JOptionPane.YES_NO_CANCEL_OPTION, 
        JOptionPane.QUESTION_MESSAGE);

      if(option != JOptionPane.NO_OPTION && option != JOptionPane.CANCEL_OPTION && option != JOptionPane.CLOSED_OPTION){
        animated = false;
        //On remplace nos boutons par nos JMenuItem
        lancer.setEnabled(true);
        arreter.setEnabled(false);

        //On ajoute l'instruction pour le menu contextuel
        launch.setEnabled(true);
        stop.setEnabled(false);
      }
    }    
  }  

  class PlayAnimation implements Runnable{
    //Inchangé
  }  

  class FormeListener implements ActionListener{
    //Inchangé    
  }

  class MorphListener implements ActionListener{
    //Inchangé    
  }  
}

La figure suivante vous montre ce que j'obtiens.

Menu contextuel

Il est beau, il est fonctionnel notre menu contextuel !

Je sens que vous êtes prêts pour mettre les nouvelles options en place, même si je me doute que certains d'entre vous ont déjà fait ce qu'il fallait. Allez, il n'est pas très difficile de coder ce genre de choses (surtout que vous êtes habitués, maintenant). Dans notre classe Panneau, nous utilisons des couleurs prédéfinies. Ainsi, il nous suffit de mettre ces couleurs dans des variables et de permettre leur modification.

Rien de difficile ici, voici donc les codes sources de nos deux classes.

Panneau.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
import java.awt.Color;
//Les autres imports

public class Panneau extends JPanel {
  //Les variables définies auparavant ne changent pas
  //On y ajoute nos deux couleurs
  private Color couleurForme = Color.red;
  private Color couleurFond = Color.white;

  public void paintComponent(Graphics g){
    //Affectation de la couleur de fond   
    g.setColor(couleurFond);
    g.fillRect(0, 0, this.getWidth(), this.getHeight());

    //Affectation de la couleur de la forme
    g.setColor(couleurForme);
    //Si le mode morphing est activé, on peint le morphing
    if(this.morph)
      drawMorph(g);
    //Sinon, mode normal
    else
      draw(g);
  }

  //Méthode qui redéfinit la couleur du fond
  public void setCouleurFond(Color color){
    this.couleurFond = color;
  }

  //Méthode qui redéfinit la couleur de la forme
  public void setCouleurForme(Color color){
    this.couleurForme = color;
  }

  //Les autres méthodes sont inchangées

}

Fenetre.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
//Nos imports habituels

public class Fenetre extends JFrame{
  //Nos variables n'ont pas changé

  //On crée des listeners globaux
  private StopAnimationListener stopAnimation = new StopAnimationListener();
  private StartAnimationListener startAnimation = new StartAnimationListener()
  //Avec des listeners pour les couleurs
  private CouleurFondListener bgColor = new CouleurFondListener();
  private CouleurFormeListener frmColor = new CouleurFormeListener();

  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());

    //On initialise le menu stop
    stop.setEnabled(false);
    //On affecte les écouteurs
    stop.addActionListener(stopAnimation);
    launch.addActionListener(startAnimation);

    //On affecte les écouteurs aux points de menu
    rouge.addActionListener(frmColor);
    bleu.addActionListener(frmColor);
    vert.addActionListener(frmColor);
    blanc.addActionListener(frmColor);

    rougeBack.addActionListener(bgColor);
    bleuBack.addActionListener(bgColor);
    vertBack.addActionListener(bgColor);
    blancBack.addActionListener(bgColor);
    //On crée et on passe l'écouteur pour afficher le menu contextuel
    //Création d'une implémentation de MouseAdapter
    //avec redéfinition de la méthode adéquate
    pan.addMouseListener(new MouseAdapter(){
      public void mouseReleased(MouseEvent event){
        //Seulement s'il s'agit d'un clic droit
        if(event.isPopupTrigger()){             
          background.add(blancBack);
          background.add(rougeBack);
          background.add(bleuBack);
          background.add(vertBack);

          couleur.add(blanc);
          couleur.add(rouge);
          couleur.add(bleu);
          couleur.add(vert);

          jpm.add(launch);
          jpm.add(stop);
          jpm.add(couleur);
          jpm.add(background);

          //La méthode qui va afficher le menu
          jpm.show(pan, event.getX(), event.getY());
        }
      }
    });

    container.add(pan, BorderLayout.CENTER);
    this.setContentPane(container);
    this.initMenu();
    this.setVisible(true);            
  }

  private void initMenu(){
    //Le menu n'a pas changé
  }

  private void go(){
    //La méthode go() est identique
  }

  //Les classes internes : 
  // -> StartAnimationListener
  // -> StopAnimationListener
  // -> PlayAnimation
  // -> FormeListener
  // -> MorphListener
  //sont inchangées !

  //Écoute le changement de couleur du fond
  class CouleurFondListener implements ActionListener{
    public void actionPerformed(ActionEvent e) {

      if(e.getSource() == vertBack)
        pan.setCouleurFond(Color.green);
      else if (e.getSource() == bleuBack)
        pan.setCouleurFond(Color.blue);
      else if(e.getSource() == rougeBack)
        pan.setCouleurFond(Color.red);
      else
        pan.setCouleurFond(Color.white);
    }
  }

  //Écoute le changement de couleur du fond
  class CouleurFormeListener implements ActionListener{
    public void actionPerformed(ActionEvent e) {
      if(e.getSource() == vert)
        pan.setCouleurForme(Color.green);
      else if (e.getSource() == bleu)
        pan.setCouleurForme(Color.blue);
      else if(e.getSource() == rouge)
        pan.setCouleurForme(Color.red);
      else
        pan.setCouleurForme(Color.white);
    }
  } 
}

la figure suivante représente deux résultats ainsi obtenus.

Changement de couleur via le menu contextuel

Vous conviendrez que les menus et les menus contextuels peuvent s'avérer vraiment utiles et ergonomiques ! En plus, ils sont relativement simples à implémenter (et à utiliser). Cependant, vous avez sans doute remarqué qu'il y a beaucoup de clics superflus, que ce soit pour utiliser un menu ou menu contextuel : il faut au moins un clic pour afficher leur contenu (sauf dans le cas de l'accélérateur).

Pour contrer ce genre de chose, il existe un concept très puissant : la barre d'outils !

Les barres d'outils

La figure suivante représente un exemple de barre d'outils (il s'agit de la partie encadrée).

Exemple de barre d'outils

Pour faire simple, la barre d'outils sert à effectuer des actions disponibles dans le menu, mais sans devoir fouiller dans celui-ci ou mémoriser le raccourci clavier (accélérateur) qui y est lié. Elle permet donc des actions rapides.

Elle est généralement composée d'une multitude de boutons, une image apposée sur chacun d'entre eux symbolisant l'opération qu'il peut effectuer.

Pour créer et utiliser une barre d'outils, nous allons utiliser l'objet JToolBar. Je vous rassure tout de suite, cet objet fonctionne comme un menu classique, à une différence près : celui-ci prend des boutons (JButton) en arguments, et il n'y a pas d'endroit spécifique où incorporer votre barre d'outils (il faudra l'expliciter lors de sa création).

Tout d'abord, il nous faut des images à mettre sur nos boutons… J'en ai fait de toutes simples (figure suivante), mais libre à vous d'en choisir d'autres.

Images pour la barre d'outils

Au niveau des actions à gérer, pour le lancement de l'animation et l'arrêt, il faudra penser à éditer le comportement des boutons de la barre d'outils comme on l'a fait pour les deux actions du menu contextuel. Concernant les boutons pour les formes, c'est un peu plus délicat. Les autres composants qui éditaient la forme de notre animation étaient des boutons radios. Or, ici, nous avons des boutons standard. Outre le fait qu'il va falloir une instance précise de la classe FormeListener, nous aurons à modifier un peu son comportement…

Il nous faut savoir si l'action vient d'un bouton radio du menu ou d'un bouton de la barre d'outils : c'est l'objet ActionEvent qui nous permettra d'accéder à cette information. Nous n'allons pas tester tous les boutons radio un par un, pour ces composants, le système utilisé jusque-là était très bien. Non, nous allons simplement vérifier si celui qui a déclenché l'action est un JRadioButtonMenuItem, et si c'est le cas, nous testerons les boutons.

Rappelez-vous le chapitre sur la réflexivité ! La méthode getSource() nous retourne un objet, il est donc possible de connaître la classe de celui-ci avec la méthode getClass() et par conséquent d'en obtenir le nom grâce à la méthode getName().

Il va falloir qu'on pense à mettre à jour le bouton radio sélectionné dans le menu. Et là, pour votre plus grand bonheur, je connais une astuce qui marche pas mal du tout : lors du clic sur un bouton de la barre d'outils, il suffit de déclencher l'événement sur le bouton radio correspondant ! Dans la classe AbstractButton, dont héritent tous les boutons, il y a la méthode doClick(). Cette méthode déclenche un événement identique à un vrai clic de souris sur le composant ! Ainsi, plutôt que de gérer la même façon de faire à deux endroits, nous allons rediriger l'action effectuée sur un composant vers un autre.

Vous avez toutes les cartes en main pour réaliser votre barre d'outils. N'oubliez pas que vous devez spécifier sa position sur le conteneur principal ! Bon. Faites des tests, comparez, codez, effacez… au final, vous devriez avoir quelque chose comme ceci :

  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
135
136
137
138
139
140
141
142
143
144
145
import javax.swing.JToolBar;
//Nos imports habituels

public class Fenetre extends JFrame{
  //Les variables declarées précédemment

  //Création de notre barre d'outils
  private JToolBar toolBar = new JToolBar();

  //Les boutons de la barre d'outils
  private JButton   play = new JButton(new ImageIcon("images/play.jpg")),
    cancel = new JButton(new ImageIcon("images/stop.jpg")),
    square = new JButton(new ImageIcon("images/carré.jpg")),
    tri = new JButton(new ImageIcon("images/triangle.jpg")),
    circle = new JButton(new ImageIcon("images/rond.jpg")),
    star = new JButton(new ImageIcon("images/étoile.jpg"));

  private Color fondBouton = Color.white;
  private FormeListener fListener = new FormeListener();

  public Fenetre(){
    //La seule nouveauté est la méthode ci-dessous
    this.initToolBar();
    this.setVisible(true);            
  }

  private void initToolBar(){
    this.cancel.setEnabled(false);
    this.cancel.addActionListener(stopAnimation);
    this.cancel.setBackground(fondBouton);
    this.play.addActionListener(startAnimation);
    this.play.setBackground(fondBouton);

    this.toolBar.add(play);
    this.toolBar.add(cancel);
    this.toolBar.addSeparator();

    //Ajout des Listeners
    this.circle.addActionListener(fListener);
    this.circle.setBackground(fondBouton);
    this.toolBar.add(circle);

    this.square.addActionListener(fListener);
    this.square.setBackground(fondBouton);
    this.toolBar.add(square);

    this.tri.setBackground(fondBouton);
    this.tri.addActionListener(fListener);
    this.toolBar.add(tri);

    this.star.setBackground(fondBouton);
    this.star.addActionListener(fListener);
    this.toolBar.add(star);

    this.add(toolBar, BorderLayout.NORTH);    
  }

  private void initMenu(){
    //Méthode inchangée
  }

  private void go(){
    //Méthode inchangée    
  }

  public class StartAnimationListener implements ActionListener{
    public void actionPerformed(ActionEvent arg0) {
      //Toujours la même boîte de dialogue…

      if(option == JOptionPane.OK_OPTION){
        lancer.setEnabled(false);
        arreter.setEnabled(true);

        //ON AJOUTE L'INSTRUCTION POUR LE MENU CONTEXTUEL
        //************************************************
        launch.setEnabled(false);
        stop.setEnabled(true);

        play.setEnabled(false);
        cancel.setEnabled(true);

        animated = true;
        t = new Thread(new PlayAnimation());
        t.start();      
      }
    }    
  }

  /**
   * Écouteur du menu Quitter
   * @author CHerby
   */
  class StopAnimationListener  implements ActionListener{

    public void actionPerformed(ActionEvent e) {
      //Toujours la même boîte de dialogue…

      if(option != JOptionPane.NO_OPTION && option != JOptionPane.CANCEL_OPTION && option != JOptionPane.CLOSED_OPTION){
        animated = false;
        //On remplace nos boutons par nos MenuItem
        lancer.setEnabled(true);
        arreter.setEnabled(false);

        //ON AJOUTE L'INSTRUCTION POUR LE MENU CONTEXTUEL
        //************************************************
        launch.setEnabled(true);
        stop.setEnabled(false);

        play.setEnabled(true);
        cancel.setEnabled(false);        
      }
    }    
  }  

  class FormeListener implements ActionListener{
    public void actionPerformed(ActionEvent e) {

      //Si l'action vient d'un bouton radio du menu 
      if(e.getSource().getClass().getName().equals("javax.swing.JRadioButtonMenuItem"))
        pan.setForme(((JRadioButtonMenuItem)e.getSource()).getText());
      else{
        if(e.getSource() == square){
          carre.doClick();
        }
        else if(e.getSource() == tri){
          triangle.doClick();
        }
        else if(e.getSource() == star){
          etoile.doClick();
        }
        else{
          rond.doClick();
        }
      }
    }    
  }

  //Les classes internes : 
  // -> CouleurFondListener
  // -> CouleurFormeListener 
  // -> PlayAnimation
  // -> MorphListener
  //sont inchangées !

}

Vous devez obtenir une IHM semblable à la figure suivante.

Votre barre d'outils

Elle n'est pas jolie, votre IHM, maintenant ? Vous avez bien travaillé, surtout qu'à présent, je vous explique peut-être les grandes lignes, mais je vous force à aussi réfléchir par vous-mêmes ! Eh oui, vous avez appris à penser en orienté objet et connaissez les points principaux de la programmation événementielle. Maintenant, il vous reste simplement à acquérir des détails techniques spécifiques (par exemple, la manière d'utiliser certains objets).

Pour ceux qui l'auraient remarqué, la barre d'outils est déplaçable ! Si vous cliquez sur la zone mise en évidence à la figure suivante, vous pourrez la repositionner.

Zone de déplacement

Il suffit de maintenir le clic et de faire glisser votre souris vers la droite, la gauche ou encore le bas. Vous verrez alors un carré se déplacer et, lorsque vous relâcherez le bouton, votre barre aura changé de place, comme le montre la figure suivante.

Déplacement de la barre d'outils

Elles sont fortes ces barres d'outils, tout de même ! En plus de tout ça, vous pouvez utiliser autre chose qu'un composant sur une barre d'outils…

Utiliser les actions abstraites

Nous avons vu précédemment comment centraliser des actions sur différents composants. Il existe une classe abstraite qui permet de gérer ce genre de choses, car elle peut s'adapter à beaucoup de composants (en général à ceux qui ne font qu'une action, comme un bouton, une case à cocher, mais pas une liste).

Le rôle de cette classe est d'attribuer automatiquement une action à un ou plusieurs composants. Le principal avantage de ce procédé est que plusieurs composants travaillent avec une implémentation de la classe AbstractAction, mais son gros inconvénient réside dans le fait que vous devrez programmer une implémentation par action :

  • une action pour la couleur de la forme en rouge ;
  • une action pour la couleur de la forme en bleu ;
  • une action pour la couleur de la forme en vert ;
  • une action pour la couleur du fond en rouge ;
  • une action pour la couleur du fond en bleu ;
  • etc.

Cela peut être très lourd à faire, mais je laisse votre bon sens déterminer s'il est pertinent d'utiliser cette méthode ou non !

Voici comment s'implémente cette classe :

 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
public class Fenetre extends JFrame{
  //Nous pouvons utiliser les actions abstraites directement dans un JButton
  private JButton bouton1 = new JButton(new RougeAction("ActionRouge", new ImageIcon("images/rouge.jpg"));

  //Ou créer une instance concrète
  private RougeAction rAct = new RougeAction("ActionRouge", new ImageIcon("images/rouge.jpg"));
  private JToolBar toolBar = new JToolBar();

  //…

  //Utiliser une action directement dans une barre d'outils
  private void initToolBar(){
    toolBar.add(rAct);
  }

  //…

  class RougeAction extends AbstractAction{
    //Constructeur avec le nom uniquement
    public RougeAction(String name){super(name);}

    //Le constructeur prend le nom et une icône en paramètre
    public RougeAction(String name, ImageIcon){super(name, img);}

    public void actionPerformed(ActionEvent){
      //Vous connaissez la marche à suivre
    }
  }
}

Vous pouvez voir que cela peut être très pratique. Désormais, si vous ajoutez une action sur une barre d'outils, celle-ci crée automatiquement un bouton correspondant ! Utiliser les actions abstraites plutôt que des implémentations de telle ou telle interface est un choix qui vous revient. Nous pouvons d'ailleurs très bien appliquer ce principe au code de notre animation, mais vous constaterez qu'il s'alourdira, nous éviterons donc de le faire… Mais comme je vous le disais, c'est une question de choix et de conception.


  • Les boîtes de dialogue s'utilisent, à l'exception des boîtes personnalisées, avec l'objet JOptionPane.
  • La méthode showMessageDialog() permet d'afficher un message informatif.
  • La méthode showConfirmDialog() permet d'afficher une boîte attendant une réponse à une question ouverte (oui/non).
  • La méthode citée ci-dessus retourne un entier correspondant au bouton sur lequel vous avez cliqué.
  • La méthode showInputDialog() affiche une boîte attendant une saisie clavier ou une sélection dans une liste.
  • Cette méthode retourne soit un String dans le cas d'une saisie, soit un Object dans le cas d'une liste.
  • La méthode showOptionDialog() affiche une boîte attendant que l'utilisateur effectue un clic sur une option.
  • Celle-ci retourne l'indice de l'élément sur lequel vous avez cliqué ou un indice négatif dans tous les autres cas.
  • Les boîtes de dialogue sont dites « modales » : aucune interaction hors de la boîte n'est possible tant que celle-ci n'est pas fermée !
  • Pour faire une boîte de dialogue personnalisée, vous devez créer une classe héritée de JDialog.
  • Pour les boîtes personnalisées, le dialogue commence lorsque setVisible(true) est invoquée et se termine lorsque la méthode setVisible(false) est appelée.
  • L'objet servant à insérer une barre de menus sur vos IHM swing est un JMenuBar.
  • Dans cet objet, vous pouvez mettre des objets JMenu afin de créer un menu déroulant.
  • L'objet cité ci-dessus accepte des objets JMenu, JMenuItem, JCheckBoxMenuItem et JRadioButtonMenuItem.
  • Afin d'interagir avec vos points de menu, vous pouvez utiliser une implémentation de l'interface ActionListener.
  • Pour faciliter l'accès aux menus de la barre de menus, vous pouvez ajouter des mnémoniques à ceux-ci.
  • L'ajout d'accélérateurs permet de déclencher des actions, le plus souvent par des combinaisons de touches.
  • Afin de récupérer les codes des touches du clavier, vous devrez utiliser un objet KeyStroke ainsi qu'un objet KeyEvent.
  • Un menu contextuel fonctionne comme un menu normal, à la différence qu'il s'agit d'un objet JPopupMenu. Vous devez toutefois spécifier le composant sur lequel doit s'afficher le menu contextuel.
  • La détection du clic droit se fait grâce à la méthode isPopupTrigger() de l'objet MouseEvent.
  • L'ajout d'une barre d'outils nécessite l'utilisation de l'objet JToolBar.