Les menus et les actions

Chapitres traités   

Java nous permet de doter une fenêtre de menus déroulants. Comme dans la plupart des applications du commerce, nous disposons de deux possibilités complémentaires :
- Créer une barre de menus qui s'affiche en haut de la fenêtre, et dans laquelle chaque menu pourra faire apparaître une liste d'options.
- Faire apparaître à un moment donné ce que nous nommons un menu surgissant (ou menu contextuel), formé quant à lui d'une seule liste d'options spécifiques correspondantes à l'endroit où se situe la demande.

Nous commencerons par la première possibilité, ce qui nous permettra d'exposer les principes généraux de construction de menus et d'exploitation des événements correspondants. Puis nous verrons comment utiliser des options de menus se présentant sous la forme de cases à cocher ou de boutons radio. Nous aborderons ensuite le cas particulier des menus surgissants.

Nous apprendrons à accéder à une option de menu par le biais de lettres mnémoniques ou de combinaisons de touches nommés accélérateurs. Nous verrons également comment éclairer l'utilisateur sur le rôle d'une option par une bulle d'information. Nous apporterons ensuite quelques précisions concernant la dynamique des menus, c'est-à-dire l'activation ou la désactivation d'une option lors de l'exécution, ou encore l'introduction ou la suppression d'options.

Enfin, nous montrerons comment la notion d'action facilite la réalisation de code dans lesquels une même action peut être provoquée de différentes manières par l'utilisateur.

Choix du chapitre Les principes des menus déroulants

Une barre de menu située au sommet de la fenêtre contient les noms des menus déroulants, il suffit de cliquer dessus pour les ouvrir, ce qui fait apparaître des options de menu et de sous-menus. Lorsque l'utilisateur clique sur une option de menu, tous les menus sont fermés et un message est envoyé au programme.

Un JMenu est un menu déroulant standard dont le nom est fixé. Les menus peuvent contenir d'autres menus en tant qu'éléments de sous-menu, ce qui permet de mettre en place des structures de menus complexes. Nous pouvons les placer à tout endroit destiné à un composant. Une autre classe, JMenuBar, héberge des menus dans une barre horizontale. Les barres de menu sont elles aussi de vrais composants, que vous pouvez donc placer à tout endroit d'un conteneur : en haut, en bas ou au milieu. Mais au milieu d'un conteneur, on placera généralement plutôt un JComboBox qu'un menu quelconque.

Ces menus déroulants usuels font donc intervenir trois sortes d'objets :

  1. Un objet barre de menus : JMenuBar.
  2. Différents objets menu qui seront visibles dans la barre de menus : JMenu.
  3. Pour chaque menu, les différentes options qui le constituent : JMenuItem.

Les options d'un menu, appelé aussi éléments, peuvent être associés à des images et des raccourcis clavier ; il existe même des éléments de menu qui ressemblent à des cases à cocher ou des boutons radio. Les éléments d'un menu sont une sorte de bouton : comme les boutons, ils déclenchent des événements d'action lorsqu'ils sont sélectionnés. Il est alors possible de répondre aux éléments d'un menu en enregistrant des écouteurs d'action avec eux.

Création d'une barre de menus

La création de menus est une opération assez simple. Voici la séquence à respecter avec ses différentes phases :

 
Nous commençons par créer une barre de menus

JMenuBar barre = new JMenuBar();

Placement de la barre de menu sur le composant désiré

Une barre de menus est un simple composant que vous ajouter n'importe où. Le plus souvent, elle est placée au sommet d'un cadre de fenêtre. Pour cela, la méthode setJMenuBar() doit être appelée :

JFrame fenêtre = new JFrame();
fenêtre.setJMenuBar(barre);

Création des différents menus associés à la barre

Les différents objets menus sont créés par appel d'un constructeur de JMenu, auquel nous fournissons le nom d'un menu, tel qu'il figurera dans la barre. Ensuite, chaque objet menu est ajouté à la barre au travers de la méthode add() de la classe JMenuBar :

JMenu édition = new JMenu("Edition");
barre.add(édition);

Mise en place des différents options associées à chaque menu

Enfin, les différentes options d'un menu sont créées par appel d'un constructeur de JMenuItem, auquel nous fournissons, là encore, le nom de l'option telle qu'elle apparaîtra lorsque l'utilisateur lancera l'affichage du menu déroulant. Chaque option est ajoutée à un menu au travers de la méthode add() de la classe JMenu :

JMenuItem couper = new JMenuItem("Couper");
édition.add(couper);
JMenuItem copier = new JMenuItem("Copier");
édition.add(copier);
JMenuItem coller = new JMenuItem("Coller");
édition.add(coller);
édition.addSeparator();
JMenu option = new JMenu("Option");
édition.add(option);

Comme pour la barre d'outils, il est possible de prévoir un séparateur dans votre menu déroulant. ceci est réalisé au travers de la méthode addSeparator() de JMenu.

Remarquez bien qu'un menu peut lui-même contenir un autre menu, qui devient alors un sous-menu. Il suffit, tout simplement, de proposer le nouveau menu au travers de la méthode add() de JMenu comme nous l'avons fait dans l'exemple précédent.

Exemple de mise en place de barre de menus

import javax.swing.*;
import java.awt.*;

