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) {
            é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);
      édition.addMenuListener(this);
      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(); }

   public void menuSelected(MenuEvent e) {
      édition.getItem(0).setEnabled(!lecture.isSelected());
      édition.getItem(2).setEnabled(!lecture.isSelected());
   }

   public void menuDeselected(MenuEvent e) {   }

   public void menuCanceled(MenuEvent e) {   }
}
Les classes JMenu ainsi que JPopupMenu disposent de méthodes intéressantes pour gérer et retrouver les options de menu :
  1. JMenuItem getItem(int position) : retourne l'option de menu suivant sa position dans le menu. La première position est 0.
  2. int getItemCount() : renvoie le nombre d'options de menu, séparateurs compris.
  3. Component getMenuComponent(int position) : renvoie le composant de la position spécifiée.
  4. int getMenuComponentCount() : renvoie le nombre de composant associé au menu.
  5. Component[] getMenuComponents() : retourne un tableau de composants de menu.
  6. MenuElement[] getSubElements() : retourne l'ensemble des options de sous-menus.

Choix du chapitre Les actions

Contexte : La classe JToolBar possède aussi une méthode spéciale add() pour ajouter un objet de type Action au lieu d'un composant. Un JButton approprié est alors automatiquement créé, qui suit l'état activé de l'action pour que si l'action est désactivée, le bouton le soit aussi. La classe JMenu a la même possibilité d'accepter des objets Action pour les enfants. L'intérêt c'est que les actions fréquemment utilisées apparaissent souvent à la fois dans les composants JMenu et JToolBar, mais ces actions sont décrites une seule fois pour ces deux types de conteneurs.

Il existe souvent plusieurs manières d'activer une même commande. L'utilisateur peut effectivement choisir la fonction désirée, par exemple l'ouverture d'un fichier, soit par l'intermédiaire d'un menu, soit à partir d'un raccourci clavier ou soit en cliquant sur un bouton dans une barre d'outils.

