ITEEDU

6. 菜单

为菜单处理事件看起来受益于Java 1.1版的事件模型,但Java生成菜单的方法常常麻烦并且需要一些手工编写代码。生成菜单的正确方法看起来像资源而不是一些代码。请牢牢记住编程工具会广泛地为我们处理创建的菜单,因此这可以减少我们的痛苦(只要它们会同样处理维护任务!)。另外,我们将发现菜单不支持并且将导致混乱的事件:菜单项使用ActionListeners(动作接收器),但复选框菜单项使用ItemListeners(项目接收器)。菜单对象同样能支持ActionListeners(动作接收器),但通常不那么有用。一般来说,我们会附加接收器到每个菜单项或复选框菜单项,但下面的例子(对先前例子的修改)演示了一个联合捕捉多个菜单组件到一个单独的接收器类的方法。正像我们将看到的,它或许不值得为这而激烈地争论。

//: MenuNew.java
// Menus in Java 1.1
import java.awt.*;
import java.awt.event.*;

public class MenuNew extends Frame {
  String[] flavors = { "Chocolate", "Strawberry",
    "Vanilla Fudge Swirl", "Mint Chip", 
    "Mocha Almond Fudge", "Rum Raisin", 
    "Praline Cream", "Mud Pie" };
  TextField t = new TextField("No flavor", 30);
  MenuBar mb1 = new MenuBar();
  Menu f = new Menu("File");
  Menu m = new Menu("Flavors");
  Menu s = new Menu("Safety");
  // Alternative approach:
  CheckboxMenuItem[] safety = {
    new CheckboxMenuItem("Guard"),
    new CheckboxMenuItem("Hide")
  };
  MenuItem[] file = {
    // No menu shortcut:
    new MenuItem("Open"),
    // Adding a menu shortcut is very simple:
    new MenuItem("Exit", 
      new MenuShortcut(KeyEvent.VK_E))
  };
  // A second menu bar to swap to:
  MenuBar mb2 = new MenuBar();
  Menu fooBar = new Menu("fooBar");
  MenuItem[] other = {
    new MenuItem("Foo"),
    new MenuItem("Bar"),
    new MenuItem("Baz"),
  };
  // Initialization code:
  {
    ML ml = new ML();
    CMIL cmil = new CMIL();
    safety[0].setActionCommand("Guard");
    safety[0].addItemListener(cmil);
    safety[1].setActionCommand("Hide");
    safety[1].addItemListener(cmil);
    file[0].setActionCommand("Open");
    file[0].addActionListener(ml);
    file[1].setActionCommand("Exit");
    file[1].addActionListener(ml);
    other[0].addActionListener(new FooL());
    other[1].addActionListener(new BarL());
    other[2].addActionListener(new BazL());
  }
  Button b = new Button("Swap Menus");
  public MenuNew() {
    FL fl = new FL();
    for(int i = 0; i < flavors.length; i++) {
      MenuItem mi = new MenuItem(flavors[i]);
      mi.addActionListener(fl);
      m.add(mi);
      // Add separators at intervals:
      if((i+1) % 3 == 0) 
        m.addSeparator();
    }
    for(int i = 0; i < safety.length; i++)
      s.add(safety[i]);
    f.add(s);
    for(int i = 0; i < file.length; i++)
      f.add(file[i]);
    mb1.add(f);
    mb1.add(m);
    setMenuBar(mb1);
    t.setEditable(false);
    add(t, BorderLayout.CENTER);
    // Set up the system for swapping menus:
    b.addActionListener(new BL());
    add(b, BorderLayout.NORTH);
    for(int i = 0; i < other.length; i++)
      fooBar.add(other[i]);
    mb2.add(fooBar);
  }
  class BL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      MenuBar m = getMenuBar();
      if(m == mb1) setMenuBar(mb2);
      else if (m == mb2) setMenuBar(mb1);
    }
  }
  class ML implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      MenuItem target = (MenuItem)e.getSource();
      String actionCommand = 
        target.getActionCommand();
      if(actionCommand.equals("Open")) {
        String s = t.getText();
        boolean chosen = false;
        for(int i = 0; i < flavors.length; i++)
          if(s.equals(flavors[i])) chosen = true;
        if(!chosen)
          t.setText("Choose a flavor first!");
        else
          t.setText("Opening "+ s +". Mmm, mm!");
      } else if(actionCommand.equals("Exit")) {
        dispatchEvent(
          new WindowEvent(MenuNew.this, 
            WindowEvent.WINDOW_CLOSING));
      }
    }
  }
  class FL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      MenuItem target = (MenuItem)e.getSource();
      t.setText(target.getLabel());
    }
  }
  // Alternatively, you can create a different
  // class for each different MenuItem. Then you
  // Don't have to figure out which one it is:
  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) {
      CheckboxMenuItem target = 
        (CheckboxMenuItem)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 static void main(String[] args) {
    MenuNew f = new MenuNew();
    f.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    f.setSize(300,200);
    f.setVisible(true);
  }
} ///:~
在我们开始初始化节(由注解“Initialization code:”后的右大括号指明)的前面部分的代码同先前(Java 1.0版)版本相同。这里我们可以注意到项目接收器和动作接收器被附加在不同的菜单组件上。

Java 1.1支持“菜单快捷键”,因此我们可以选择一个菜单项目利用键盘替代鼠标。这十分的简单;我们只要使用过载菜单项构建器设置第二个自变量为一个MenuShortcut(菜单快捷键事件)对象即可。菜单快捷键构建器设置重要的方法,当它按下时不可思议地显示在菜单项上。上面的例子增加了Control-E到“Exit”菜单项中。

我们同样会注意setActionCommand()的使用。这看似一点陌生因为在各种情况下“action command”完全同菜单组件上的标签一样。为什么不正好使用标签代替可选择的字符串呢?这个难题是国际化的。如果我们重新用其它语言写这个程序,我们只需要改变菜单中的标签,并不审查代码中可能包含新错误的所有逻辑。因此使这对检查文字字符串联合菜单组件的代码而言变得简单容易,当菜单标签能改变时“动作指令”可以不作任何的改变。所有这些代码同“动作指令”一同工作,因此它不会受改变菜单标签的影响。注意在这个程序中,不是所有的菜单组件都被它们的动作指令所审查,因此这些组件都没有它们的动作指令集。

大多数的构建器同前面的一样,将几个调用的异常增加到接收器中。大量的工作发生在接收器里。在前面例子的BL中,菜单交替发生。在ML中,“寻找ring”方法被作为动作事件(ActionEvent)的资源并对它进行造型送入菜单项,然后得到动作指令字符串,再通过它去贯穿串联组,当然条件是对它进行声明。这些大多数同前面的一样,但请注意如果“Exit”被选中,通过进入封装类对象的句柄(MenuNew.this)并创建一个WINDOW_CLOSING事件,一个新的窗口事件就被创建了。新的事件被分配到封装类对象的dispatchEvent()方法,然后结束调用windowsClosing()内部帧的窗口接收器(这个接收器作为一个内部类被创建在main()里),似乎这是“正常”产生消息的方法。通过这种机制,我们可以在任何情况下迅速处理任何的信息,因此,它非常的强大。

FL接收器是很简单尽管它能处理特殊菜单的所有不同的特色。如果我们的逻辑十分的简单明了,这种方法对我们就很有用处,但通常,我们使用这种方法时需要与FooL,BarL和BazL一道使用,它们每个都附加到一个单独的菜单组件上,因此必然无需测试逻辑,并且使我们正确地辨识出谁调用了接收器。这种方法产生了大量的类,内部代码趋向于变得小巧和处理起来简单、安全。