使用的教材是java核心技术卷1,我将跟着这本书的章节同时配合视频资源来进行学习基础java知识。
day072 事件处理(动作)
通常,激活一个命令可以有多种方式。用户可以通过菜单、击键或工具栏上的按钮选择特定的功能。在AWT事件模型中实现这些非常容易:将所有事件连接到同一个监听器上。例如,假设blueAction是一个动作监听器,它的actionPerformed方法可以将背景颜色改变成蓝色。将一个监听器对象加到下面几个事件源上:
•标记为Blue的工具栏按钮
•标记为Blue的菜单项
•按键CTRL+B
然后,无论改变背景颜色的命令是通过哪种方式下达的,是点击按钮、菜单选择,还是按键’其处理都是一样的。
Swing包提供了一种非常实用的机制来封装命令,并将它们连接到多个事件源,这就是Action接口。一个动作是一个封装下列内容的对象:
•命令的说明(一个文本字符串和一个可选图标);
•执行命令所需要的参数(例如,在列举的例子中请求改变的颜色)。
Action接口包含下列方法:
void actionPerformed(ActionEvent event)
void setEnabled(boolean b)
booleanisEnabled()
void putValue(String key, Object value)
Object getValue(String key)
void addPropertyChangeListener(PropertyChangeListener listener)
void removePropertyChangeListener(PropertyChangeListener listener)
第一个方法是ActionListener接口中很熟悉的一个:实际上,Action接口扩展于ActionListener接口,因此,可以在任何需要ActionListener对象的地方使用Action对象。
接下来的两个方法允许启用或禁用这个动作,并检査这个动作当前是否启用。当一个连接到菜单或工具栏上的动作被禁用时,这个选项就会变成灰色。
putValue和getvalue方法允许存储和检索动作对象中的任意名/值。有两个重要的预定义字符串:Action.NAME和Action.SMALL_ICON,用于将动作的名字和图标存储到一个动作对象中:
action.putValue(Action.NAME, "Blue");
action.putValue(Action.SMALL_ICON, new ImageIcon("blue-ball.gif"));
如果动作对象添加到菜单或工具栏上,它的名称和图标就会被自动地提取出来,并显示在菜单项或工具栏项中。SHORT_DESCRIPTION值变成了工具提示。
Action接口的最后两个方法能够让其他对象在动作对象的属性发生变化时得到通告,尤其是菜单或工具栏触发的动作。例如,如果增加一个菜单,作为动作对象的属性变更监听器,而这个动作对象随后被禁用,菜单就会被调用,并将动作名称变为灰色。属性变更监听器是一种常用的构造形式,它是JavaBeans组件模型的一部分。
需要注意,Action是一个接口,而不是一个类。实现这个接口的所有类都必须实现刚才讨论的7个方法。庆幸的是,有一个类实现了这个接口除actionPerformed方法之外的所有方法,它就是AbstractAction。这个类存储了所有名/值对,并管理着属性变更监听器。我们可以直接扩展AbstractAction类,并在扩展类中实现actionPerformed方法。
卞面构造一个用于执行改变颜色命令的动作对象。首先存储这个命令的名称、图标和需要的颜色。将颜色存储在AsbstractAction类提供的名/值对表中。下面是ColorAction类的代码。构造器设置名/值对,而actionPerformed方法执行改变颜色的动作。
public class ColorAction extends AbstractAction
{
public ColorAction(String name, Icon icon, Color c)
{
putValue(Action.NAME, name);
putValue(Action.SMALLJCON, icon);
putValuefcolor", c);
putValue(Action.SHORT_DESCRIPTION,"Set panel color to " + name.toLowerCase());
}
public void actionPerformed(ActionEvent event)
{
Color c = (Color) getValue("color");
buttonPanel.setBackground(c);
}
}
在测试程序中,创建了这个类的三个对象,如下所示:
Action blueAction = new ColorAction("Blue", new IinageIconCblue-ban.gif"), Color.BLUE);
接下来,将这个动作与一个按钮关联起来。由于JButton有一个用Action对象作为参数的构造器,所以实现这项操作很容易。
JButton blueButton = new JButton(blueAction);
构造器读取动作的名称和图标,为工具提示设置简要说明,将动作设置为监听器。
最后,想要将这个动作对象添加到击键中,以便让用户敲击键盘命令来执行这项动作。为了将动作与击键关联起来,首先需要生成Keystroke类对象。这是一个很有用的类,它封装了对键的说明。要想生成一个Keystroke对象,不要调用构造器,而是调用Keystroke类中的静态getKeyStroke方法:
Keystroke ctrlBKey = Keystroke.getKeyStroke("Ctrl B");
为了能够理解下一个步骤,需要知道的概念。用户界面中可以包含许多按钮、菜单、滚动栏以及其他的组件。当用户敲击键盘时,这个动作会被发送给拥有焦点的组件。通常具有焦点的组件可以明显地察觉到(但并不总是这样),例如,在Java观感中,具有焦点的按钮在按钮文本周围有一个细的矩形边框。用户可以使用TAB键在组件之间移动焦点。当按下SPACE键时,就点击了拥有焦点的按钮。还有一些键执行一些其他的动作,例如,按下箭头键可以移动滚动条。
然而,在这里的示例中,并不希望将击键发送给拥有焦点的组件。否则,每个按钮都需要知道如何处理CTRL+Y、CTRL+B和CTRL+R这些组合键。
这是一个常见的问题,Swing设计者给出了一种很便捷的解决方案。每个JComponent有三个输入映射(imputmap),每一个映射的Keystroke对象都与动作关联。三个输人映射对应着三个不同的条件。
按键处理将按照下列顺序检査这些映射:
1)检查具有输入焦点组件的WHEN_FOCUSED映射。如果这个按键存在,将执行对应的动作。如果动作已启用,则停止处理。
2)从具有输人焦点的组件开始,检査其父组件的WHEN_ANCESTOR_OF_FOCUSED_COMPONENT映射。一旦找到按键对应的映射,就执行对应的动作。如果动作已启用,将停止处理。
3)査看具有输人焦点的窗口中的所有可视的和启用的组件,这个按键被注册到WHEN_IN_FOCUSED_WINDOW映射中。给这些组件(按照按键注册的顺序)一个执行对应动作的机会。一旦第一个启用的动作被执行,就停止处理。如果一个按键在多个WHENJN_FOCUSED_WINDOW映射中出现,这部分处理就可能会出现问题。
可以使用getlnputMap方法从组件中得到输人映射。例如:
InputMap imap = panel.getInputMap(JComponent.WHEN_FOCUSED);
WHEN_FOCUSED条件意味着在当前组件拥有键盘焦点时会查看这个映射。在这里的示例中,并不想使用这个映射。是某个按钮拥有输入焦点,而不是面板。其他的两个映射都能够很好地完成增加颜色改变按键的任务。在示例程序中使用的是WHEN_ANCESTOR_OF_F0CUSED_C0MP0NENT。
InputMap不能直接地将Keystroke对象映射到Action对象。而是先映射到任意对象上,然后由ActionMap类实现将对象映射到动作上的第2个映射。这样很容易实现来自不同输入映射的按键共享一个动作的目的。
因而,每个组件都可以有三个输人映射和一个动作映射。为了将它们组合起来,需要为动作命名。下面是将键与动作关联起来的方式:
imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow");
ActionMap amap = panel.getActionMap();
amap.put("panel.yellow", yellowAction);
习惯上,使用字符串 none 表示空动作。这样可以轻松地取消一个按键动作:
imap.put(KeyStroke.getKeyStroke("ctrl C"),"none");
下面总结一下用同一个动作响应按钮、菜单项或按键的方式:
1)实现一个扩展于AbstractAction类的类。多个相关的动作可以使用同一个类。
2)构造一个动作类的对象。
3)使用动作对象创建按钮或菜单项。构造器将从动作对象中读取标签文本和图标。
4)为了能够通过按键触发动作,必须额外地执行几步操作。首先定位顶层窗口组件,例如,包含所有其他组件的面板。
5)然后,得到顶层组件的WHEN_ANCESTOR_OF_FOCUS_COMPONENT输入映射。为需要的按键创建一个KeyStrike对象。创建一个描述动作字符串这样的动作键对象。将(按键,动作键)对添加到输人映射中。
6)最后,得到顶层组件的动作映射。将(动作键,动作对象)添加到映射中。
下面的程序给出了将按钮和按键映射到动作对象的完整程序代码。试试看,点击按钮或按下CTRL+Y、CTRL+B或CTRL+R来改变面板颜色。(没有找到合适的照片随便放了几张照片)
/**
*@author zzehao
*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ActionFrame extends JFrame
{
private JPanel buttonPanel;
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 200;
public ActionFrame()
{
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
buttonPanel = new JPanel();
//define actions
Action yellowAction = new ColorAction("Yellow", new ImageIcon("yellow-ball.gif"),Color.YELLOW);
Action blueAction = new ColorAction("Blue", new ImageIcon("blue-ball.gif"), Color.BLUE);
Action redAction = new ColorAction("Red", new ImageIcon("red-ball.gif") ,Color.RED);
//add buttons for these actions
buttonPanel.add(new JButton(yellowAction));
buttonPanel.add(new JButton(blueAction));
buttonPanel.add(new JButton(redAction));
//add panel to frame
add(buttonPanel);
//associate the Y, B, and R keys with names
InputMap imap = buttonPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow");
imap.put(KeyStroke.getKeyStroke("ctrl B"), "panel.blue");
imap.put(KeyStroke.getKeyStroke("ctrl R"), "panel.red");
//associate the names with actions
ActionMap amap = buttonPanel.getActionMap();
amap.put("panel.yellow", yellowAction);
amap.put("panel.blue", blueAction);
amap.put("panel.red",redAction);
}
public class ColorAction extends AbstractAction
{
public ColorAction(String name, Icon icon, Color c)
{
putValue(Action.NAME, name);
putValue(Action.SMALL_ICON, icon);
putValue(Action.SHORT_DESCRIPTION,"Set panel color to "+name.toLowerCase());
putValue("color", c);
}
public void actionPerformed(ActionEvent event)
{
Color c =(Color) getValue("color");
buttonPanel.setBackground(c);
}
}
public static void main(String[] args)
{
ActionFrame frame = new ActionFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
运行的结果如下: