在Swing中,按钮点击、鼠标移动、键盘输入等事件处理是与用户交互的关键部分。Swing的事件处理机制遵循的是观察者模式(Observer Pattern),通过事件源、事件对象和事件监听器来实现。不过实际上Swing的事件处理部分是借用AWT的实现,因此我们可能发现很多的类其实位于AWT包下。这篇笔记我们对Swing的事件处理机制进行详细介绍。
Swing中,事件源就是发出事件的对象,例如对于按钮,点击按钮就会发出按钮点击事件。事件监听器则需要注册到事件源的,事件监听器通常是一个回调函数,告知当此事件发生时,程序该怎么做。我们简单看一下ActionListener
这个接口。
public interface ActionListener extends EventListener {
public void actionPerformed(ActionEvent e);
}
我想经验丰富的同学一看这个就知道监听器怎么写了,其实actionPerformed
就是一个回调函数,事件发生时就会被回调,传入一个事件对象,我们根据这个事件对象作出相应判断即可。我们可以实现这个接口实现自己的监听器类,或者直接使用匿名类。除了ActionListener
,Swing中还有许多其它的监听器实现GUI界面的各种功能,具体可以参考文档。
事件被封装在事件对象中,事件对象有很多,如ActionEvent
,WindowEvent
等,事件对象又可以划分成不同的继承层次,所有事件派生自java.util.EventObject
。
JButton btnHello = new JButton("hello");
btnHello.addActionListener((e)->{
System.out.println("button clicked!");
});
事件对象还有一些其他的实用方法:
Object getSource()
返回发生事件的对象的引用。String getActionCommand()
返回这个事件的动作字符串。如果事件来自按钮,默认动作字符串是按钮标签,除非使用setActionCommand()
对按钮对象的动作字符串进行修改。有时候多个事件源(控件)需要绑定同一种命令(操作),多个控件绑定同一个监听器是可以实现的,但是Swing再次封装了一层,提供了Action
接口用来封装命令,并把它连接到多个事件源。具体使用Action的好处请往下看。我们先大致看下Action
接口有什么内容。
public interface Action extends ActionListener {
public static final String DEFAULT = "Default";
public static final String NAME = "Name";
public static final String SHORT_DESCRIPTION = "ShortDescription";
public static final String LONG_DESCRIPTION = "LongDescription";
public static final String SMALL_ICON = "SmallIcon";
public static final String ACTION_COMMAND_KEY = "ActionCommandKey";
public static final String ACCELERATOR_KEY="AcceleratorKey";
public static final String MNEMONIC_KEY="MnemonicKey";
public static final String SELECTED_KEY = "SwingSelectedKey";
public static final String DISPLAYED_MNEMONIC_INDEX_KEY = "SwingDisplayedMnemonicIndexKey";
public static final String LARGE_ICON_KEY = "SwingLargeIconKey";
public Object getValue(String key);
public void putValue(String key, Object value);
public void setEnabled(boolean b);
public boolean isEnabled();
public void addPropertyChangeListener(PropertyChangeListener listener);
public void removePropertyChangeListener(PropertyChangeListener listener);
}
这个接口实际上并不复杂,我们一组一组看。
首先,Action
接口继承了ActionListener
,那它就由ActionPerformed()
回调函数。除此之外,还定义了上述的一些属性的方法。
setEnabled()/isEnabled()
启用禁用这个动作/检查动作是否可用,不可用按钮会显示为灰色getValue()/putValue()
存储/读出动作键属性值对,有一些Swing预定义的键值对可以使用:NAME
动作名称,显示在按钮和菜单上SMALL_ICON
存储小图标,显示在按钮,菜单或工具栏中SHORT_DESCRIPTION
动作的简短说明LONG_DESCRIPTION
详细说明,Swing组件不使用这个值MNEMONIC_KEY
快捷键缩写,显示在菜单栏中ACCELERATOR_KEY
存储加速击键的地方,Swing组件不使用这个值DEFAULT
常用的综合属性,Swing组件不使用这个值addPropertyChangeListener()/removePropertyChangeListener()
属性变更监听器当动作被赋予一个控件时,动作属性就会自动被显示出来。这比多个控件编写相同的动作名、图标、说明等等简洁多了。
实际上,我们不需要自己实现Action
接口,Swing提供了一个AbstractAction
实现了这个接口,我们继承这个类,并重写ActionPerformed()
就好了。
一个自定义Action的例子代码如下。
class MyAction extends AbstractAction {
public MyAction(String name, Icon icon) {
putValue(Action.NAME, name);
putValue(Action.SMALL_ICON, icon);
putValue(Action.SHORT_DESCRIPTION, "test description");
}
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("hello");
}
}
调用这个Action创建按钮例子代码如下。
JButton btn = new JButton(new MyAction("btn", icon));
上面介绍的Listener接口如果有不止一个回调方法,那么显然我们创建类时就需要实现其全部方法,比如WindowListener
用于监听窗体的生命周期,非常麻烦,我们实现如何优雅的实现此类Listner呢?其实Swing还提供了一系列适配器,比如对应WindowListener
就有WindowAdaptor
。
package com.gacfox.demo;
import javax.swing.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class MainFrame extends JFrame {
public MainFrame() {
this.addWindowListener(new WindowAdapter() {
@Override
public void windowDeactivated(WindowEvent e) {
System.out.println("窗口失去了焦点");
}
});
this.setSize(300, 200);
}
}
此时虽然不能使用lambda
表达式,但是我们创建的匿名类只用实现一个方法即可。