每个组件都能够持有一个菜单,包括 JApplet, JFrame, JDialog以及它们的子类。它们的
setJMenuBar( )方法接受一个JMenuBar对象(每个组件只能持有一个JMenuBar对象)
作为参数。你先把JMenu对象添加到JmenuBar中,然后把JmenuItem添加到Jmenu中。
每个JmenuItem都能有一个相关联的ActionListener,用来捕获菜单项被选中时触发的事
件。
与使用“资源文件”的系统不同,在 Java 和 Swing 中你必须在源代码中构造所有的菜单。
下面是个非常简单的菜单例子:
//: c14: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 {
private JTextField t = new JTextField(15);
private ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent e) {
t.setText(((JMenuItem)e.getSource()).getText());
}
};
private JMenu[] menus = {
new JMenu("Winken"), new JMenu("Blinken"),
new JMenu("Nod")
};
private 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.length; i++) {
items[i].addActionListener(al);
menus[i % 3].add(items[i]);
}
JMenuBar mb = new JMenuBar();
for(int i = 0; i < menus.length; 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);
}
} ///:~
程序中通过取模运算“i%3”把菜单项分配给三个Jmenu。每个JMenuItem必须有一个相
关联的ActionListener;这里使用了同一个ActionListener,不过通常你要为每个
JmenuItem都单独准备一个ActionListener。
JMenuItem 从 AbstractButton 继承而来,所以它具有类似按钮的行为。它提供了一个可
以单独放置在下拉菜单上的条目。还有三种类型继承自 JMenuItem:JMenu 用来持有其
它的 JMenuItem(这样才能实现层叠式菜单);JCheckBoxMenuItem 提供了一个检查
标记,用来表明菜单项是否被选中;JRadioButtonMenuItem 包含了一个单选按钮。
下面是一个更复杂的例子,这里仍然是冰激凌口味菜单。这个例子还演示了层叠式菜单、
菜单快捷键、JCheckBoxMenuItem,以及动态改变菜单的方法:
//: c14:Menus.java
// Submenus, checkbox menu items, swapping menus,
// mnemonics (shortcuts) and action commands.
// <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 {
private String[] flavors = {
"Chocolate", "Strawberry", "Vanilla Fudge Swirl",
"Mint Chip", "Mocha Almond Fudge", "Rum Raisin",
"Praline Cream", "Mud Pie"
};
private JTextField t = new JTextField("No flavor", 30);
private JMenuBar mb1 = new JMenuBar();
private JMenu
f = new JMenu("File"),
m = new JMenu("Flavors"),
s = new JMenu("Safety");
// Alternative approach:
private JCheckBoxMenuItem[] safety = {
new JCheckBoxMenuItem("Guard"),
new JCheckBoxMenuItem("Hide")
};
private JMenuItem[] file = { new JMenuItem("Open") };
// A second menu bar to swap to:
private JMenuBar mb2 = new JMenuBar();
private JMenu fooBar = new JMenu("fooBar");
private JMenuItem[] other = {
// Adding a menu shortcut (mnemonic) is very
// simple, but only JMenuItems can have them
// in their constructors:
new JMenuItem("Foo", KeyEvent.VK_F),
new JMenuItem("Bar", KeyEvent.VK_A),
// No shortcut:
new JMenuItem("Baz"),
};
private JButton b = new JButton("Swap Menus");
class BL implements ActionListener {
public void actionPerformed(ActionEvent e) {
JMenuBar m = getJMenuBar();
setJMenuBar(m == mb1 ? mb2 : mb1);
validate(); // Refresh the frame
}
}
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 < 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!");
}
}
}
class FL implements ActionListener {
public void actionPerformed(ActionEvent e) {
JMenuItem target = (JMenuItem)e.getSource();
t.setText(target.getText());
}
}
// 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) {
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 hidden? " + 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[1].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 < flavors.length; i++) {
JMenuItem mi = new JMenuItem(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]);
s.setMnemonic(KeyEvent.VK_A);
f.add(s);
f.setMnemonic(KeyEvent.VK_F);
for(int i = 0; i < file.length; 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);
// Set up the system for swapping menus:
b.addActionListener(new BL());
b.setMnemonic(KeyEvent.VK_S);
cp.add(b, BorderLayout.NORTH);
for(int i = 0; i < other.length; 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);
}
} ///:~
在这个程序中,我把菜单项放到了几个数组中,然后通过遍历这些数组,并为每个
JmenuItem调用add()方法的方式,将它们添加到菜单中。这就使添加或减少菜单项时
不至于太乏味。
程序中不是创建了一个而是创建了两个JMenuBar,用以演示程序运行期间可以动态替换菜
单条。你可以看到如何用JMenu构造JMenuBar,以及用JMenuItem,
JCheckBoxMenuItem甚至JMenu(产生子菜单)来装配JMenu。当构造完一个JmenuBar
后,可以使用setJMenuBar( )方法把它安装到当前程序上。注意,当按钮按下的时候,它
将通过调用 getJMenuBar( )来判断当前安装的是哪一个菜单条,然后替换成另一个菜单
条。
在测试“Open”菜单项的时候,要注意到拼写和大小写是很关键的,如果没有任何匹配的
“Open”,Java也不会报告任何错误。这种类型的字符串比较是造成程序错误的根源之一。
菜单项的选中和清除能够被自动地处理。处理JCheckBoxMenuItem的代码演示了判断菜
单项是否选中的两种方式:字符串比较(如上所述,尽管能使用这种方法,但很不安全),
和比较事件的目标对象。可以使用getState( )方法得到是否选中的状态。你还可以用
setState( )方法来改变JCheckBoxMenuItem的状态。
菜单对应的事件有些不一致,这可能会引起困惑:JMenuItem使用的是ActionListener,
而JCheckBoxMenuItem使用的是ItemListener。JMenu虽然对象也支持
ActionListeners,不过其用处并不大。一般来说,你要把监听器关联到每一个JMenuItem,
JCheckBoxMenuItem, 或者JradioButtonMenuItem上,但是在上例中ItemListener
和ActionListener关联到了不同的菜单组件上。
Swing支持助记键,或者称为“键盘快捷键”,所以你可以使用键盘而不是鼠标来选择任
何从AbstractButton(按钮,菜单项等等)继承而来的组件。做到这一点很简单;只要使
用重载的构造器,使它的第二个参数接受快捷键的标识符即可。不过,大多数
AbstractButton没有这样的构造器,所以更通用作法的是使用setMnemonic( )方法。上
例中为按钮和部分菜单项添加了快捷键;快捷指示符会自动地出现在组件上。
你还能看到 setActionCommand( )的用法。它看起来有些奇怪,因为在每种情况下的“动
作命令”(action command)与菜单上的标签都完全相同。为什么不直接使用标签而是
这种额外的字符串呢?问题在于对国际化的支持。如果你要把程序以另一种语言发布,最
好是希望只改变菜单上的标签,而不用修改代码(毫无疑问,修改代码会引入新的错误)。
为了使代码能更容易地判断与菜单关联的字符串,可以把“动作命令”作为不变量,而把
菜单上的标签作为可变量。所有的代码在运行时都使用“动作命令”,这样改变菜单标签
的时候就不会影响代码。注意,在本例中,并非所有菜单都是基于“动作命令”进行判断
的,这是因为没有专门为它们设定“动作命令”。
大量工作都是在监听器中完成的。BL执行的是JMenuBar的交换。在ML中,采用了“找出
按铃者”方式,它的作法是先得到ActionEvent的事件源,然后把它类型转换成
JmenuItem,接着得到其“动作命令”的字符串,并且把它传递给级联的if语句进行处理。
尽管FL监听器处理的是风味菜单中所有不同风味的菜单项,但它的确很简单。如果你的事
件处理逻辑够简单的话,这种方式值得参考。不过一般情况下,你会采用在FooL,BarL和
BazL里面所使用的方式,它们只被关联到一个菜单项,所以就不需要进行额外的判断,因
为你明确知道是谁调用了监听器。尽管这种方式产生了更多的类,但是类内部的代码会更
短,整个处理过程也更安全。
你会发现,有关菜单的代码很快就变得冗长而且凌乱了。这时,使用 GUI 构造工具才是明
智的选择。好的工具还可以对菜单进行维护。