想要实现用户界面,必须掌握 Java 事件处理的基本方法。现在开始探讨 Java AWT 事件模型的工作机制,从中可以看到如何捕获用户界面组件和输入设备产生的事件。另外,还将探讨如何以更加结构化的方式处理动作(actions)事件。
事件处理基础
任何支持 GUI 的操作环境都要不断地监视按键或点击鼠标这样的事件。操作环境将这些事件报告给正在运行的应用程序。如果有事件发生,每个应用程序将决定如何对它们做出响应。
像 Java 这样的面向对象语言,都将事件的相关信息封装在一个事件对象(event object)中。在 Java 中,所有的事件对象都最终派生于 java.util.EventObject 类。当然,每个事件类型还有子类,例如,ActionEvent 和 WindowEvent。
不同的事件源可以产生不同类别的事件。例如,按钮可以发送一个 ActionEvent 对象,而窗口可以发送 WindowEvent 对象。
为了深入了解事件委托模型,下面用一个相应按钮点击事件的简单实例来说明所需要知道的所有细节。在这个示例中,想要在一个面板中放置三个按钮,添加三个监听器对象用来作为按钮的动作监听器。
先上代码:
package button;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ButtonFrameTest {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new ButtonFrame();
frame.setTitle("ButtonFrameTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
class ButtonFrame extends JFrame {
private JPanel buttonPanel;
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 200;
public ButtonFrame() {
setSize(DEFAULT_WIDTH,DEFAULT_HEIGHT);
//create buttons
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("blue");
JButton redButton = new JButton("red");
buttonPanel = new JPanel();
buttonPanel.add(yellowButton);
buttonPanel.add(blueButton);
buttonPanel.add(redButton);
// add panel to frame
add(buttonPanel);
// create button actions
ColorAction yellowAction = new ColorAction(Color.YELLOW);
ColorAction blueAction = new ColorAction(Color.BLUE);
ColorAction redAction = new ColorAction(Color.RED);
//associste actions with buttons
yellowButton.addActionListener(yellowAction);
blueButton.addActionListener(blueAction);
redButton.addActionListener(redAction);
}
/*
* An action listener that sets the panel's background color.
*/
private class ColorAction implements ActionListener{
private Color backgroundColor;
public ColorAction(Color c) {
backgroundColor = c;
}
public void actionPerformed(ActionEvent event) {
buttonPanel.setBackground(backgroundColor);
}
}
}
当我点击按钮后会显示相应的颜色,结果如图:
接下来逐行解释下列代码:
在演示如何监听按钮点击事件之前,首先需要解释如何创建按钮以及如何将它们添加到面板中。
可以通过在按钮构造器中指定一个标签字符串、一个图标或两项都指定来创建一个按钮(具体细节先不考虑,以后会详细讨论)。下面是两个示例:
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton(new ImageIcon("blue-ball.gif"));
将按钮添加到面板中需要调用 add 方法:
//create buttons
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("blue");
JButton redButton = new JButton("red");
buttonPanel = new JPanel();
buttonPanel.add(yellowButton);
buttonPanel.add(blueButton);
buttonPanel.add(redButton);
// add panel to frame
add(buttonPanel);
接下来需要增加让面板监听这些按钮的代码。这需要一个实现了 ActionListener 接口的类。如前所述,应该包含一个 ActionPerformed 方法,其签名为:
public void actionPerformed(ActionEvent event)
当按钮被点击时,希望将面板的背景颜色设置为指定的颜色。这个颜色存储在监听器类中:
private class ColorAction implements ActionListener{
private Color backgroundColor;
public ColorAction(Color c) {
backgroundColor = c;
}
public void actionPerformed(ActionEvent event) {
buttonPanel.setBackground(backgroundColor);
}
}
然后,为每种颜色构造一个对象,并将这些对象设置为按钮监听器。
// create button actions
ColorAction yellowAction = new ColorAction(Color.YELLOW);
ColorAction blueAction = new ColorAction(Color.BLUE);
ColorAction redAction = new ColorAction(Color.RED);
//associste actions with buttons
yellowButton.addActionListener(yellowAction);
blueButton.addActionListener(blueAction);
redButton.addActionListener(redAction);
例如,如果一个用户在标有“Yellow”的按钮上点击了一下,yellowAction 对象的 actionPerformed 方法就会被调用。这个对象的 backgroundColor 实例域被设置为 Color.YELLOW,现在就将面板的背景颜色改为黄色了。
我们可以更简洁的指定监听器:
在上面这个例子中,我们为事件监听器定义了一个类并构造了这个类的 3 个对象。一个监听器类有多个实例的情况并不多见。更常见的情况是:每个监听器执行一个单独的动作。在这种情况下,没有必要分别建立单独的类。只需要使用一个 lambda 表达式:
exitButton.addActionListener(event -> System.exit(0));
现在考虑这样一种情况:有多个相互关联的动作,如上面彩色按钮的例子。在这种情况下,可以实现一个辅助方法:
public void makeButton(String name, Color backgroundColor){
JButton button = new JButton(name);
buttonPanel.add(button);
button.addActionListener(event ->
buttonPanel.setBackground(backgroundColor));
}
需要说明,lambda 表达式指示参数变量 backgroundColor。
然后只需调用:
makeButton("Yellow", Color.YELLOW);
makeButton("blue", Color.BLUE);
makeButton("red", Color.RED);
在这里,我们构造了 3 个监听器对象,分别对应一种颜色,但并没有显示定义一个类。每次调用这个辅助方法时,它会建立实现了 ActionListener 接口的一个类的实例。它的 actionPerformed 动作会引用实际上随监听器对象存储的 backGroudColor 值。不过,所有这些会自动完成,而无需显示定义监听器类、实例变量或这些变量的构造器。
接下来我们来探讨 适配器类
并不是所有的事件处理都像按钮点击那样简单。在正规的程序中,往往希望用户在确认没有丢失所做的工作之后再关闭程序。当用户关闭框架时,可能希望弹出一个对话框来警告用户没有保存的工作可能会丢失,只有在用户确认后才退出程序。
当程序用户试图关闭一个框架窗口时,JFrame 对象就是 WindowEvent 的事件源。如果希望捕获这个事件,就必须有一个合适的监听器对象,并将它添加到框架的窗口监听器列表中。
WindowListener listener = ...;
frame.addWindowListener(listener);
窗口监听器必须是实现 WindowListener 接口的对象。在 WindowListener 接口中包含 7 个方法。当发生窗口事件时,框架将调用这些方法响应 7 个不同的事件。
正像前面曾经说过的那样,在 Java 中,实现一个接口的任何类都必须实现其中的所有方法;在这里,意味着需要实现 7 个方法。然而我们只对名为 windowClosing 的方法感兴趣。
当然,可以这样定义实现这个接口的类:在 WindowClosing 方法中增加一个对 System.exit(0) 的调用,其他 6 个方法不做任何事情:
class Terminator implements WindowListener{
public void windowClosing(WindowEvent e){
if(...)
System.exit(0);
}
public void windowOpened(windowEvent e) {}
public void windowClosed(windowEvent e) {}
public void windowIconified(windowEvent e) {}
...
}
这样很麻烦,出于简化的目的,每个含有多个方法的 AWT 监听器接口都配有一个 适配器(adapter) 类,这个类实现了接口中所有方法,但每个方法没有做任何事情。这意味着适配器自动地满足了 Java 实现相关监听器接口的技术需求。可以通过扩展适配器类来指定对某些事件的相应动作,而不必实现接口中的每个方法。
我们先来定义一个 WindowAdapter 类的扩展类,其中包含继承的 6 个没有做任何事情的放法和一个覆盖的方法 windowClosing:
class Terminator extends WindowAdapter{
public void windowClosing(WindowEvent e){
if(...)
System.exit(0);
}
}
然后把一个 Terminator 对象注册为事件监听器:
WindowListener listener = new Terminator();
frame.addWindowListener(listener);
化简不要停!!! 启动超级变化形态(把监听器类定义为框架的匿名内部类):
frame.addWindowListener(new
windowAdapter(){
public void windowClosing(WindowEvent e){
if(...)
System.exit(0);
}
});
这段代码有以下作用:
- 定义了一个扩展于 WindowAdapter 类的无名类。
- 将 windowClosing 方法添加到匿名类中(与前面一样,这种方法将退出程序)。
- 从 WindowAdapter 继承 6 个没有做任何事的方法。
- 创建这个类的一个对象,这个对象没有名字。
- 将这个对象传递给 addWindowListener 方法。