IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java

  Chapitre 13 - Création de fenêtres & d'Applets

pages : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 

On remarquera que showOptionDialog() et showInputDialog() retournent des objets contenant la valeur entrée par l'utilisateur.

Menus

Chaque composant capable de contenir un menu, y compris JApplet, JFrame, JDialog et leurs descendants, possède une méthode setJMenuBar() qui prend comme paramètre un JMenuBar (il ne peut y avoir qu'un seul JMenuBar sur un composant donné). On ajoute les JMenus au JMenuBar, et les JMenuItems aux JMenus. On peut attacher un ActionListener à chaque JMenuItem, qui sera lancé quand l'élément de menu est sélectionné.

Contrairement à un système qui utilise des ressources, en Java et Swing il faut assembler à la main tous les menus dans le code source. Voici un exemple très simple de menu :

//: c13:SimpleMenus.java
// <applet code=SimpleMenus
// width=200 height=75> </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class SimpleMenus extends JApplet {
  JTextField t = new JTextField(15);
  ActionListener al = new ActionListener() {
    public void actionPerformed(ActionEvent e){
      t.setText(
        ((JMenuItem)e.getSource()).getText());
    }
  };
  JMenu[] menus = { new JMenu("Winken"),
    new JMenu("Blinken"), new JMenu("Nod") };
  JMenuItem[] items = {
    new JMenuItem("Fee"), new JMenuItem("Fi"),
    new JMenuItem("Fo"),  new JMenuItem("Zip"),
    new JMenuItem("Zap"), new JMenuItem("Zot"),
    new JMenuItem("Olly"), new JMenuItem("Oxen"),
    new JMenuItem("Free") };
  public void init() {
    for(int i = 0; i       items[i].addActionListener(al);
      menus[i%3].add(items[i]);
    }
    JMenuBar mb = new JMenuBar();
    for(int i = 0; i       mb.add(menus[i]);
    setJMenuBar(mb);
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(t);
  }
  public static void main(String[] args) {
    Console.run(new SimpleMenus(), 200, 75);
  }
} ///:~

L'utilisation de l'opérateur modulo [modulus] dans i%3 distribue les éléments de menus parmi les trois JMenus. Chaque JMenuItem doit avoir un ActionListener attaché ; ici le même ActionListener est utilisé partout mais on en aura besoin normalement d'un pour chaque JMenuItem.

JMenuItem hérite d'AbstractButton, et il a donc certains comportements des boutons. En lui-même, il fournit un élément qui peut être placé dans un menu déroulant. Il y a aussi trois types qui héritent de JMenuItem : JMenu pour contenir d'autres JMenuItems (pour réaliser des menus en cascade), JCheckBoxMenuItem, qui fournit un marquage pour indiquer si l'élément de menu est sélectionné ou pas, et JRadioButtonMenuItem, qui contient un bouton radio.

En tant qu'exemple plus sophistiqué, voici à nouveau les parfums de crèmes glacées, utilisés pour créer des menus. Cet exemple montre également des menus en cascade, des mnémoniques clavier, des JCheckBoxMenuItems, et la façon de changer ces menus dynamiquement :

//: c13:Menus.java
// Sous-menus, éléments de menu avec boîtes à cocher, permutations de  menus,
// mnémoniques (raccourcis) et commandes d'actions.
// <applet code=Menus width=300
// height=100> </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Menus extends JApplet {
  String[] flavors = { "Chocolate", "Strawberry",
    "Vanilla Fudge Swirl", "Mint Chip",
    "Mocha Almond Fudge", "Rum Raisin",
    "Praline Cream", "Mud Pie" };
  JTextField t = new JTextField("No flavor", 30);
  JMenuBar mb1 = new JMenuBar();
  JMenu
    f = new JMenu("File"),
    m = new JMenu("Flavors"),
    s = new JMenu("Safety");
  // Approche alternative :
  JCheckBoxMenuItem[] safety = {
    new JCheckBoxMenuItem("Guard"),
    new JCheckBoxMenuItem("Hide")
  };
  JMenuItem[] file = {
    new JMenuItem("Open"),
  };
  // Une seconde barre de menu pour échanger :
  JMenuBar mb2 = new JMenuBar();
  JMenu fooBar = new JMenu("fooBar");
  JMenuItem[] other = {
    // Ajouter un raccourci de menu (mnémonique) est très
    // simple, mais seuls les JMenuItems peuvent les avoir
    // dans leurs constructeurs:
    new JMenuItem("Foo", KeyEvent.VK_F),
    new JMenuItem("Bar", KeyEvent.VK_A),
    // Pas de raccourci :
    new JMenuItem("Baz"),
  };
  JButton b = new JButton("Swap Menus");
  class BL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      JMenuBar m = getJMenuBar();
      setJMenuBar(m == mb1 ? mb2 : mb1);
      validate(); // Rafraîchissement de la fenêtre
    }
  }
  class ML implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      JMenuItem target = (JMenuItem)e.getSource();
      String actionCommand =
        target.getActionCommand();
      if(actionCommand.equals("Open")) {
        String s = t.getText();
        boolean chosen = false;
        for(int i = 0; i           if(s.equals(flavors[i])) chosen = true;
        if(!chosen)
          t.setText("Choose a flavor first!");
        else
          t.setText("Opening "+ s +". Mmm, mm!");
      }
    }
  }
  class FL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      JMenuItem target = (JMenuItem)e.getSource();
      t.setText(target.getText());
    }
  }
  // Alternativement, on peut créer une classe
