本文讲述GUI中的事件处理。

模型及流程

 为了使图形界面能够接收用户的操作,必须给各个组件加上事件处理机制。在事件处理的过程中,主要涉及三类对象。

  • Event Source (事件源):事件发生的场所,通常就是各个组件,例如按钮、窗口、菜单等。
  • Event (事件):事件封装了GUI组件上发生的特定事情(通常就是一次用户操作)。如果程序需 要获得GUI组件上所发生事件的相关信息,都通过Event对象来取得。
  • Event Listener(事件监听器):负责监听事件源所发生的事件,并对各种事件做出响应处理。

 当用户单击一个按钮,或者单击某个菜单项,或者单击窗口右上角的状态按钮时,这些动作就会触发一个相应的事件,该事件由AWT封装成相应的Event对象,该事件会触发事件源上注册的事件监听器(特殊的Java对象),事件监听器调用对应的事件处理器(事件监听器里的实例方法)来做出相应的响应。 AWT的事件处理机制是一种委派式(Delegation)事件处理方式——普通组件(事件源)将事件的处理工作委托给特定的对象(事件监听器);当该事件源发生指定的事件时,就通知所委托的事件监听器,由事件监听器来处理这个事件。每个组件均可以针对特定的事件指定一个或多个事件监听对象,每个事件监听器也可以监听一个或多个事件源。因为同一个事件源上可能发生多种事件,委派式事件处理方式可以把事件源上可能发生的不同的事件分别授权给不同的事件监听器来处理;同时也可以让一类事件都使用同一个事件监听器来处理。

流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
* @author YL
* @date 2018/7/21 14:53
*/
public class EventQs {
private Frame f = new Frame("事件测试");
private JButton ok = new JButton("确定");
private TextField tf = new TextField(30);

public void init() {
ok.addActionListener(new OkListener());
f.add(tf);
f.add(ok, BorderLayout.SOUTH);
f.pack();
f.setVisible(true);
}

class OkListener implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
System.out.println("你单击了OK按钮");
tf.setText("Hello");
}
}

public static void main(String[] args) {
new EventQs().init();
}
}

 上面的代码中JButton类组件OK按钮就是事件源,对于这个组件,用ok.addActionListener(new OkListener());注册事件监听器,然后实现了一个ActionListener接口,即定义了具体的事件监听器类。这个接口实际是一个函数式接口,因此可以使用Lambda表达式。

 从上面程序中可以看出,实现AWT事件处理机制的步骤如下:
1 实现事件监听器类,该监听器类是一个特殊的Java类 ,必须实现一个XxxListener接口。

2 创建普通组件(事件源),创建事件监听器对象。

3 调用addXxxListener()方法将事件监听器对象注册给普通组件(事件源)。 当事件源上发生指定事件时,AWT会触发事件监听器,由事件监听器调用相应的方法(事件处理器)来处理事件,事件源上所发生的事件会作为参数传入事件处理器。

事件和事件监听器

 AWT事件机制涉及三个成员:事件源、事件和事件监听器,其中事件源最容易创建,只要通过new来创建一个AWT组件,该组件就是事件源;事件是由系统自动产生的,无须程序员关心。所以,实现事件监听器是整个事件处理的核心。
事件监听器必须实现事件监听器接口,AWT提供了大量的事件监听器接口用于实现不同类型的事件监听器,用于监听不同类型的事件。AWT中提供了丰富的事件类,用于封装不同组件上所发生的特定操作—— AWT的事件类都是AWTEvent类的子类,AWTEvent是 EventObject的子类。

 AWT事件分为两大类:低级事件和高级事件。

 1.低级事件
 低级事件是指基于特定动作的事件。比如进入、点击、拖放等动作的鼠标事件,当组件得到焦点、失去焦点时触发焦点事件。

  • ComponentEvent:组件事件,当组件尺寸发生变化、位置发生移动、显示/隐藏状态发生改变时触发该事件。
  • ContainerEvent:容器事件,当容器里发生添加组件、删除组件时触发该事件。
  • WindowEvent:窗口事件,当窗口状态发生改变(如打开、关闭、最大化、最小化)时触发该 事件。
  • FocusEvent:焦点事件,当组件得到焦点或失去焦点时触发该事件。
  • KeyEvent:键盘事件,当按键被按下、松开、单击时触发该事件。
  • MouseEvent:鼠标事件,当进行单击、按下、松开、移动鼠标等动作时触发该事件。
  • PaintEvent:组件绘制事件,该事件是一个特殊的事件类型,当GUI组件调用叩date/paint方法 来呈现自身时触发该事件,该事件并非专用于事件处理模型。

 2.高级事件(语义事件)
 高级事件是基于语义的事件,它可以不和特定的动作相关联,而依赖于触发此事件的类。比如,在TextField中按Enter键会触发ActionEvent事件,在滑动 条上移动滑块会触发AdjustmentEvent事件,选中项目列 表的某一项就会触ItemEvent事件。

  • ActionEvent:动作事件,当按钮、菜单项被单击, 在 TextField中按Enter键时触发该事件。
  • AdjustmentEvent:调节事件,在滑动条上移动滑 块以调节数值时触发该事件。
  • ItemEvent:选项事件,当用户选中某项,或取消 选中某项时触发该事件。
  • TextEvent:文本事件,当文本框、文本域里的文 本发生改变时触发该事件。