public class Menus extends JFrame {
   private JMenuBar barre = new JMenuBar();
   private JMenu fichier = new JMenu("Fichier");
   private JMenu édition = new JMenu("Edition");
   private JMenuItem ouvrir = new JMenuItem("Ouvrir");
   private JMenuItem enregistrer = new JMenuItem("Enregistrer");
   private JMenuItem quitter = new JMenuItem("Quitter");
   private JMenuItem couper = new JMenuItem("Couper");
   private JMenuItem copier = new JMenuItem("Copier");
   private JMenuItem coller = new JMenuItem("Coller");
   
   public Menus() {
      super("Les menus");
      setJMenuBar(barre);
      barre.add(fichier);
      barre.add(édition);
      fichier.add(ouvrir);
      fichier.add(enregistrer);
      fichier.addSeparator();
      fichier.add(quitter);
      édition.add(couper);
      édition.add(copier);
      édition.add(coller);
      getContentPane().setBackground(Color.YELLOW);
      setSize(300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new Menus(); }
}

Gérer les événements associés à la sélection d'une option de menu

Lorsqu'un utilisateur sélectionne un menu, un événement de type Action est déclenché. Nous connaisons déjà bien cette technique. Rappelez-vous qu'il s'agit d'installer un écouteur d'action à chaque option de menu pour prendre en compte tous les fonctionnalités désirées.

Ainsi, l'action de sélection d'une option (JMenuItem) génère un événement de type Action que nous pouvons traiter en associant un écouteur ActionListener à l'objet correspondant au travers de la méthode addActionListener(). Dans la méthode actionPerformed() de cet écouteur, l'option concernée pourra être identifiée classiquement au moyen de la méthode getSource() de la classe ActionEvent.

Nous pourront aussi, le cas échéant, recourir à la méthode getActionCommand() de la même classe ActionEvent, qui comme pour un bouton, fournit la chaîne de commande associée à l'option. Par défaut, il s'agit tout simplement du nom de l'option (fournie au constructeur de JMenuItem) mais, là encore, celle-ci pourrait éventuellement être modifiée en se servant de la méthode setActionCommand() de la classe JMenuItem.

Je rappelle que cette méthode setActionCommand() est issue de la classe abstraite AbstractButton qui est utilisée, par héritage, aussi bien par la classe JButton que JMenuItem.

Pour connaître plus précisément la classe AbstractButton, repportez-vous sur l'étude suivante : Sélection et choix.
§

Une possibilité intéressante, qui permet de traiter la fonctionnalité requise de façon personnalisée pour chaque option, est de proposer une classe anonyme au moment de l'appel de la méthode addActionListener(), et qui gère ainsi l'événement spécifique à chaque objet JMenuItem.

Exemple de gestion d'événements qui permet de réaliser du copier-coller sur un éditeur basique

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Menus extends JFrame {
   private JMenuBar barre = new JMenuBar();
   private JMenu édition = new JMenu("Edition");
   private JMenuItem couper = new JMenuItem("Couper");
   private JMenuItem copier = new JMenuItem("Copier");
   private JMenuItem coller = new JMenuItem("Coller");
   private JTextArea éditeur = new JTextArea();
   
   public Menus() {
      super("Les menus");
      setJMenuBar(barre);
      barre.add(édition);
      couper.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.cut();
         }
      });
      édition.add(couper);
      copier.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.copy();
         }
      });
      édition.add(copier);
      coller.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.paste();
         }
      });
      édition.add(coller);
      éditeur.setBackground(Color.YELLOW);
      add(new JScrollPane(éditeur));
      setSize(300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new Menus(); }
}

Nous n'avons parlé que des événements générés par les options elles-mêmes. En toute rigueur, les menus (JMenu) génèrent des événements de la catégorie MenuEvent lors de leur affichage ou lors de leur disparition. L'écouteur correspondant est ajouté par addMenuListener() et il doit alors implémenter l'interface MenuListener contenant les trois méthodes (il n'y a pas d'adaptateur) : menuSelected(), menuDeselected() et menuCanceled(). En pratique, ces possibilités sont peu utilisées.

Méthode add() de JMenu qui crée automatiquement une option de menu JMenuItem

Il existe une méthode bien pratique de JMenu, qui s'appelle également add(), mais qui attend une chaîne de caractères au lieu d'un objet de type JMenuItem. Cette méthode ajoute tout simplement une option à la fin du menu, donc également un JMenuItem qui porte le même nom que l'argument proposé.

Voici comment, par exemple, rajouter l'option Couper au menu Edition :

JMenu édition = new JMenu("Edition");
édition.add("Couper");

L'avantage de cette technique, c'est qu'en une seule ligne, nous créons l'option, et nous l'ajoutons, en même temps, au menu déroulant. De plus, cette méthode add() renvoie l'option de menu créée (comme l'autre méthode add() d'ailleurs), afin que vous puissiez la capturer et lui associer un écouteur :

JMenuItem couper = édition.add("Couper");
couper.addActionListener(écouteur);

Grâce à cette technique, nous pouvons même proposer trois traitements spécifiques sur une seule ligne de code :

  1. Création de l'option.
  2. Ajout de cette option au menu déroulant.
  3. Associer une action spécifique à un événement de type sélection.

JMenu édition = new JMenu("Edition");
édition.add("Couper").addActionListener(écouteur);

Simplification du code de l'éditeur précédent

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Menus extends JFrame {
   private JMenuBar barre = new JMenuBar();
   private JMenu édition = new JMenu("Edition");
   private JTextArea éditeur = new JTextArea();
   
   public Menus() {
      super("Les menus");
      setJMenuBar(barre);
      barre.add(édition);
      édition.add("Couper").addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.cut();
         }
      });
      édition.add("Copier").addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.copy();
         }
      });
      édition.add("Coller").addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.paste();
         }
      });
      éditeur.setBackground(Color.YELLOW);
      add(new JScrollPane(éditeur));
      setSize(300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new Menus(); }
}

