2.3 Swing特定的事件处理
请记住,Swing组件是构建在AWT库之上的,Swing组件库具有一些改进的功能从而使得事件处理更为简单。功能改进覆盖AWT核心事件处理特性之上,由基本的动作监听到焦点管理。
为了简化事件处理,Swing库使用Action接口扩展了原始的ActionListener接口来存储具有事件处理器的可视属性。这使得事件处理器的创建独立于可视化组件。然后,当Action在稍后与一个组件相关联时,组件直接由事件处理器自动获取信息(例如按钮标签)。这包括当Action被修改时更新标签的通知。AbstractAction与TextAction类实现了这个概念。
Swing库同时添加了KeyStroke类从而使得我们更容易的响应键盘事件。当一个特定的击键序列被按下时,我们可以通知组件必须响应特定的动作,而无需监听一个特定键的所有按键事件。这些击键到动作的映射存储在InputMap与ActionMap对象的组合中。当组件容器具有信息时,InputMap就会特例化ComponentInputMap。Swing文本组件借助于Keymap接口可以更容易的使用这些来存储击键到动作的映射。第16章更详细的描述了TextAction支持的映射,以及文本事件处理功能的其余部分。
KeyboardFocusManager与DefaultKeyboardFocusManager,借助于FocusTraversalPolicy及其实现的帮助,管理焦点子系统。InputVerifier用于用户输入验证。这些内容都会在本章稍后的Swing组件管理部分进行讨论。
2.3.1 Action接口
Action接口是ActionListener接口的扩展,他可以非常灵活的用于定义与作为触发代理的组件相独立的共享事件处理器。这个接口实现了ActionListener,并且定义了一个查询表数据结构,其键值作为属性。然后,当Action与一个组件相关联时,这些显示属性会自动的传递到Action。下面是接口定义:
- public interface Action implements ActionListener {
-
// Constants
-
public final static String ACCELERATOR_KEY;
-
public final static String ACTION_COMMAND_KEY;
-
public final static String DEFAULT;
-
public final static String LONG_DESCRIPTION;
-
public final static String MNEMONIC_KEY;
-
public final static String NAME;
-
public final static String SHORT_DESCRIPTION;
-
public final static String SMALL_ICON; // Listeners
-
public void addPropertyChangeListener(PropertyChangeListener listener);
-
public void removePropertyChangeListener(PropertyChangeListener listener);
-
// Properties
-
public boolean isEnabled();
-
public void setEnabled(boolean newValue);
-
// Other methods
-
public Object getValue(String key);
-
public void putValue(String key, Object value);
-
}
因为Action仅是一个接口,Swing提供了一个类来实现这个接口,这就是AbstractAction。
2.3.2 AbstractAction类
AbstractAction类提供了Action接口的一个默认实现。这就是属性行为实现的地方。
使用Action
一旦我们通过继承定义一个AbstractAction并且提供一个public void actionPerformed(ActionEvent actionEvent)方法,我们就可以将其传递给一些特殊的Swing组件。JCheckBox,JToggleButton,JMenuItem,JCheckBoxMenuItem以及JRadioButtonMenuItem提供了由动作创建组件的构造函数,而Swing文本组件通过Keymap,InputMap以及ActionMap对Action对象提供了内建支持。
当具有关联Action的组件被添加到相应的Swing容器中时,选中会触发Action的actionPerformed(ActionEvent actionEvent)方法的调用。组件的显示是通过添加到内部数据结构的属性元素来定义的。了为演示的需要,列表2-8提供了一个具有Print标签以及一个图标的Action。当其被激活时,会输出一个Hello, World消息。
- import java.awt.event.*;
-
import javax.swing.*;
-
public class PrintHelloAction extends AbstractAction {
-
private static final Icon printIcon = new ImageIcon("Print.gif");
-
PrintHelloAction() {
-
super("Print", printIcon);
-
putValue(Action.SHORT_DESCRIPTION, "Hello, World");
-
}
-
public void actionPerformed(ActionEvent actionEvent) {
-
System.out.println("Hello, World");
-
}
-
}
一旦定义了Action,我们就可以创建Action并将其与我们所希望的组件相关联。
- Action printAction = new PrintHelloAction();
-
menu.add(new JMenuItem(printAction));
-
toolbar.add(new JButton(printAction));
在我们将Action与对象相关联之后,如果我们发现我们需要修改Action的属性,我们只需要在一个地方修改其设置 。因为所有的属性都是绑定的,他们会传播到使用Action的任意组件。例如,禁止Action(printAction.setEnabled(false))将会禁止分别在JMenu与JToolBar上所创建的JMenuItem与JButton。相应的,通过printAction.putValue(Action.NAME, "Hello, World")修改Action的名字将会修改相关联组件的文本标签。
图2-6JToolBar与JMenu上的PrintHelloAction的样子。可选中的按钮用来允许或是禁止Action,同时也可以修改其名字。
此示例的完整代码显示在列表2-9中。不要担心工具栏与菜单栏的创建。我们将会在第6章对其进行详细的讨论。
- /**
-
*
-
*/
-
package swingstudy.ch02;
-
-
import java.awt.BorderLayout;
-
import java.awt.EventQueue;
-
import java.awt.event.ActionEvent;
-
import java.awt.event.ActionListener;
-
-
import javax.swing.Action;
-
import javax.swing.JButton;
-
import javax.swing.JFrame;
-
import javax.swing.JMenu;
-
import javax.swing.JMenuBar;
-
import javax.swing.JMenuItem;
-
import javax.swing.JPanel;
-
import javax.swing.JToolBar;
-
-
/**
-
* @author lenovo
-
*
-
*/
-
public class ActionTester {
-
-
/**
-
* @param args
-
*/
-
public static void main(String[] args) {
-
// TODO Auto-generated method stub
-
-
Runnable runner = new Runnable() {
-
public void run() {
-
JFrame frame = new JFrame("Action Sample");
-
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-
-
final Action printAction = new PrintHelloAction();
-
-
JMenuBar menuBar = new JMenuBar();
-
JMenu menu = new JMenu("File");
-
menuBar.add(menu);
-
menu.add(new JMenuItem(printAction));
-
-
JToolBar toolBar = new JToolBar();
-
toolBar.add(new JButton(printAction));
-
-
JButton enableButton = new JButton("Enable");
-
ActionListener enableActionListener = new ActionListener() {
-
public void actionPerformed(ActionEvent event) {
-
printAction.setEnabled(true);
-
}
-
};
-
enableButton.addActionListener(enableActionListener);
-
-
JButton disableButton = new JButton("Disable");
-
ActionListener disableActionListener = new ActionListener() {
-
public void actionPerformed(ActionEvent event) {
-
printAction.setEnabled(false);
-
}
-
};
-
disableButton.addActionListener(disableActionListener);
-
-
JButton relabelButton = new JButton("Relabel");
-
ActionListener relabelActionListener = new ActionListener() {
-
public void actionPerformed(ActionEvent event) {
-
printAction.putValue(Action.NAME, "Hello, World");
-
}
-
};
-
relabelButton.addActionListener(relabelActionListener);
-
-
JPanel buttonPanel = new JPanel();
-
buttonPanel.add(enableButton);
-
buttonPanel.add(disableButton);
-
buttonPanel.add(relabelButton);
-
-
frame.setJMenuBar(menuBar);
-
-
frame.add(toolBar, BorderLayout.SOUTH);
-
frame.add(buttonPanel, BorderLayout.NORTH);
-
-
frame.setSize(300, 200);
-
frame.setVisible(true);
-
}
-
};
-
-
EventQueue.invokeLater(runner);
-
}
-
-
}
AbstractAction属性
正如表2-2所示,AbstractAction类有三个可用的属性。
属性名
|
数据类型
|
访问性 |
enabled
|
boolean
|
读写绑定 |
keys
|
Object[]
|
只读 |
propertyChangeListeners
|
PropertyChangeListener[]
|
只读 |
其余的绑定属性通过putValue(String key, Object value)放置在查询表中。获取当前的keys属性设置可以使得我们查看可以进行哪些设置,而不需要进行单独请求。表2-3描述了可以用作键值的Action预定义常量集合。我们也可以添加我们自己的常量,从而在以后动作发生时进行查询。
常量
|
描述 |
NAME
|
Action名字,用作按钮标签 |
SMALL_ICON
|
Action图标,用作按钮标签 |
SHORT_DESCRIPTION
|
Action的简短描述;可以用作提示文本,但是默认情况下并不用 |
LONG_DESCRIPTION
|
Action的长描述;可以用作访问功能(查看第22章) |
ACCELERATOR
|
KeyStroke字符串;可以用Action的快捷键 |
ACTION_COMMAND_KEY
|
InputMap键;映射到与JComponent相关的ActionMap中的Action |
MNEMONIC_KEY
|
按键代码;可以用作Action的快捷键 |
DEFAULT
|
可以用于我们自定义属性的未用常量 |
一旦一个属性已经存放在查询表中,我们可以通过public Object getValue(String key)进行获取。其作用方式类似于java.util.Hashtable类或是java.util.Map接口,区别在于:如果表中存在一个键值,那么我们尝试存入一个具有null值的key/value对,则查询表会移除这个键值。
2.3.3 KeyStroke类
KeyStroke类以及特定JComponent的inputMap与actionMap属性提供了一个简单的替换可以向组件注册KeyListener对象并监听特定键的按下。KeyStroke使得我们可以定义一个简单的按键集合,例如Shift-Ctrl-P或是F4。然后我们可以通过将其注册到组件来激活按键,并且在组件识别出时通知按键进行动作,从而通知ActionListener。
在我们探讨如何创建按键之前,我们先来了解一下可以激活按键的不同条件,从而添加不同的输入映射。有三个条件可以激活已注册的按键,并JComponent中的四个常量可以提供帮助。第四个用于未定义的状态。表2-4中列出了可用的四个常量。
常量
|
描述 |
WHEN_FOCUSED
|
当实际的组件获得输入焦点时激活按键 |
WHEN_IN_FOCUSED_WINDOW
|
当组件所在的窗口获得输入焦点时激活按键 |
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
|
当在组件或是在组件的容器中按下时激活按键 |
UNDEFINED_CONDITION
|
用于没有定义条件的情况 |
构建按键
KeyStroke类是AWTKeyStroke的子类,并且没有公开的构造函数。我们可以通过下面的方法来创建一个按键:
- public static KeyStroke getKeyStroke(char keyChar)
-
public static KeyStroke getKeyStroke(String representation)
-
public static KeyStroke getKeyStroke(int keyCode, int modifiers)
-
public static KeyStroke getKeyStroke(int keyCode, int modifiers,
-
boolean onKeyRelease)
-
public static KeyStroke getKeyStrokeForEvent(KeyEvent keyEvent)
列表中的第一个版本,public static KeyStroke getKeyStroke(char keyChar),可以使得我们由一个char变量创建按键,例如Z。
KeyStroke space = KeyStroke.getKeyStroke('Z');
public static KeyStroke getKeyStroke(String representation)版本是最有趣的版本。他可以使得我们通过一个文本字符串来指定按键,例如"control F4"。字符串的标识符集合为shift, control, meta, alt, button1, button2与button3以及可以指定的多标识符。字符串的其余部分来自KeyEvent类的VK_*常量。例如,下面的代三为Ctrl-Alt-7定义了一个按键:
KeyStroke controlAlt7 = KeyStroke.getKeyStroke("control alt 7");
public static KeyStroke getKeyStroke(int keyCode, int modifiers)public static KeyStroke getKeyStroke(int keyCode, int modifiers,boolean onKeyRelease)是两个最为直接的方法。他允许我们直接指定VK_*常量 以及用于标识符的InputEvent掩码(没有标识符时为0)。当没有指定时,onKeyRelease为false。
- KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true);
-
KeyStroke shiftF4 = KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.SHIFT_MASK);
列表中的最后一个版本,public static KeyStroke getKeyStrokeForEvent(KeyEvent keyEvent),将特定的KeyEvent直接映射到KeyStroke。当我们希望允许用户使用按键来激活事件时,这个方法就十分有用。我们要求用户为某一事件按下一个键,然后注册KeyEvent,从而下次按键发生时,事件就会被激活。
KeyStroke fromKeyEvent = KeyStroke.getKeyStrokeForEvent(keyEvent);
注册按键
在我们创建了按键之后,我们需要将其注册到组件。当我们向组件注册一个按键时,我们提供一个当按键按下(或是释放)时要调用的Action。注册要提供一个由按键到Action的映射。首先,我们通过getInputMap(condition)方法获取基于焦点激活条件组件的相应的InputMap。如果没有指定条件,则假定为WHEN_FOCUSED。然后我们在InputMap中添加一个由按键到文本字符串的映射:
component.getInputMap().put(keystroke, string)
如果我们知道已存在动作的动作字符串,我们就可以使用这个字符串;否则我们要定义这个字符串。然后我们使用ActionMap将字符串映射到Action:
component.getActionMap.put(string, action)
我们可以通过共享ActionMap实例来在组件之间共享动作。列表2-10的例子中创建了四个按钮,每一个都注册了不同的按键以及不同的焦点激活条件。按钮标签表明了按键激活条件。Action只是简单的输出消息并激活按钮标签。
- /**
-
*
-
*/
-
package swingstudy.ch02;
-
-
import java.awt.EventQueue;
-
import java.awt.GridLayout;
-
import java.awt.event.ActionEvent;
-
import java.awt.event.InputEvent;
-
import java.awt.event.KeyEvent;
-
-
import javax.swing.AbstractAction;
-
import javax.swing.Action;
-
import javax.swing.ActionMap;
-
import javax.swing.InputMap;
-
import javax.swing.JButton;
-
import javax.swing.JComponent;
-
import javax.swing.JFrame;
-
import javax.swing.KeyStroke;
-
-
/**
-
* @author lenovo
-
*
-
*/
-
public class KeyStrokeSample {
-
-
private static final String ACTION_KEY = "theAction";
-
-
/**
-
* @param args
-
*/
-
public static void main(String[] args) {
-
// TODO Auto-generated method stub
-
Runnable runner = new Runnable() {
-
public void run() {
-
JFrame frame = new JFrame("KeyStroke Sample");
-
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-
-
JButton buttonA = new JButton("FOCUSED
control alt 7");
-
JButton buttonB = new JButton("FOCUS/RELEASE
VK_ENTER");
-
JButton buttonC = new JButton("ANCESTOR
VK_F4+SHIFT_MASK");
-
JButton buttonD = new JButton("WINDOW
' '");
-
-
Action actionListener = new AbstractAction() {
-
public void actionPerformed(ActionEvent event) {
-
JButton source = (JButton)event.getSource();
-
System.out.println("Activated: "+source.getText());
-
}
-
};
-
-
KeyStroke controlAlt7 = KeyStroke.getKeyStroke("control alt 7");
-
InputMap inputMap = buttonA.getInputMap();
-
inputMap.put(controlAlt7, ACTION_KEY);
-
ActionMap actionMap = buttonA.getActionMap();
-
actionMap.put(ACTION_KEY, actionListener);
-
-
KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true);
-
inputMap = buttonB.getInputMap();
-
inputMap.put(enter, ACTION_KEY);
-
buttonB.setActionMap(actionMap);
-
-
KeyStroke shiftF4 = KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.SHIFT_MASK);
-
inputMap = buttonC.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
-
inputMap.put(shiftF4, ACTION_KEY);
-
buttonC.setActionMap(actionMap);
-
-
KeyStroke space = KeyStroke.getKeyStroke(' ');
-
inputMap = buttonD.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
-
inputMap.put(space, ACTION_KEY);
-
buttonD.setActionMap(actionMap);
-
-
frame.setLayout(new GridLayout(2,2));
-
frame.add(buttonA);
-
frame.add(buttonB);
-
frame.add(buttonC);
-
frame.add(buttonD);
-
-
frame.setSize(400, 200);
-
frame.setVisible(true);
-
}
-
};
-
-
EventQueue.invokeLater(runner);
-
}
-
-
}
图2-7显示了程序运行时的样子。
2.3.4 使用快捷键
Swing库也可以使用KeyStroke对象用于一些内部功能。两个这样的功能为记忆键与快捷键,其工作如下:
- 在组件记忆键中,标签中的一个字符以下划线出现。当这个字符平台特定的热键组合被按下时,组件就会被激活。例如,在图2-8所示的窗体中按下Alt-A则会在Windows XP平台下选中About按钮。
- 菜单快捷键可以在菜单条目不可见的情况下激活条目。例如,在图2-8所示的窗体中按下Ctrl-P将会在File菜单不可见的情况下选中Print菜单条目。
我们将会在第6章了解更多关于记忆键与快捷键的内容。
阅读(6503) | 评论(1) | 转发(1) |