// différente pour chaque JMenuItem. Ensuite
// il n'est plus nécessaire de rechercher de laquelle il s'agit :
  class FooL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t.setText("Foo selected");
    }
  }
  class BarL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t.setText("Bar selected");
    }
  }
  class BazL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t.setText("Baz selected");
    }
  }
  class CMIL implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      JCheckBoxMenuItem target =
        (JCheckBoxMenuItem)e.getSource();
      String actionCommand =
        target.getActionCommand();
      if(actionCommand.equals("Guard"))
        t.setText("Guard the Ice Cream! " +
          "Guarding is " + target.getState());
      else if(actionCommand.equals("Hide"))
        t.setText("Hide the Ice Cream! " +
          "Is it cold? " + target.getState());
    }
  }
  public void init() {
    ML ml = new ML();
    CMIL cmil = new CMIL();
    safety[0].setActionCommand("Guard");
    safety[0].setMnemonic(KeyEvent.VK_G);
    safety[0].addItemListener(cmil);
    safety[1].setActionCommand("Hide");
    safety[0].setMnemonic(KeyEvent.VK_H);
    safety[1].addItemListener(cmil);
    other[0].addActionListener(new FooL());
    other[1].addActionListener(new BarL());
    other[2].addActionListener(new BazL());
    FL fl = new FL();
    for(int i = 0; i       JMenuItem mi = new JMenuItem(flavors[i]);
      mi.addActionListener(fl);
      m.add(mi);
      // Ajout de séparateurs par  intervalles:
      if((i+1) % 3 == 0)
        m.addSeparator();
    }
    for(int i = 0; i       s.add(safety[i]);
    s.setMnemonic(KeyEvent.VK_A);
    f.add(s);
    f.setMnemonic(KeyEvent.VK_F);
    for(int i = 0; i       file[i].addActionListener(fl);
      f.add(file[i]);
    }
    mb1.add(f);
    mb1.add(m);
    setJMenuBar(mb1);
    t.setEditable(false);
    Container cp = getContentPane();
    cp.add(t, BorderLayout.CENTER);
    // Installation du système d'échange de menus:
    b.addActionListener(new BL());
    b.setMnemonic(KeyEvent.VK_S);
    cp.add(b, BorderLayout.NORTH);
    for(int i = 0; i       fooBar.add(other[i]);
    fooBar.setMnemonic(KeyEvent.VK_B);
    mb2.add(fooBar);
  }
  public static void main(String[] args) {
    Console.run(new Menus(), 300, 100);
  }
} ///:~

Dans ce programme j'ai placé les éléments de menus dans des tableaux et ensuite j'ai parcouru chaque tableau en appelant add() pour chaque JMenuItem. Ceci rend l'ajout ou la suppression d'un élément de menu un peu moins fastidieux.

Ce programme crée deux JMenuBars pour démontrer que les barres de menu peuvent être échangées dynamiquement à l'exécution du programme. On peut voir qu'un JMenuBar est composé de JMenus, et que chaque JMenu est composé de JMenuItems, JCheckBoxMenuItems, ou même d'autres JMenus (qui produisent des sous-menus). Une fois construit un JMenuBar, il peut être installé dans le programme courant avec la méthode setJMenuBar(). Remarquons que lorsque le bouton est cliqué, il regarde quel menu est installé en appelant getJMenuBar(), et à ce moment il le remplace par l'autre barre de menu.

Lorsqu'on teste "Open", il faut remarquer que l'orthographe et les majuscules/minuscules sont cruciaux, et que Java ne signale pas d'erreur s'il n'y a pas correspondance avec "Open". Ce genre de comparaison de chaînes est une source d'erreurs de programmation.

Le cochage et le décochage des éléments de menus est pris en compte automatiquement. Le code gérant les JCheckBoxMenuItems montre deux façons différentes de déterminer ce qui a été coché : la correspondance des chaînes (qui, comme mentionné ci-dessus, n'est pas une approche très sûre bien qu'on la rencontre) et la correspondance de l'objet cible de l'événement. Il montre aussi que la méthode getState() peut être utilisée pour connaître son état. On peut également changer l'état d'un JCheckBoxMenuItem à l'aide de setState().

Les événements des menus sont un peu inconsistants et peuvent prêter à confusion : les JMenuItems utilisent des ActionListeners, mais les JCheckboxMenuItems utilisent des ItemListeners. Les objets JMenu peuvent aussi supporter des ActionListeners, mais ce n'est généralement pas très utile. En général, on attache des listeners à chaque JMenuItem, JCheckBoxMenuItem, ou JRadioButtonMenuItem, mais l'exemple montre des ItemListeners et des ActionListeners attachés aux divers composants de menus.

Swing supporte les mnémoniques, ou raccourcis clavier, de sorte qu'on peut sélectionner tout ce qui est dérivé de AbstractButton (bouton, menu, élément, et cetera) en utilisant le clavier à la place de la souris. C'est assez simple : pour le JMenuItem on peut utiliser le constructeur surchargé qui prend en deuxième argument l'identificateur de la touche. Cependant, la plupart des AbstractButtons n'ont pas ce constructeur; une manière plus générale de résoudre ce problème est d'utiliser la méthode setMnemonic(). L'exemple ci-dessus ajoute une mnémonique au bouton et à certains des éléments de menus : les indicateurs de raccourcis apparaissent automatiquement sur les composants.

On peut aussi voir l'utilisation de setActionCommand(). Ceci paraît un peu bizarre car dans chaque cas la commande d'action [action command] est exactement la même que le label sur le composant du menu. Pourquoi ne pas utiliser simplement le label plutôt que cette chaîne de remplacement ? Le problème est l'internationalisation. Si on réoriente ce programme vers une autre langue, on désire changer uniquement le label du menu, et pas le code (ce qui introduirait à coup sûr d'autres erreurs). Donc pour faciliter ceci pour les codes qui testent la chaîne associée à un composant de menu, la commande d'action peut être invariante tandis que le label du menu peut changer. Tout le code fonctionne avec la commande d'action, de sorte qu'il n'est pas touché par les modifications des labels des menus. Remarquons que dans ce programme on ne recherche pas des commandes d'actions pour tous les composants de menus, de sorte que ceux qui ne sont pas examinés n'ont pas de commande d'action positionnée.