Choix du chapitre Icônes, mnémoniques et raccourcis clavier

Il est possible de faire figurer à côté du nom d'options un petit pictogramme que nous nommons souvent une icône. En réalité, les options de menu sont très semblables aux boutons, puisque dans les deux cas, JButton et JMenuItem héritent de la classe abstraite AbstractButton. Comme les boutons, les menus peuvent donc contenir un libellé, une icône, ou les deux.

Cette possibilité n'existe que pour les options de type JMenuItem ; elle n'est donc pas disponible pour les boutons radio ou les cases à cocher.
.

Il est tout à fait possible d'utiliser le clavier plutôt que la souris pour sélectionner une option particulière. Le clavier s'utilise de deux manières avec les menus.
  1. La première s'appelle mnémonique. Un mnémonique est un caractère du libellé du menu. Lorsque nous saisissons un mnémonique de menu en maintenant la touche <ALT> enfoncée, le menu se déroule, comme si nous avions cliqué dessus avec la souris. Les options de menu possèdent également des mnémoniques. Une fois le menu ouvert, il est lors possible de sélectionner une option en tapant directement le caractère mnémonique à l'aide du clavier.
  2. Les options de menu peuvent posséder aussi des accélérateurs. Il s'agit cette fois-ci d'une combinaison de touches permettant de sélectionner une option du menu, que ce dernier soit affiché ou non. Par exemple, l'accélérateur <Ctrl+C> est souvent utilisé comme raccourci de l'option Copier du menu Edition.

Ajouter une icône à une option de menu

Vous pouvez spécifier une icône à l'aide des constructeurs suivant :

  1. JMenuItem(String libellé, Icon icône) : Construit une option de menu avec son libellé et une icône associée.
  2. JMenuItem(Icon icône) : Construit une option de menu uniquement avec son icône, sans son libellé.

Nous avons aussi la possiblité de proposer une icône à une option de menu ultérieurement, après sa création, à l'aide de la méthode setIcon() dont la classe JMenuItem hérite de la classe AbstractButton.

Pour connaître plus précisément la classe AbstractButton, repportez-vous sur l'étude suivante : Sélection et choix.
§

Placement d'icônes sur l'application précédente

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Menus extends JFrame {
   private JMenuBar barre = new JMenuBar();
   private JMenu édition = new JMenu("Edition");
   private JTextArea éditeur = new JTextArea();
   
   public Menus() {
      super("Les menus");
      setJMenuBar(barre);
      barre.add(édition);
      édition.add(new JMenuItem("Couper", new ImageIcon("couper.gif"))).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.cut();
         }
      });
      édition.add(new JMenuItem("Copier", new ImageIcon("copier.gif"))).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.copy();
         }
      });
      édition.add(new JMenuItem("Coller", new ImageIcon("coller.gif"))).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.paste();
         }
      });
      éditeur.setBackground(Color.YELLOW);
      add(new JScrollPane(éditeur));
      setSize(300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new Menus(); }
}

Le texte des options est placé par défaut à droite de l'icône. Si vous préférez que le texte soit placé à gauche, appelez la méthode setHorizontalTextPosition() qui, encore une fois, est issue de la classe AbstractButton, avec la constante suivante SwingConstants.LEFT. Dans le même ordre d'idée, il est possible de régler l'écartement qui existe entre l'icône et le libellé de l'option au moyen de la méthode setIconTextGap(int).

Les caractères mnémoniques

Les utilisateurs expérimentés préfèrent souvent sélectionner une option de menu par le biais d'un caractère mnémonique, plutôt que d'utiliser la souris, dans le cas où l'utilisateur a déjà les mains sur le clavier. Le caractère mnémonique est un caractère (unique) qui apparaît sous la forme d'un soulignement de la lettre concernée dans le nom du menu ou de l'option. Ainsi :

  1. Nous sélectionnons un menu de caractère mnémonique C, en frappant le combinaison de touche <Alt+C>.
  2. Nous sélectionnons une option de menu de caractère mnémonique C, en frappant tout simplement ce caractère (sans la touche <Alt>), alors que le menu contenant cette option est affiché.
Pour associer un caractère mnémonique à un menu ou à une option, nous utilisons pour cela la méthode setMnemonic() de la classe AbstractButton (dont dérivent, entre autres, les classe JMenu et JMenuItem) :

JMenu édition = new JMenu("Edition");
édition.setMnemonic('E');
JMenuItem couper = new JMenuItem("Couper");
couper.setMnemonic('p');
JMenuItem copier = new JMenuItem("Copier");
copier.setMnemonic('i');
JMenuItem coller = new JMenuItem("Coller");
coller.setMnemonic('l');

Nous pouvons aussi préciser le caractère mnémonique lors de la phase de construction. Attention toutefois, vous ne pouvez spécifier de caractère mnémonique que dans les constructeurs d'une option de menu (JMenuItem), et non dans le constructeur d'un menu (JMenu). Dans ce dernier cas, la seule possiblité reste l'utilisation de la méthode setMnemonic() :

JMenu édition = new JMenu("Edition");
édition.setMnemonic('E');
JMenuItem couper = new JMenuItem("Couper", 'p');
JMenuItem copier = new JMenuItem("Copier", 'i');
JMenuItem coller = new JMenuItem("Coller", 'l');

