6.1.4 JMenuItem类
JMenuItem组件是用户可以在菜单栏上选择的预定义组件。作为AbstractButton的子类,JMenuItem是一个特殊的按钮组件,其行为类似于JButton。除了作为AbstractButton的子类,JMenuItem类共享JButton的数据模型(ButtonModel接口与DefaultButtonModel实现)。
创建JMenuItem组件
JMenuItem有六个构造函数。这些构造函数可以使得我们初始化菜单项的字符串或是图标以及菜单项的热键。并不存在显式的构造函数允许我们在创建时设置所有三个选项,除非我们将其作为Action的一部分。
public JMenuItem() JMenuItem jMenuItem = new JMenuItem(); public JMenuItem(Icon icon) Icon atIcon = new ImageIcon("at.gif"); JMenuItem jMenuItem = new JMenuItem(atIcon); public JMenuItem(String text) JMenuItem jMenuItem = new JMenuItem("Cut"); public JMenuItem(String text, Icon icon) Icon atIcon = new ImageIcon("at.gif"); JMenuItem jMenuItem = new JMenuItem("Options", atIcon); public JMenuItem(String text, int mnemonic) JMenuItem jMenuItem = new JMenuItem("Cut", KeyEvent.VK_T); public JMenuItem(Action action) Action action = ...; JMenuItem jMenuItem = new JMenuItem(action);
热键可以使得我们通过键盘浏览选择菜单。例如,在Windows平台上,如果菜单项出现在已打开的Edit菜单中,我们可以通过简单的按下Alt-T来选中Cut菜单。菜单项的热键通常以菜单文本标签中的下划线形式出现。然而,如果字符并没有出现在文本标签中,或者是没有文本标签,用户通常并不会得到明显的提示。字符是通过java.awt.event.KeyEvent类中的不同常量来标识的。
其他的平台也许会提供其他的选中热键。在Unix平台下,通常是Alt键;而在Macintosh平台下则是Command键。
JMenuItem属性
JMenuItem有多个属性。大约有100个属性是通过各种超类来继承的。表6-3显示了JMenuItem特定的10个属性。
JMenuItem属性
属性名 | 数据类型 | 访问性 |
accelerator | KeyStroke | 读写绑定 |
accessibleContext | AccessibleContext | 只读 |
armed | boolean | 读写 |
component | Component | 只读 |
enabled | boolean | 只写绑定 |
menuDragMouseListeners | MenuDragMouseListener[] | 只读 |
menuKeyListeners | MenuKeyListener[] | 只读 |
subElements | MenuElement[] | 只读 |
UI | MenuElementUI | 只写绑定 |
UIClassID | String | 只读 |
其中比较有趣的一个属性就是accelerator。正如第2章所解释的,KeyStroke是一个工厂类,可以使得我们基于按键与标识符组合创建实例。例如,下面的代码语句来自于本章列表6-1中的示例,将Ctrl-X作为快捷键与一个特定的菜单项相关联:
KeyStroke ctrlXKeyStroke = KeyStroke.getKeyStroke("control X");
cutMenuItem.setAccelerator(ctrlXKeyStroke);
只读的component与subElement属性是JMenuItem所实现的MenuElement接口的一部分。component属性是菜单项的渲染器(JMenuItem本身)。subElement属性是空的(也就是一个空的数组,而不是null),因为JMenuItem并没有子类。
处理JMenuItem事件
我们可以在JMenuItem内部使用至少五种不同的方法来处理事件。组件继承了允许我们通过AbstractButton的ChangeListener与ActionListener注册的方法来触发ChangeEvent与ActionEvent的能力。中软皮,JMenuItem组件支持当MenuKeyEvent与MenuDragMouseEvent事件发生时注册MenuKeyListener与MenuDragMouseListener对象。这些技术会在后面的章节中进行讨论。第五种方法是向JMenuItem的构造函数传递Action,其作用类似于一种特殊的使用ActionListener监听的方法。要了解更多的关于使用Action的内容,可以查看本章稍后的“JMenu类”一节中关于在菜单中使用Action对象的讨论。
使用ChangeListener监听JMenuItem事件
通常我们并不会向JMenuItem注册ChangeListener。然而,演示一个理想的例子助于更为清晰的解释JMenuItem关于其ButtonModel数据模型的变化。所考虑的事件变化是与JButton相同的arm,press与select。然而,他们的名字会有一些迷惑,因为所选择的模型属性并没有进行设置。
当鼠标略过菜单选项并且菜单变为选中时,JMenuItem是armed。当用户释放其上的鼠标按钮时,JMenuItem是pressed。紧随按下之后,菜单项会变为未按下与unarmed。在菜单项变为按下与未按下之间,AbstractButton会得到模型变化的通知,从而使得菜单项所注册的ActionListener对象得到通知。一个普通JMenuItem的按钮模型不会报告被选中。如果我们没有选择而将鼠标移动到另一个菜单项上,则第一个菜单项会自动变化unarmed。为了有助于我们更好的理解不同的变化,图6-5显示了一个序列图。
使用ActionListener监听JMenuItem事件
关联到JMenuItem更好的监听器是ActionListener,或者是向构造函数传递一个Action。他可以使得我们确定哪一个菜单项被选中。当用户在作为打开菜单一部分的JMenuItem上释放鼠标按钮时,所注册的ActionListener对象会得到通知。如果用户通过键盘(箭头键或是热键)或是按下菜单快捷键来选中菜单时,所注册的监听器也会得到通知。
当我们希望菜单被选中时发生某个动作,我们必须为每一个JMenuItem添加一个ActionListener。并不存在一个自动的方法使得我们可以为JMenu或是JMenuBar注册一个ActionListener从而使得其所包含的JMenuItem对象通知一个ActionListener。
列表6-1中的示例程序为每一个JMenuItem关联了一个相同的ActionListener:
class MenuActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println("Selected: " + e.getActionCommand()); } }
然而更为通常的是,我们为每一个菜单项关联一个不同的动作,从而每一个菜单项可以进行不同的响应。
提示:我们并不需要为组件创建一个自定义的ActionListener并进行注册,我们可以创建一个自定义的Action,并且在组件上调用setAction()方法。
使用MenuKeyListener监听JMenuItem事件
MenuKeyEvent是用户界面类内部为JMenu与JMenuItem所用的特殊的KeyEvent,使得组件可以监听何时其键盘热键被按下。要监听这种键盘输入,每一个菜单组件注册一个MenuKeyListener,从而监听相应的输入。如果键盘热键被按下,事件就会被处理,从而不会再被传送到所注册的监听器。如果键盘热键没有被按下,所注册的键盘监听器(而不是菜单键监听器)就会得到通知。
MenuKeyListener接口定义如下:
public interface MenuKeyListener extends EventListener { public void menuKeyPressed(MenuKeyEvent e); public void menuKeyReleased(MenuKeyEvent e); public void menuKeyTyped(MenuKeyEvent e); }
通常我们并不需要自己注册这种类型的监听器对象,尽管如果我们希望我们仍可以这样做。如果我们确定这样做,并且如果MenuKeyEvent发生(也就是一个键被按下/释放),JMenuBar中的每一个JMenu都会得到通知,就如同打开菜单中的每一个JMenuItem(或是子类)都有一个注册的MenuKeyListener。这包括禁止的菜单项,从而他们可以处理按下的热键。MenuKeyEvent类的定义如下:
public class MenuKeyEvent extends KeyEvent { public MenuKeyEvent(Component source, int id, long when, int modifiers, int keyCode, char keyChar, MenuElement path[], MenuSelectionManager mgr); public MenuSelectionManager getMenuSelectionManager(); public MenuElement[] getPath(); }
确定当前选择路径是MenuSelectionManager的工作。选择路径是由顶层的JMenuBar上的JMenu到所选中的组件的菜单元素集合。对于大多数情况而言,管理器在幕后工作,而我们无需担心。
使用MenuDragMouseListener监听JMenuItem事件
与MenuKeyEvent类似,MenuDragMouseEvent也是用户界面类为JMenu与JMenuBar在内部所用的特殊的事件类型。正如其名字所显示的,MenuDragMouseEvent是一种特殊类型的MouseEvent。通过监听鼠标何时在打开的菜单中移动,用户界面类使用监听器来维护选择路径,从而确定当前选中的菜单项。其定义如下:
public interface MenuDragMouseListener extends EventListener { public void menuDragMouseDragged(MenuDragMouseEvent e); public void menuDragMouseEntered(MenuDragMouseEvent e); public void menuDragMouseExited(MenuDragMouseEvent e); public void menuDragMouseReleased(MenuDragMouseEvent e); }
与MenuKeyListener类似,通常我们并不需要亲自监听这一事件。如果我们比较感兴趣一个菜单或是子菜单何时显示,要注册的更好的监听器是MenuListener,这个监听器可以注册到JMenu,但是并不可以注册到单个的JMenuItem。我们将会在描述JMenu的下一节了解到这一点。
MenuDragMouseEvent类的定义,MenuDragMouseListener方法的参数如下:
public class MenuDragMouseEvent extends MouseEvent { public MenuDragMouseEvent(Component source, int id, long when, int modifiers, int x, int y, int clickCount, boolean popupTrigger, MenuElement path[], MenuSelectionManager mgr); public MenuSelectionManager getMenuSelectionManager(); public MenuElement[] getPath(); }
自定义JMenuItem观感
与JMenuBar类似,预定义的观感类型提供了不同的JMenuItem外观以及默认的UIResource值集合。图6-3显示了预安装集合的JMenuItem的外观:Motif,Windows与Ocean。
表6-4显示了JMenuItem的UIResource相关属性集合。JMenuItem组件提供了20个不同的属性。
JMenuItem UIResource元素
属性字符串 | 对象类型 |
MenuItem.acceleratorDelimiter | String |
MenuItem.acceleratorFont | Font |
MenuItem.acceleratorForeground | Color |
MenuItem.acceleratorSelectionForeground | Color |
MenuItem.actionMap | ActionMap |
MenuItem.arrowIcon | Icon |
MenuItem.background | Color |
MenuItem.border | Border |
MenuItem.borderPainted | Boolean |
MenuItem.checkIcon | Icon |
MenuItem.commandSound | String |
MenuItem.disabledForeground | Color |
MenuItem.font | Font |
MenuItem.foreground | Color |
MenuItem.margin | Insets |
MenuItem.opaque | Boolean |
MenuItem.selectionBackground | Color |
MenuItem.selectionForeground | Color |
MenuItem.textIconGap | Integer |
MenuItemUI | String |
6.1.5 JMenu类
JMenu组件是放置在JMenuBar上的基本菜单项。当一个JMenu被选中时,菜单在JPopupMenu内显示所包含的菜单项。对于JMenuItem,JMenu的数据模型则是一个ButtonModel实现,或者更为特定的,DefaultButonModel。
创建JMenu组件
JMenu有四个构造函数可以使得我们初始化菜单的字符串标签:
public JMenu() JMenu jMenu = new JMenu(); public JMenu(String label) JMenu jMenu = new JMenu("File"); public JMenu(String label, boolean useTearOffs) public JMenu(Action action) Action action = ...; JMenu jMenu = new JMenu(action);
其中一个构造函数用于tear-off菜单。然而,tear-off菜单当前并不被支持;所以其参数会被忽略。第四个构造函数使用Action中的属性来填充菜单。
注意:所谓tear-off菜单是显示在一个窗口中并且在选择之后仍然保持打开,而不是自动关闭。
向JMenu添加菜单项
一旦我们有了JMenu,我们需要向其添加JMenuItem对象;否则,菜单不会显示任何选择。有五个方法可以向JMenu添加所定义的菜单项,一个用于添加分隔符:
public JMenuItem add(JMenuItem menuItem); public JMenuItem add(String label); public Component add(Component component); public Component add(Component component, int index); public JMenuItem add(Action action); public void addSeparator();
在本章前面的列表6-1中,所有的JMenuItem组件是通过第一个add()方法添加到JMenu组件的。为了简便起见,我们可以将JMenuItem的文本标签传递给JMenu的add()方法。这个方法会创建菜单项,设置其标签,并且传回新菜单项组件。然后我们可以将菜单项事件处理器绑定到这个新获得的菜单项。第三个add()方法表明我们可以在JMenu上放置任意的Component的,而不仅是JMenuItem。第四个add()方法允许我们按位置放置组件。最后一个add()方法变体,带有一个Action参数,将会在下一节进行讨论。
我们可以使用JMenu的addSeparator()方法添加分隔栏。例如,在列表6-1中,File菜单是使用类似下面的代码来创建的:
JMenu fileMenu = new JMenu("File"); JMenuItem newMenuItem = new JMenuItem("New"); fileMenu.add(newMenuItem); JMenuItem openMenuItem = new JMenuItem("Open"); fileMenu.add(openMenuItem); JMenuItem closeMenuItem = new JMenuItem("Close"); fileMenu.add(closeMenuItem); fileMenu.addSeparator(); JMenuItem saveMenuItem = new JMenuItem("Save"); fileMenu.add(saveMenuItem); fileMenu.addSeparator(); JMenuItem exitMenuItem = new JMenuItem("Exit"); fileMenu.add(exitMenuItem);
注意,addSpeparator()调用包围了添加Save菜单项的调用。
除了在菜单的结束处添加菜单项以外,我们可以将菜单项插入在指定的位置或是将分隔符插入在指定的位置,如下所示:
public JMenuItem insert(JMenuItem menuItem, int pos); public JMenuItem insert(Action a, int pos); public void insertSeparator(int pos);
当一个菜单被添加到JMenu后,他就被加入了内部的JPopupMenu。
菜单中使用Action对象
在第2章中描述了Aciton接口及其相关联的类。Action是ActionListener接口的扩展,并且包含用于自定义与其实现相关联的组件的一些特殊属性。
借助于AbstractAction这现,我们可以很容易的定义文本标签,图标,热键,工具提示文本,允许状态,以及一个与组件相分离的ActionListener。然后我们可以使用相关联的Action创建组件,并且不必为组件指定文本标签,图标,热键,工具提示文本,允许状态,或是ActionListener,因为这些属性来自Action。要了解详细的描述,可以参考第2章。
为了演示的需要,列表6-2创建了AbstractAction的一个特定实现,并且将其多次添加到JMenu中。一旦Action被添加到JMenu,选择JMenuItem将会借助JOptionPane类显示一个弹出对话框,我们会在第9章讨论这一主题 。
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ShowAction extends AbstractAction { Component parentComponent; public ShowAction(Component parentComponent) { super("About"); putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_A)); this.parentComponent = parentComponent; } public void actionPerformed(ActionEvent actionEvent) { Runnable runnable = new Runnable() { public void run() { JOptionPane.showMessageDialog( parentComponent, "About Swing", "About Box V2.0", JOptionPane.INFORMATION_MESSAGE); } }; EventQueue.invokeLater(runnable); } }
下面的代码为列表6-1中的示例程序中的File与Edit菜单创建了一个ShowAction与一个JMenuItem。无需显示的设置菜单项属性,他会具有一个About文本标签与一个热键,并且会执行所定义的actionPerformed()方法作为其ActionListener。事实上,我们可以创建Action一次,然后可以将其关联到所需要的多个地方(或者是其他支持添加Action对象的组件)。
Action showAction = new ShowAction(aComponent); JMenuItem fileAbout = new JMenuItem(showAction); fileMenu.add(fileAbout); JMenuItem editAbout = new JMenuItem(showAction); editMenu.add(editAbout);
使用AbstractAction的副作用就是通过setEnabled(false)方法禁止Action时,相应的,会禁止所有由其创建的组件。
JMenu属性
除了由JMenu继承的100多个属性以外,表6-5显示了16个JMenu特定的属性。其中的一些属性覆盖了继承属性的行为。例如,accelerator属性的设置方法会在我们尝试为这个属性赋值时抛出错误。换句话说,快捷键并不为JMenu对象所支持。其余的属性描述了JMenu对象的当前状态及其所包含的菜单组件。
JMenu属性
属性名 | 数据类型 | 访问性 |
accelerator | KeyStroke | 只写 |
accessibleContext | AccessibleContext | 只读 |
component | Component | 只读 |
delay | int | 读写 |
itemCount | int | 只读 |
menuComponentCount | int | 只读 |
menuComponents | Component[] | 只读 |
menuListeners | MenuListener[] | 只读 |
model | ButtonModel | 只写绑定 |
popupMenu | JPopupMenu | 只读 |
popupMenuVisible | boolean | 读写 |
selected | boolean | 读写 |
subElements | MenuElement[] | 只读 |
tearOff | boolean | 只读 |
topLevelMenu | boolean | 只读 |
UIClassID | String | 只读 |
delay属性表示在选择JMenu与发出JPopupMenu之间所逝去的时间。默认情况这个值为0,表示会立即显示子菜单。尝试将这个值设置负值会抛出IllegalArgumentException。
选择菜单组件
通常我们并不需要监听JMenu组件的选择。我们只需要监听单个的JMenuItem组件的选择。然而,与JMenuItem相比,我们会对JMenu所用的ChangeEvent的不同方法感兴趣。另外,当一个菜单被弹出或是关闭时,MenuEvent会通知我们。
使用ChangeListener监听JMenu事件
与JMenuItem类似,如果我们对于修改底层的ButtonModel比较感兴趣,我们可以向JMenu注册ChangeListener。奇怪的是,JMenu的ButtonModel的唯一的状态改变就是selected属性。当被选中时,JMenu显示其菜单项。当没有被选中时,弹出菜单会消失。
使用MenuListener监听JMenu事件
监听弹出菜单何时显示或是隐藏的更好的方法是就是向JMenu对象注册MenuListener对象。其定义如下:
public interface MenuListener extends EventListener { public void menuCanceled(MenuEvent e); public void menuDeselected(MenuEvent e); public void menuSelected(MenuEvent e); }
通过注册MenuListener,当JMenu在弹出菜单打开之前选中时,我们会得到通知。这可以使得我们自定义其菜单选项。除了得到相关联的弹出菜单何时被弹出的通知,当菜单被取消选中以及菜单被关闭时我们也会得到通知。正如下面的MenuEvent类定义所显示的,事件所提供的唯一信息就是源(菜单):
public class MenuEvent extends EventObject { public MenuEvent(Object source); }
提示:如果我们选择动态自定义JMenu上的项,在确保调用revalidate(),因为组件会在我们更新显示之前一直等待。
自定义JMenu观感
与JMenuBar和JMenuItem类似,预定义的观感提供了不同的JMenu外观以及默认的UIResource值集合。图6-3显示了预安装的观感类型集合的JMenu对象外观。
表6-6显示了JMenu的UIResource相关属性的集合。对于JMenu组件,有30个不同的属性。
JMenu UIResource元素
属性字符串 | 对象类型 |
menu | Color |
Menu.acceleratorDelimiter | String |
Menu.acceleratorFont | Font |
Menu.acceleratorForeground | Color |
Menu.acceleratorSelectionForeground | Color |
Menu.ActionMap | ActionMap |
Menu.arrowIcon | Icon |
Menu.background | Color |
Menu.border | Border |
Menu.borderPainted | Boolean |
Menu.checkIcon | Icon |
Menu.delay | Integer |
Menu.disabledForeground | Color |
Menu.font | Font |
Menu.foreground | Color |
Menu.margin | Insets |
Menu.menuPopupOffsetX | Integer |
Menu.menuPopupOffsetY | Integer |
Menu.opaque | Boolean |
Menu.selectionBackground | Color |
Menu.selectionForeground | Color |
Menu.shortcutKeys | int[] |
Menu.submenuPopupOffsetX | Integer |
Menu.submenuPopupOffsetY | Integer |
Menu.textIconGap | Integer |
Menu.useMenuBarBackgroundForTopLevel | Boolean |
menuPressedItemB | Color |
menuPressedItemF | Color |
menuText | Color |
MenuUI | String |