La plus grosse partie du travail se trouve dans les listeners. BL effectue l'échange des JMenuBars. Dans ML, l'approche du "qui a sonné ?" est utilisée en utilisant la source de l'ActionEvent et en l'émettant vers un JMenuItem, en faisant passer la chaîne de la commande d'action à travers une instruction if en cascade.

Le listener FL est simple, bien qu'il gère les différents parfums du menu parfums. Cette approche est utile si la logique est simple, mais en général, on utilisera l'approche de FooL, BarL et BazL, dans lesquels ils sont chacun attaché à un seul composant de menu de sorte qu'il n'est pas nécessaire d'avoir une logique de détection supplémentaire, et on sait exactement qui a appelé le listener. Même avec la profusion de classes générées de cette façon, le code interne tend à être plus petit et le traitement est plus fiable.

On peut voir que le code d'un menu devient rapidement long et désordonné. C'est un autre cas où l'utilisation d'un GUI builder est la solution appropriée. Un bon outil gérera également la maintenance des menus.

Menus pop-up

La façon la plus directe d'implémenter un JPopupMenu est de créer une classe interne qui étend MouseAdapter, puis d'ajouter un objet de cette classe interne à chaque composant pour lequel on veut créer le pop-up :

//: c13:Popup.java
// Création de menus popup avec Swing.
// <applet code=Popup
//  width=300 height=200></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Popup extends JApplet {
  JPopupMenu popup = new JPopupMenu();
  JTextField t = new JTextField(10);
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(t);
    ActionListener al = new ActionListener() {
      public void actionPerformed(ActionEvent e){
        t.setText(
          ((JMenuItem)e.getSource()).getText());
      }
    };
    JMenuItem m = new JMenuItem("Hither");
    m.addActionListener(al);
    popup.add(m);
    m = new JMenuItem("Yon");
    m.addActionListener(al);
    popup.add(m);
    m = new JMenuItem("Afar");
    m.addActionListener(al);
    popup.add(m);
    popup.addSeparator();
    m = new JMenuItem("Stay Here");
    m.addActionListener(al);
    popup.add(m);
    PopupListener pl = new PopupListener();
    addMouseListener(pl);
    t.addMouseListener(pl);
  }
  class PopupListener extends MouseAdapter {
    public void mousePressed(MouseEvent e) {
      maybeShowPopup(e);
    }
    public void mouseReleased(MouseEvent e) {
      maybeShowPopup(e);
    }
    private void maybeShowPopup(MouseEvent e) {
      if(e.isPopupTrigger()) {
        popup.show(
          e.getComponent(), e.getX(), e.getY());
      }
    }
  }
  public static void main(String[] args) {
    Console.run(new Popup(), 300, 200);
  }
} ///:~