Notez que java ne vérifie pas si le caractère mnémonique mentionné appartient bien au nom du menu. Lorsque la lettre utilisé ne fait pas partie du libellé, elle n' apparaît pas dans le menu, mais son activation permet néanmoins de sélectionner l'option. On peut bien entendu douter de l'utilité de caractères mnémoniques invisibles.

Par ailleurs, si plusieurs options d'un même menu se voient attribuer le même caractères mnémonique, seul le premier sera exploitable par ce biais.
§

Parfois, vous ne souhaiterez pas souligner la première letttre de l'élément de menu correspondant au caractère mnémonique. Si vous avez, par exemple, un 'E' mnémonique pour l'option de menu "Enregistrer", vous pourriez souligner le deuxième 'e'. Vous pouvez ainsi spécifier le caractère qui sera souligné en appelant la méthode setDisplayedMnemonicIndex(int).

Remplacer les icônes par des caractères mnémoniques

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Menus extends JFrame {
   private JMenuBar barre = new JMenuBar();
   private JMenu édition = new JMenu("Edition");
   private JTextArea éditeur = new JTextArea();
   
   public Menus() {
      super("Les menus");
      setJMenuBar(barre);
      barre.add(édition);
      édition.setMnemonic('E');
      édition.add(new JMenuItem("Couper", 'p')).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.cut();
         }
      });
      édition.add(new JMenuItem("Copier", 'i')).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.copy();
         }
      });
      édition.add(new JMenuItem("Coller", 'l')).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.paste();
         }
      });
      éditeur.setBackground(Color.YELLOW);
      add(new JScrollPane(éditeur));
      setSize(300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new Menus(); }
}

Les accélérateurs

Les touches ménmoniques permettent de sélectionner une option ou un sous-menu à partir d'un menu déjà ouvert. Les combinaisons de touches ou accélérateurs sont des raccourcis clavier qui permettent de sélectionner des options sans avoir à ouvrir de menu. Par exemple, de nombreux programmes associent les combinaisons <Ctrl+X>, <Crtl+C> et <Ctrl+V> aux options Couper, Copier et Coller du menu Edition.

Un accélérateur est donc nécessairement une combinaison de touches que nous associons à une option de menu (jamais à un menu) et qui s'affiche à droite de son nom. Il suffit que l'utilisateur frappe cette combinaison de touches pour provoquer la sélection de l'option correspondante et ce, indépendamment de ce qui s'affiche dans la fenêtre à ce moment là.

Pour associer une telle combinaison de touches à une option, nous utilisons la méthode setAccelerator() de la classe JMenuItem, à laquelle nous fournissons, en argument, la combinaison de touches voulue. Pour ce faire, nous pouvons utiliser la méthode statique getKeyStroke(int codetouche, int modificateur) de la classe KeyStroke :

JMenuItem couper = new JMenuItem("Couper");
couper.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_MASK));
JMenuItem copier = new JMenuItem("Copier");
copier.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK));
JMenuItem coller = new JMenuItem("Coller");
coller.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK));
JMenuItem collageSpécial = new JMenuItem("Collage spécial...");
collageSpécial.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK));

  1. Le premier paramètre de la méthode getKeyStroke() correspond à ce que nous appelons le code de la touche virtuelle. Nous avons déjà travailler sur cette notion lors de l'étude des événements. Pour rappel, sachez simplement qu'à chaque touche du clavier (lettre, chiffre, mais aussi touche de fonction, F1, F2, Delete, End...) correspond une constante entière, proposée par la classe KeyEvent, nommée code de touche virtuelle qui commence par le préfixe VK_ .
  2. Quand au second paramètre de la méthode getKeyStroke(), il correspond aux touches modificatrices, c'est-à-dire à une ou plusieurs touches parmi Shift, Ctrl et Alt. Il utilise les constantes de la classe InputEvent, respectivement SHIFT_MASK, CRTL_MASK et ALT_MASK.

Il existe également une méthode getKeyStroke(String) de la classe KeyStroke qui possède une seul paramètre de type chaîne de caractères. Cette méthode est capable d'analyser le texte proposer et donne le raccourci clavier équivalent. L'avantage de cette méthode c'est qu'elle est beaucoup plus concise que la précédente. Par contre, il faut bien respecter la syntaxe prévue à cet effet. Il faut d'abord préciser le ou les modificateurs (shift | control | ctrl | meta | alt | altGraph) et ensuite directement le nom de la touche concernée (en majuscule) . Voici quelques exemples qui illustrent bien la syntaxe à suivre :

- "INSERT" => getKeyStroke(KeyEvent.VK_INSERT, 0);
- "control DELETE" => getKeyStroke(KeyEvent.VK_DELETE, InputEvent.CTRL_MASK);
- "alt shift X" => getKeyStroke(KeyEvent.VK_X, InputEvent.ALT_MASK | InputEvent.SHIFT_MASK);

Vous ne pouvez associer de raccourcis clavier qu'aux options de menu, et non aux menus. Les raccourcis clavier n'ouvrent pas le menu. Ils déclenchent directement l'événement d'action associé à l'option de menu.

D'un point de vue conceptuel, l'ajout d'un raccourci clavier à une option de menu est semblable à la technique d'ajout d'un raccourci à un composant Swing. Cependant, lorsque le raccourci clavier est ajouté à une option de menu, la combinaison de touches est automatiquement affichée en regard de l'option.