事件继承

 不同的事件需要使用不同的监听器监听,不同的监听器需要实现不同的监听器接口,当指定事件发
生后,事件监听器就会调用所包含的事件处理器(实例方法)来处理事件。下表显示了事件、监听
器接口和处理器之间的对应关系。

对应关系

 下面是一个监听器监听多个组件,一个组件被多个监听器监听的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
* @author YL
* @date 2018/7/23 17:30
*/
public class MultiListener {
private Frame f = new Frame("测试");
private TextArea ta = new TextArea(6, 40);
private Button b1 = new Button("按钮1");
private Button b2 = new Button("按钮2");

public void init() {
FirstListener f1 = new FirstListener();
b1.addActionListener(f1);
b1.addActionListener(new SecondListener());
b2.addActionListener(f1);
f.add(ta);
Panel p = new Panel();
p.add(b1);
p.add(b2);
f.add(p, BorderLayout.SOUTH);
f.pack();
f.setVisible(true);
}

class FirstListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
// getActionCommand用于获得按钮上的名字,方便区分多个按钮,返回字符串,类似的getSource返回对象
ta.append("第一个事件监听器被触发,事件源是:"
+ e.getActionCommand() + "\n");
}
}

class SecondListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
ta.append("单击了" + e.getActionCommand() + "\n");
}
}

public static void main(String[] args) {
new MultiListener().init();
}
}

 注意这里使用JButton和Button的效果不一样,两个监听器触发的顺序不一样。

 下面是关闭窗口的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import java.awt.*;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

/**
* @author YL
* @date 2018/7/24 17:47
*/
public class WindowListenerTest {
private Frame f = new Frame("测试");
private TextArea ta = new TextArea(6, 40);

public void init() {
f.addWindowListener(new MyListener());
f.add(ta);
f.pack();
f.setVisible(true);
}

class MyListener implements WindowListener {
@Override
public void windowOpened(WindowEvent e) {
ta.append("窗口被初次打开\n");
}

@Override
public void windowClosing(WindowEvent e) {
ta.append("窗口正在被关闭\n");
System.exit(0);
}

@Override
public void windowClosed(WindowEvent e) {
ta.append("窗口关闭成功\n");
}

@Override
public void windowIconified(WindowEvent e) {
ta.append("窗口被最小化\n");
}

@Override
public void windowDeiconified(WindowEvent e) {
ta.append("窗口被恢复\n");
}

@Override
public void windowActivated(WindowEvent e) {
ta.append("窗口被激活\n");
}

@Override
public void windowDeactivated(WindowEvent e) {
ta.append("窗口失去焦点\n");
}
}

public static void main(String[] args) {
new WindowListenerTest().init();
}
}

事件适配器

 类似上面的代码中,当我们只需要关闭窗口的事件监听时,其他的监听器就是多余的,因此需要用到事件适配器。思路是,写一个类实现接口中的方法,但是方法体为空,当需要某个监听器时,继承改适配器,将某个监听器的方法体补全。

 下面列出了常用的监听器接口的适配器:

适配器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
* @author YL
* @date 2018/7/25 23:28
*/
public class WindowAdapterTest {
private Frame f = new Frame("测试");
private TextArea ta = new TextArea(6, 10);

public void init() {
f.addWindowListener(new Mylistener());
f.add(ta);
f.pack();
f.setVisible(true);
}

class Mylistener extends WindowAdapter {
@Override
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
System.out.println("用户关闭窗口\n");
System.exit(0);
}
}

public static void main(String[] args) {
new WindowListenerTest().init();
}
}

使用内部类实现监听器

 在MultiListener的代码中,在一个外部类中包含了两个内部类,内部类方便复用,可以自由访问外部类的GUI组件,这是优点。

使用外部类实现监听器

 事件监听器通常是属于特定组件的,而且外部类不能自由访问GUI组件,所以外部类的形式用的不多。强烈不建议使用这种方式。

类本身作为事件监听器类

 感觉是一种很变扭的写法,让GUI类继承事件适配器,然后直接在改类里写监听器,结构混乱了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
* @author YL
* @date 2018/7/26 0:09
*/
public class SimpleEventHandler extends WindowAdapter {
private Frame f = new Frame("测试");
private TextArea ta = new TextArea(6, 40);

public void init() {
f.addWindowListener(this);
f.add(ta);
f.pack();
f.setVisible(true);
}

@Override
public void windowClosing(WindowEvent e) {
System.out.println("用户关闭窗口\n");
System.exit(0);
}

public static void main(String[] args) {
new SimpleEventHandler().init();
}
}

匿名内部类实现监听器

 最广泛的运用形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
* @author YL
* @date 2018/7/26 0:20
*/
public class AnonymousEventHandler {
private Frame f = new Frame("测试");
private TextArea ta = new TextArea(6, 10);

public void init() {
f.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.out.println("用户试图关闭窗口\n");
System.exit(0);
}
});
f.add(ta);
f.pack();
f.setVisible(true);
}

public static void main(String[] args) {
new AnonymousEventHandler().init();
}
}