Le même ActionListener est ajouté à chaque JMenuItem de façon à prendre le texte du label du menu et l'insérer dans le JTextField.

Dessiner

Dans un bon outil de GUI, dessiner devrait être assez facile, et ça l'est dans la bibliothèque Swing. Le problème de tout exemple de dessin est que les calculs qui déterminent où vont les éléments sont souvent beaucoup plus compliqués que les appels aux sous-programmes de dessin, et que ces calculs sont souvent mélangés aux appels de dessin, de sorte que l'interface semble plus compliquée qu'elle ne l'est.

Pour simplifier, considérons le problème de la représentation de données sur l'écran. Ici, les données sont fournies par la méthode intrinsèque Math.sin() qui est la fonction mathématique sinus. Pour rendre les choses un peu plus intéressantes, et pour mieux montrer combien il est facile d'utiliser les composants Swing, un curseur sera placé en bas du formulaire pour contrôler dynamiquement le nombre de cycles du sinus affiché. De plus, si on redimensionne la fenêtre, on verra le sinus s'adapter de lui-même à la nouvelle taille de la fenêtre.

Ce livre a été écrit par Bruce Eckel ( télécharger la version anglaise : Thinking in java )
Ce chapitre a été traduit par P. Boite ( groupe de traduction )
télécharger la version francaise (PDF) | Commandez le livre en version anglaise (amazon) | télécharger la version anglaise
pages : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java