Editeur avec une icône, un mnémonique et raccourci clavier par option de menu

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Menus extends JFrame {
   private JMenuBar barre = new JMenuBar();
   private JMenu édition = new JMenu("Edition");
   private JTextArea éditeur = new JTextArea();
   
   public Menus() {
      super("Les menus");
      setJMenuBar(barre);
      barre.add(édition);
      édition.setMnemonic('E');
      édition.add(new Option("Couper", 'p', 'X')).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.cut();
         }
      });
      édition.add(new Option("Copier", 'i', 'C')).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.copy();
         }
      });
      édition.add(new Option("Coller", 'l', 'V')).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.paste();
         }
      });
      éditeur.setBackground(Color.YELLOW);
      add(new JScrollPane(éditeur));
      setSize(300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   private class Option extends JMenuItem {
      public Option(String intitulé, char mnémonique, char raccourci) {
         super(intitulé, new ImageIcon(intitulé.toLowerCase()+".gif"));
         setMnemonic(mnémonique);
         setAccelerator(KeyStroke.getKeyStroke("control "+raccourci));
      }
   }
   
   public static void main(String[] args) { new Menus(); }
} 

 

Choix du chapitre Bulles d'aide et composition d'options

Dans la plupart des applications professionnelle, un petit rectangle (nommé tooltip en anglais) contenant un bref texte explicatif apparaît lorsque vous laissez un instant la souris sur certains composants (boutons, menus, ...). Java vous permet d'obtenir un tel affichage pour n'importe quel composant. Il vous suffit pour cela de lui associer le texte désiré à l'aide de la méthode setToolTipText() qui existe depuis la classe JComponent.

Par ailleurs, dans les exemples précédents, un menu était formé d'une simple liste d'options. En fait, dans de nombreuses applications, une option peut à son tour faire apparaître une liste de sous-options. Pour obtenir ce résultat avec Java, il vous suffit d'utiliser dans un menu une option qui soit non plus du type JMenuItem, mais de type JMenu (comme le menu lui-même). Vous pouvez alors rattacher à ce sous-menu les options de votre choix. La démarche peut être répétée autant de fois que vous voulez.

Ajout d'une combinaison d'options avec les bulles d'aides sur l'éditeur précédent
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Menus extends JFrame {
   private JMenuBar barre = new JMenuBar();
   private JMenu édition = new JMenu("Edition");
   private JTextArea éditeur = new JTextArea();
   private JMenu option = new JMenu("Option");
   
   public Menus() {
      super("Les menus");
      setJMenuBar(barre);
      barre.add(édition);
      option.setMnemonic('O');
      option.add(new Option("Lecture seule", "Empêche la saisie de l'utilisateur"));
      option.addSeparator();
      option.add(new Option("Mode insertion", "Le texte s'insère à l'endroit du curseur"));
      option.add(new Option("Mode suppression", "Le texte saisie remplace celui existant"));
      édition.setMnemonic('E');
      édition.add(new Option("Couper", 'X', "Enlève le texte")).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.cut();
         }
      });
      édition.add(new Option("Copier", 'C', "Copie le texte")).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.copy();
         }
      });
      édition.add(new Option("Coller", 'V', "Ajoute le texte copier")).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.paste();
         }
      });
      édition.addSeparator();
      édition.add(option); <-----------------------------
      éditeur.setBackground(Color.YELLOW);
      add(new JScrollPane(éditeur));
      setSize(300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   private class Option extends JMenuItem {
      public Option(String intitulé, char raccourci, String aide) {
         this(intitulé, aide);
         setAccelerator(KeyStroke.getKeyStroke("control "+raccourci));
      }
      public Option(String intitulé, String aide) {
         super(intitulé, new ImageIcon(intitulé.toLowerCase()+".gif"));
         setToolTipText(aide);         
      }
   }
   
   public static void main(String[] args) { new Menus(); }
}

 

Choix du chapitre Les différentes sortes d'options

Jusqu'à présent, nous avons présenté uniquement les options de type JMenuItem qui sont, il est vrai, les plus usuelles. Nous avons aussi la possibilité d'utiliser dans un menu :

  1. Des options cases à cocher, c'est-à-dire des objets de type JCheckBoxMenuItem.
  2. Des options boutons radio, c'est-à-dire des objets de type JRadioButtonMenuItem.

Lorsque l'utilisateur sélectionne l'une de ces options, elle passe à l'état activé ou désactivée, selon son état initial.
§

Excepté leur aspect, vous traitez ces options de la même manière que toutes les autres. Il suffit d'utiliser tout simplement la même méthode add() de la classe JMenu. Effectivement, les classes JCheckBoxMenuItem et JRadioButtonMenuItem héritent toutes les deux de la classe JMenuItem.

Comme les boutons radio classiques, les options boutons radio fonctionnent de façon standard. Ainsi, vous devez les inclure dans un groupe (objet de type ButtonGroup) de manière à assurer l'unicité de la sélection à l'intérieur du groupe. Dans cette infrastructure, quand l'un des boutons est choisi, tous les autres sont automatiquement désactivés.