Dans ce cadre là, si nous souhaitons réaliser des logiciels de qualité, il est préférable que le traitement de la commande (par exemple l'ouverture du fichier) ne soit réalisée qu'en un seul point du code. Nous pouvons déjà tendre vers cet idéal en faisant en sorte que les écouteurs appropriés se contentent d'appeler une méthode unique responsable de l'action en question. En général, cependant, cela ne sera pas suffisant et il faudra s'acheminer vers la création d'objets abstraits encapsulant toutes les informations nécessaires à la réalisation d'une action (par exemple la boîte de dialogue à faire apparaître, le nom de la méthode à solliciter, etc.).

C'est dans ce contexte que Java offre un outil très puissant. Il s'agit de la classe AbstractAction qui comporte déjà les services de base que nous pouvons attendre d'une classe destinée à représenter une telle action. Bien entendu, nous pourrons la compléter à volonté par héritage.

Retour sur les méthodes classiques du paquetage AWT

Avant de prendre connaissance de cette classe, revoyons le traitement de la gestion des événements de façon classique. Il est en effet très facile avec AWT de lier tous les événements au même écouteur. Par exemple, supposons que écouteurOuvrir soit un écouteur d'action dont la méthode actionPerformed() permet l'ouverture d'un fichier texte. Vous pouvez attacher le même objet comme écouteur de plusieurs sources d'événements :

  1. Un bouton de la barre d'outils libellé "Ouvrir" ;
  2. Une option du menu principal baptisée "Ouvrir" ;
  3. Une option du menu surgissant baptisée également "Ouvrir" ;
  4. Une combinaison de touches, appelée accélérateur : Crtl+O.

Puis chaque commande d'ouverture de fichier est gérée d'une seule manière, quelle que soit l'action qui l'a déclenchée : un clic sur un bouton, un choix sur un menu ou une frappe au clavier.

L'interface Action du paquetage Swing

Toutefois, le paquetage Swing fournit un mécanisme beaucoup plus pratique pour encapsuler des commandes et les attacher à plusieurs sources d'événement : il s'agit de l'interface Action. Une action est un objet qui encapsule :

  1. Une desciption de la commande (comme une chaîne de texte et une icône facultative) ;
  2. Les paramètres nécessaires à l'exécution de la commande (dans notre cas, l'ouverture d'un fichier).

L'interface Action possède les méthodes suivantes :

interface Action {
   void actionPerformed(ActionEvent événement);
   void setEnabled(boolean disponible);
   boolean isEnabled();
   void putValue(String clé, Object valeur);
   Object getValue(String clé);
   void addPropertyChangeListener(PropertyChangeListener écouteur);
   void removePropertyChangeListener(PorpertyChangeListener écouteur);
}

La première méthode actionPerformed() est la méthode habituelle de l'interface ActionListener : en fait, l'interface Action est dérivée de ActionListener. Par conséquent, il est possible d'utiliser un objet Action partout où un objet ActionListener est attendu.

Les deux méthodes suivantes setEnabled() et isEnabled() permettent d'activer ou de désactiver l'action, et de vérifier si elle est activée. Lorsqu'une action attachée à un menu ou à une barre d'outils est désactivée, l'option correspondante apparaît en grisé.


Les méthodes putValue() et getValue() sont employées pour stocker et récupérer un couple arbitraire clé/valeur dans l'objet Action. Il existe des chaînes prédéfinies comme Action.NAME et Action.SMALL_ICON pour faciliter le stockage des noms et des icônes dans un objet Action :
action.putValue(Action.NAME, "Ouvrir");
action.putValue(Action.SMALL_ICON, new ImageIcon("ouvrir.gif"));
Nom de l'action prédéfini Valeur de l'action
NAME Nom de l'action ; affiché sur les boutons et les options de menu.
SMALL_ICON Emplacement de stockage d'une petite icône ; pour affichage sur un bouton, une option de menu ou dans la barre d'outils.
SHORT_DESCRIPTION Courte description de l'icône ; pour affichage dans une bulle d'aide.
LONG_DESCRIPTION Description détaillée de l'icône ; pour utilisation potentielle dans l'aide en ligne.
MNEMONIC_KEY Abreviation mnémonique ; pour affichage dans une option de menu.
ACCELERATOR_KEY Emplacement pour le stockage d'un raccourci clavier.
ACTION_COMMAND_KEY Utilisée dans la méthode registerKeyboardAction(), maintenant obsolète.
DEFAULT Propiété fourre-tout.

Si l'objet Action est ajouté à un menu ou à une barre d'outils, le nom et l'icône sont automatiquement récupérés et affichés dans l'option du menu ou sur le bouton de la barre d'outils. La valeur de SHORT_DESCRIPTION s'affiche dans une bulle d'aide.

Les deux dernières méthodes addPropertyChangeListener() et removePropertyChangeListener() de l'interface Action permettent aux autres objets - en particulier les menus ou les barres d'outils qui ont déclenché l'action - de recevoir une notification lorsque les propriétés de l'objet Action sont modifiées. Par exemple, si un menu est recencé en tant qu'écouteur de changement de propriétés d'un objet Action, et que l'objet Action soit ensuite désactivé, le menu est prévenu et peut alors affiché en grisé la rubrique correspondant à l'action. Les écouteurs de changement de propriété sont une construction générique intégré au modèle de composant des JavaBeans.

L'objet AbstractAction implémente l'interface Action

Notez que Action est une interface et non une classe. Toute classe implémentant cette interface est donc tenue d'implémenter les sept méthodes citées. Heureusement, un bon samaritain a implémenté toutes ces méthodes - sauf actionPerformed() - dans une classe nommée AbstractAction, qui se charge de stocker les couples clé/valeur et de gérer les écouteurs de changement de propriété. Il ne vous reste plus qu'à étendre AbstractAction et à écrire la méthode actionPerformed(). Cette classe propose trois constructeurs :

AbstractAction() // création d'une action sans spécification d'intitulé, ni d'icône associée
AbstractAction(String libellé) // création d'une action en spécifiant son intitulé sans icône associée
AbstractAction(String libellé, Icon icône) // création d'une action avec un libellé et son icône associée.

Ajouter des actions à des composants de choix, à un menu ou à une barre d'outils

Ces actions sont extrêmement intéressantes puisqu'elles peuvent être insérer dans n'importe quel composant de choix ou de menu.

  1. Tous les boutons, JButton, JToggleButton, JCheckBox et JRadioButton comportent systématiquement un constructeur qui prend en compte l'action désirée. Ainsi toutes les caractéristiques de l'action sont automatiquement répercutées sur le type de bouton choisi : le libellé, l'îcone, le mnémonique, l'accélérateur, la bulle d'aide et la gestion de l'événement de type Action. Voici recensés les constructeurs de chaque type de bouton :

    JButton(Action action);
    JToggleButton(Action action);
    JCheckBox(Action action); // JCheckBox hérite de JToggleButton
    JRadioButton(Action action); // JRadioButton hérite de JToggleButton

  2. La classe JMenuItem possède également un constructeur qui est capable de prendre en compte l'action désirée avec toutes les caractéristiques spécifiées dans l'action. Par voie de conséquence, vues que les classes JMenu, JCheckBoxMenuItem et JRadioButtonMenuItem héritent toutes de la classe JMenuItem, elles proposent donc également le même type de constructeur :

    JMenuItem(Action action);
    JMenu(Action action); // JMenu hérite de JMenuItem
    JCheckBoxMenuItem(Action action); // JCheckBoxMenuItem hérite deJMenuItem
    JRadioButtonMenuItem(Action action); // JRadioButtonMenuItem hérite deJMenuItem

  3. Par ailleurs, la classe JMenu propose une méthode add() qui permet de construire automatiquement une option de menu (JMenuItem, JMenu, JCheckBoxMenuItem et JRadioButtonMenuItem) à partir d'une action spécifiée en argument. Cette méthode est vraiment intéressante, elle permet d'avoir un code beaucoup plus concis.

    JMenuItem add(Action action);

  4. Dans le même ordre d'idée, la classe JToolBar, qui représente la barre d'outils, possède également la méthode spéciale add() pour ajouter un objet de type Action au lieu d'un composant. Un JButton approprié est alors automatiquement créé, qui suit l'état activé de l'action pour que si l'action est désactivée, le bouton le soit aussi. Comme il s'agit d'une barre d'outils, cette fois-ci le libellé n'est pas affiché par défaut, seule l'icône représentative apparaît dans le bouton.

    JButton add(Action action);

Grâce à ces différentes techniques, il est très facile de mettre en oeuvre un ensemble de sélections dont chacune est représentée par différents éléments graphiques et dont chacune est recensée en un seul endroit commun qui est l'action.

Mise en oeuvre

Afin de bien illustrer l'intérêt de cette classe AbstractAction, je vous propose de mettre en oeuvre un simple éditeur de texte. Cet éditeur comporte une barre de menu, une barre d'outils et un menu contextuel qui proposent les mêmes actions, c'est-à-dire l'ouverture et la sauvegarde d'un texte ainsi que les rubriques d'éditions classiques, comme le couper, le copier et le coller. Chacun des éléments possède un libellé, une icône, une bulle d'aide avec, en plus, une activation possible par raccourci clavier. Il est possible de placer temporairement l'éditeur en lecture seule. Suivant l'état du document, certaines options deviennent grisées.

Codage correspondant
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;

public class Menus extends JFrame  {
   private JMenuBar menu = new JMenuBar();
   private JToolBar barre = new JToolBar();
   private JMenu fichier = new JMenu("Fichier");
   private Actions actionNouveau = new Actions("Nouveau", 'N', "Tout effacer dans la zone d'édition");
   private Actions actionOuvrir = new Actions("Ouvrir", 'O', "Ouvrir le fichier texte");
   private Actions actionEnregistrer = new Actions("Enregistrer", 'W', "Sauvegarder le texte");
   private Actions actionCouper = new Actions("Couper", 'X', "Enlever le texte sélectionné");
   private Actions actionCopier = new Actions("Copier", 'C', "Copier le texte sélectionné");
   private Actions actionColler = new Actions("Coller", 'V', "Coller le texte à l'endroit du curseur");
   private JMenu édition = new JMenu("Edition");
   private JTextPane éditeur = new JTextPane();
   private JCheckBoxMenuItem lecture = new JCheckBoxMenuItem("Lecture seule");
   private JPopupMenu surgissant = new JPopupMenu();
   
   public Menus() {
      super("Les menus");
      setJMenuBar(menu);
      add(barre, BorderLayout.NORTH);     
      menu.add(fichier);
      fichier.setMnemonic('F');
      fichier.add(actionNouveau);
      fichier.add(actionOuvrir);
      fichier.add(actionEnregistrer);
      activation(false);      
      menu.add(édition);            
      édition.setMnemonic('E');
      édition.add(actionCouper);
      édition.add(actionCopier);
      édition.add(actionColler);
      édition.addSeparator();
      édition.add(lecture).addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            éditeur.setEditable(!lecture.isSelected());
            actionCouper.setEnabled(!lecture.isSelected());
            actionColler.setEnabled(!lecture.isSelected());            
         }         
      });
      lecture.setToolTipText("Empêche la saisie de l'utilisateur");
      barre.add(actionNouveau);
      barre.add(actionOuvrir);
      barre.add(actionEnregistrer);
      barre.addSeparator();
      barre.add(actionCouper);
      barre.add(actionCopier);
      barre.add(actionColler);
      surgissant.add(actionEnregistrer);
      surgissant.addSeparator();
      surgissant.add(actionCopier);
      surgissant.add(actionColler);
      éditeur.setComponentPopupMenu(surgissant);
      éditeur.setBackground(Color.YELLOW);
      éditeur.addKeyListener(new KeyAdapter() {
         @Override
         public void keyReleased(KeyEvent ev) {
            activation(éditeur.getDocument().getLength()!=0);
         }
      });
      add(new JScrollPane(éditeur));
      setSize(400, 300);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }
   
   private void activation(boolean actif) {
      actionEnregistrer.setEnabled(actif);  
      actionCouper.setEnabled(actif);
      actionCopier.setEnabled(actif);
   }
   
   public static void main(String[] args) { new Menus(); }

   private class Actions extends AbstractAction {
      private String méthode;
       
      public Actions(String libellé, char raccourci, String description) {
         super(libellé, new ImageIcon(libellé.toLowerCase()+".gif"));
         putValue(SHORT_DESCRIPTION, description);
//         putValue(MNEMONIC_KEY, (int)libellé.charAt(0));
         putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control "+raccourci));
         méthode = libellé.toLowerCase();
      }
      
      public void actionPerformed(ActionEvent e) {
         try {
            this.getClass().getDeclaredMethod(méthode).invoke(this);
         } 
         catch (Exception ex) { Menus.this.setTitle("Problème");}
      }  
       
      private void nouveau() {
         Menus.this.setTitle("Nouveau document");
         éditeur.setText("");
         activation(false);
      }
       
      private void ouvrir() throws IOException {
         JFileChooser boîte = new JFileChooser();
         if (boîte.showOpenDialog(Menus.this)==JFileChooser.APPROVE_OPTION) {
            File fichier = boîte.getSelectedFile();
            Menus.this.setTitle(fichier.getName());
            éditeur.read(new FileInputStream(fichier), null);
         }         
      }
       
      private void enregistrer() throws IOException {
         JFileChooser boîte = new JFileChooser();
         if (boîte.showSaveDialog(Menus.this)==JFileChooser.APPROVE_OPTION) {
            File fichier = boîte.getSelectedFile();
            Menus.this.setTitle(fichier.getName());
            if (!fichier.exists()) fichier.createNewFile();
            PrintWriter écriture = new PrintWriter(fichier);
            écriture.println(éditeur.getText());
            écriture.close();
         }           
      }
      
      private void couper() { éditeur.cut(); }
      private void copier()  { éditeur.copy(); }
      private void coller()   { éditeur.paste(); }
   }
}