Les événements générés par ces nouvelles options sont les mêmes types que ceux générés par les boutons correspondants. Autrement dit :

  1. Chaque modification d'une option case à cocher génère à la fois un événement de type Action et un événement de type Item.
  2. Chaque action sur une option bouton radio d'un groupe provoque :
    • un événement Action et un événement Item cette option (qu'elle soit ou non sélectionnée),
    • un événement Item pour l'option précédemment sélectionnée dans le groupe, si celle-ci existe et si elle diffère de l'option sur laquelle on agit.

Nous remarquons que pour les options bouton radio, l'événement Item prend une signification différente de l'événement Action puisqu'il permet de cerner les changements d'états.

Pour en savoir plus sur les boutons radio classiques et leurs événements assosiés.
Pour en savoir plus sur les cases à cocher classiques et leurs événements associés.

Lorsque vous utilisez ce type d'option, vous n'avez généralement pas besoin d'être informé du moment exact où l'utilisateur effectue sa sélection. Vous pouvez alors simplement employer la méthode isSelected() (de la classe JRadioButtonMenuItem ou JCheckBoxMenuItem) pour tester l'état actuel d'une option (cela implique bien sûr que vous conserviez une référence sur l'option de menu dans un attribut de la classe fenêtre. Vous pouvez également utiliser la méthode setSelected() pour définir l'état d'une option.

On nottera effectivement que les options usuelles d'un menu devaient obligatoirement être traitées au moment de leur sélection. En revanche, avec les options cases à cocher ou boutons radio, nous disposons de plus de liberté. Nous pouvons, en effet, les traiter comme des options usuelles mais nous pouvons aussi nous contenter de s'intéresser à leur état à un moment donné.

Création des options case à cocher ou radio bouton

Les constructeurs disponibles sont exactement de même natures que ceux que nous avons déjà exploités pour les cases à cocher et les boutons radio. Voici donc ci-dessous la liste des constructeurs qui sont à votre disposition et qui vous offrent énormément de possibilité :

JCheckBoxMenuItem()
JCheckBoxMenuItem(Action action)
JCheckBoxMenuItem(String libellé)
JCheckBoxMenuItem(Icon icône)
JCheckBoxMenuItem(String libellé,
Icon icône)
JCheckBoxMenuItem(String libellé, boolean sélectionné)
JCheckBoxMenuItem(Icon icône, boolean sélectionné)
JCheckBoxMenuItem(String libellé,
Icon icône, boolean sélectionné)

et :

JRadioButtonMenuItem()
JRadioButtonMenuItem(Action action)
JRadioButtonMenuItem(String libellé)
JRadioButtonMenuItem(Icon icône)
JRadioButtonMenuItem(String libellé,
Icon icône)
JRadioButtonMenuItem(String libellé, boolean sélectionné)
JRadioButtonMenuItem(Icon icône, boolean sélectionné)
JRadioButtonMenuItem(String libellé,
Icon icône, boolean sélectionné)

Mise en oeuvre des différentes sortes d'option

Nous reprenons l'application précédente à laquelle nous rajoutons des fonctionnalités supplémentaires. Il est ainsi possible de bloquer l'éditeur pour soit momentanément en lecture seule. Par ailleurs, nous avons la possibilité de choisir la couleur du texte (uniquement trois valeurs proposées) :

Codage de l'application

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Menus extends JFrame {
   private JMenuBar barre = new JMenuBar();
   private JMenu édition = new JMenu("Edition");
   private JTextArea éditeur = new JTextArea();
   private JMenu option = new JMenu("Option");
   private ButtonGroup groupe = new ButtonGroup();
   private JCheckBoxMenuItem lecture = new JCheckBoxMenuItem("Lecture seule");
   
   public Menus() {
      super("Les menus");
      setJMenuBar(barre);
      barre.add(édition);
      option.setMnemonic('O');
      lecture.setToolTipText("Empêche la saisie de l'utilisateur");
      option.add(lecture).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.setEditable(!lecture.isSelected());
         }         
      });
      option.addSeparator();
      option.add(new OptionRadioBouton("Noir", Color.BLACK, true));
      option.add(new OptionRadioBouton("Rouge", Color.RED, false));
      option.add(new OptionRadioBouton("Bleu", Color.BLUE, false));
      édition.setMnemonic('E');
      édition.add(new Option("Couper", 'X', "Enlève le texte")).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.cut();
         }
      });
      édition.add(new Option("Copier", 'C', "Copie le texte")).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.copy();
         }
      });
      édition.add(new Option("Coller", 'V', "Ajoute le texte copier")).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.paste();
         }
      });
      édition.addSeparator();
      édition.add(option);
      éditeur.setBackground(Color.YELLOW);
      add(new JScrollPane(éditeur));
      setSize(300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   private class Option extends JMenuItem {
      public Option(String intitulé, char raccourci, String aide) {
         super(intitulé, new ImageIcon(intitulé.toLowerCase()+".gif"));
         setAccelerator(KeyStroke.getKeyStroke("control "+raccourci));
         setToolTipText(aide); 
      }
   }
   
   private class OptionRadioBouton extends JRadioButtonMenuItem implements ActionListener {
      private Color couleur;

      public OptionRadioBouton(String libellé, Color couleur, boolean actif) {
         super(libellé, actif);
         this.couleur = couleur;
         setToolTipText("Couleur du texte de l'éditeur");
         groupe.add(this);
         addActionListener(this);
      }

      public void actionPerformed(ActionEvent e) {
         éditeur.setForeground(couleur);
      }
   }
   
   public static void main(String[] args) { new Menus(); }
}

Choix du chapitre Les menus contextuels surgissants

Nous venons de voir comment utiliser des menus usuels, c'est-à-dire rattachés à une barre de menus et donc affichés en permanence dans la fenêtre. Java vous permet également d'utiliser ce que nous appelons des menus surgissants, c'est-à-dire des menus (sans nom) dont la liste d'options apparaît suite à une certaine action de l'utilisateur, en général un clic sur le bouton droit de la souris.

Ces menus ne sont pas attachés à une barre de menu, mais flotte à l'intérieur d'une fenêtre à l'endroit du clic de la souris et proposent juste les options nécessaires correspondantes à la situation actuelle. Dans ce cadre là, ces menus sont aussi appelés des menus contextuels.

Création de menu surgissant contextuel

Pour ce faire, il vous suffit de créer un objet de type JPopupMenu, auquel vous rattachez des objets de type JMenuItem, exactement comme vous l'auriez fait avec un objet de type JMenu.

Vous créez un menu contextuel comme n'importe quel autre menu, sachant qu'il ne possède pas de titre. Vous ajoutez ensuite les options comme à l'accoutumée :

JPopupMenu édition = new JPopupMenu();
JMenuItem couper = new JMenuItem("Couper", new ImageIcon("couper.gif"));
couper.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_MASK));
couper.setMnemonic('p');
JMenuItem copier = new JMenuItem("Copier", new ImageIcon("copier.gif"));
copier.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK));
copier.setMnemonic('i');
JMenuItem coller = new JMenuItem("Coller", new ImageIcon("coller.gif"));
coller.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK));
coller.setMnemonic('l');
édition.add(couper);
édition.add(copier);
édition.add(coller);

Nous pouvons, bien sûr, utiliser des options cases à cocher ou des options boutons radio. Toutes les techniques que nous avons mises en oeuvre dans la classe JMenu s'appliquent en totalité dans la classe JPopupMenu.

Faire apparaître un menu surgissant contextuel

Contrairement à la barre de menu standard qui apparaît toujours au sommet d'un cadre, un menu contextuel doit être explicitement affiché à l'aide de la méthode show() de la classe JPopupMenu. Vous devez alors spécifier le composant parent et la position du menu contextuel en fonction du système de coordonnées du parent :

JTextArea éditeur = new JTextArea();
édition.show(éditeur, x, y);

Le menu restera affiché jusqu'à ce que l'utilisateur sélectionne une option ou encore qu'il ferme le menu en cliquant à côté. Cette méthode show() est intéressante, toutefois, il est nécessaire d'élaborer une gestion d'événements adaptée afin de préciser les coordonnées de la souris.

Le code est habituellement écrit pour que le menu surgisse au moment où l'utilisateur clique sur un bouton particulier de la souris (sous Windows et Linux, il s'agit souvent du bouton droit) avec des options correspondantes au composant sur lequel se trouve la souris. Chaque composant, héritant de la classe JComponent, est capable de gérer automatiquement les événements de sélection et de faire apparaître un menu à l'endroit du curseur de la souris. Il suffit juste de spécifier le menu contextuel au moyen de la méthode setComponentPopupMenu().

JPopupMenu édition = new JPopupMenu();
JTextArea
éditeur = new JTextArea();
éditeur.setComponentPopupMenu(édition);

Très occasionnellement, vous pouvez placer un composant dans un autre qui dispose d'un menu contextuel. Le composant enfant peut hériter du menu contextuel du composant parent en appelant la méthode setInheritsPopuMenu(true).

Gestion événementielle

Les événements générés par les options d'un menu surgissant restent les mêmes que ceux que nous avons déjà rencontrés : Action et éventuellement Item.

A l'instar des menus usuels, les menus surgissants génèrent des événements lors de leur affichage ou de leur disparition. Cette fois, il s'agit d'événements de la catégorie PopupMenuEvent. L'écouteur correspondant est ajouté par la méthode addPopupMenuListener(). Il doit alors implémenter l'interface PopupMenuListener, comportant les méthodes popupMenuWillBecomeVisible(), popupMenuWillBecomeInvisible() et popupMenuCanceled().

Mise en oeuvre d'un menu surgissant contextuel

Reprenons l'éditeur précédent. Cette fois-ci, il ne possèdera plus la barre de menu. A la place, un menu contextuel, avec exactement les mêmes options, apparaîtra lorsque l'utilisateur cliquera avec le bouton droit de la souris dans la zone d'édition :

Codage correspondant

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Menus extends JFrame {
   private JPopupMenu édition = new JPopupMenu();
   private JTextArea éditeur = new JTextArea();
   private JMenu option = new JMenu("Option");
   private ButtonGroup groupe = new ButtonGroup();
   private JCheckBoxMenuItem lecture = new JCheckBoxMenuItem("Lecture seule");
   
   public Menus() {
      super("Les menus");
      éditeur.setComponentPopupMenu(édition);
      option.setMnemonic('O');
      lecture.setToolTipText("Empêche la saisie de l'utilisateur");
      option.add(lecture).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.setEditable(!lecture.isSelected());
         }         
      });
      option.addSeparator();
      option.add(new OptionRadioBouton("Noir", Color.BLACK, true));
      option.add(new OptionRadioBouton("Rouge", Color.RED, false));
      option.add(new OptionRadioBouton("Bleu", Color.BLUE, false));
      édition.add(new Option("Couper", 'X', "Enlève le texte")).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.cut();
         }
      });
      édition.add(new Option("Copier", 'C', "Copie le texte")).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.copy();
         }
      });
      édition.add(new Option("Coller", 'V', "Ajoute le texte copier")).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.paste();
         }
      });
      édition.addSeparator();
      édition.add(option);
      éditeur.setBackground(Color.YELLOW);
      add(new JScrollPane(éditeur));
      setSize(300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   private class Option extends JMenuItem {
      public Option(String intitulé, char raccourci, String aide) {
         super(intitulé, new ImageIcon(intitulé.toLowerCase()+".gif"));
         setAccelerator(KeyStroke.getKeyStroke("control "+raccourci));
         setToolTipText(aide); 
      }
   }
   
   private class OptionRadioBouton extends JRadioButtonMenuItem implements ActionListener {
      private Color couleur;

      public OptionRadioBouton(String libellé, Color couleur, boolean actif) {
         super(libellé, actif);
         this.couleur = couleur;
         setToolTipText("Couleur du texte de l'éditeur");
         groupe.add(this);
         addActionListener(this);
      }

      public void actionPerformed(ActionEvent e) {
         éditeur.setForeground(couleur);
      }
   }
   
   public static void main(String[] args) { new Menus(); }
}

Choix du chapitre Les menus dynamiques

Dans les exemples précédents, les menus (usuels ou surgissants) étaient créés une fois pour toute, de sorte qu'ils présentaient toujours les mêmes options et que celles-ci étaient toujours actives. En fait, Java vous permet :

  1. de désactiver et de réactiver à volonté n'importe quelle option : une option désactivée apparaît en brillance atténuée et elle est insensible à une action de l'utilisateur.
  2. de modifier le contenu d'un menu pendant l'exécution.

Activation et désactivation des options d'un menu

Il arrive qu'une option de menu spécifique ne peut être sélectionnée que dans certains contextes. Par exemple, lorsqu'un document se trouve en lecture seule, les options "Couper" et "Coller" n'ont aucunes utilité. Bien sûr, nous pourrions les supprimer du menu à l'aide de la méthode remove() de JMenu, mais les utilisateurs seraient désorientés de voir son contenu changer ainsi. Par conséquent, il vaut mieux désactiver les options pouvant donner lieu à des commandes inappropriées. Une option de menu désactivée apparaît grisée et ne peut pas être sélectionnée.

Pour activer ou désactiver une option de menu, utilisez la méthode setEnabled() de la façon suivante :

JMenuItem coller = new JMenuItem("Coller");
coller.setEnabled(true);

Il existe en fait deux façons de procéder :

  1. Chaque fois qu'une situation change, vous pouvez appeler la méthode setEnabled() sur les options de menu ou les actions concernées. Par exemple, dès qu'un document a été défini en lecture seule, vous pouvez localiser les options "Couper" et "Coller" et les désactiver. Elle peut être exécutée à n'importe quel moment. En particulier, nous pouvons l'appliquer à une option, même si le menu correspondant n'est pas affiché.
  2. Vous pouvez aussi désactiver les éléments juste avant que les options ne soient affichées. Pour cela, vous devez enregistrer un écouteur pour l'événement "menu sélectionné". Le paquetage javax.swing.event définit une interface MenuListener comprenant trois méthodes :
    • void menuSelected(MenuEvent événement)
    • void menuDeselected(MenuEvent événement)
    • void menuCanceled(MenuEvent événement)

La méthode menuSelected() est appelée avant que le menu ne soit affiché. Par conséquent, elle peut être utilisée pour activer ou désactiver une option.

Attention : Désactiver des éléments de menu juste avant l'affichage du menu est une idée brillante, mais cela ne fonctionne pas pour ceux qui disposent aussi de touches accélérateur. Le menu n'étant jamais ouvert à la pression de la touche accélérateur, l'action n'est donc jamais désactivée ; elle est toujours déclenchée par la touche accélérateur.

En fait, nous verrons dans le chapitre suivant que les objets de type AbstractAction fourniront une solution plus élégante et plus générale : il suffira d'activer l'action pour activer toutes les options associées.

Modification du contenu d'un menu

En pratique, cette seconde possiblité est rarement utilisée et ce pour d'évidentes raisons ergonomiques. En effet, il n'est guère appréciable pour l'utilisateur de voir les options d'un menu apparaître au fil de l'exécution. Il est beaucoup plus raisonnable de se limiter aux possibilités d'activation et de désactivation exposées précédemment.

A titre indicatif, sachez que vous disposez (aussi bien JMenu que pour JPopupMenu) des méthodes insert() et remove(), respectivement pour introduire de nouvelles options ou pour les supprimer par la suite.

Mise en oeuvre d'une activation et d'une désactivation d'une option de menu

Nous allons reprendre notre éditeur avec sa barre de menu. Les options "Couper" et "Coller" pourront être désactivées lorsque le document se trouvera en lecture seule.

Codage correspondant

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;

public class Menus extends JFrame implements MenuListener {
   private JMenuBar barre = new JMenuBar();
   private JMenu édition = new JMenu("Edition");
   private JTextArea éditeur = new JTextArea();
   private JMenu option = new JMenu("Option");
   private ButtonGroup groupe = new ButtonGroup();
   private JCheckBoxMenuItem lecture = new JCheckBoxMenuItem("Lecture seule");
   
   public Menus() {
      super("Les menus");
      setJMenuBar(barre);
      barre.add(édition);      
      option.setMnemonic('O');
      lecture.setToolTipText("Empêche la saisie de l'utilisateur");
      option.add(lecture).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.setEditable(!lecture.isSelected());
         }         
      });
      option.addSeparator();
      option.add(new OptionRadioBouton("Noir", Color.BLACK, true));
      option.add(new OptionRadioBouton("Rouge", Color.RED, false));
      option.add(new OptionRadioBouton("Bleu", Color.BLUE, false));
      édition.setMnemonic('E');
      édition.add(new Option("Couper", 'X', "Enlève le texte")).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {