第9章 AWT事件模型
本模块讨论了事件驱动的图形用户界面(GUI)的用户输入机制。
第一节 相关问题
讨论 - 以下为与本模块内容有关的问题:
- 哪些部分对于一个图形用户界面来说是必需的?
- 一个图形化程序如何处理鼠标点击或者其他类型的用户交互?
第二节 目 标
完成本模块之后,你应当能够:
- 编写代码来处理在图形用户界面中发生的事件
- 描述Adapter类的概念,包括如何和何时使用它们
- 根据事件对象的细节来确定产生事件的用户动作
- 为各种类型的事件创建合适的接口和事件处理器。
第三节 什么是事件?
什么是事件? - 事件-描述发生了什么的对象 - 事件源-事件的产生器 - 事件处理器-接收事件、解释事件并处理用户交互的方法 |
如果用户在用户界面层执行了一个动作(鼠标点击和按键),这将导致一个事件的发生。事件是描述发生了什么的对象。存在各种不同类型的事件类用来描述各种类型的用户交互。
9.3.1 事件源
事件源是一个事件的产生者。例如,在Button组件上点击鼠标会产生以这个Button 为源的一个ActionEvent. 这个ActionEvent实例是一个对象,它包含关于刚才所发生的那个事件的信息的对象。这些信息包括:
- getActionCommand-返回与动作相关联的命令名称。
- GetModifiers-返回在执行动作时持有的修饰符。
9.3.2 事件处理器
事件处理器就是一个接收事件、解释事件并处理用户交互的方法。
第四节 JDK1.0的事件模型与JDK1.2的事件模型比较
JDK1.0的事件模型与JDK1.2的事件模型比较 - 层次模型(JDK 1.0) - 委托模型(JDK 1.2)
|
在JDK1.1中,事件接收和处理的方法发生了重要的改变。本节将比较以前的事件模型(JDK1.0)和当前的事件模型(JDK1.1和JDK1.2)。
JDK1.0采用的是层次事件模型,而JDK1.1以及更高的版本采用的是委托事件模型。
9.4.1 层次模型(JDK1.0)
层次模型是基于容器的。 事件先发送到组件,然后沿容器层次向上传播。没有被组件处理的事件会自动地继续传播到组件的容器。
JDK1.0的事件模型与JDK1.2的事件模型比较
例如,在下图中,Button对象(包含在一个Frame上的Panel中)上的鼠标点击首先向Button发送一个动作事件。如果它没有被Button处理,这个事件会被送往Panel,如果它在那儿仍然没有被处理,这个事件会被送往Frame。
层次模型(JDK1.0) 优点 - 简单,而且非常适合面向对象的编程环境。 缺点 - 事件只能由产生这个事件的组件或包含这个组件的容器处理。 - 为了处理事件,你必须定义接收这个事件的组件的子类,或者在基容器创建handleEvent()方法。 |
层次模型(JDK1.0)(续)
这种模型有一个显著的优点:
- 它简单,而且非常适合面向对象的编程环境;说到底,所有的组件都继承了java.awt.Component类,而handleEvent()就在java.awt.Component类中。
然而,这种模型也存在缺点:
- 事件只能由产生这个事件的组件或包含这个组件的容器处理。这个限制违反了面向对象编程的一个基本原则:功能应该包含在最合适的类中。而最适合处理事件的类往往并不是源组件的容器层次中的成员。
- 大量的CPU周期浪费在处理不相关的事件上。任何对于程序来说不相关或者并不重要的事件会沿容器层次一路传播,直到最后被抛弃。不存在一种简单的方法来过滤事件。
- 为了处理事件,你必须定义接收这个事件的组件的子类,或者在基容器创建一个庞大的handleEvent()方法。
委托事件模型是在JDK1.1中引入的。在这个模型中,事件被送往产生这个事件的组件,然而,注册一个或多个称为监听者的类取决于每一个组件,这些类包含事件处理器,用来接收和处理这个事件。采用这种方法,事件处理器可以安排在与源组件分离的对象中。监听者就是实现了Listener接口的类。
事件是只向注册的监听者报告的对象。每个事件都有一个对应的监听者接口,规定哪些方法必须在适合接收那种类型的事件的类中定义。实现了定义那些方法的接口的类可以注册为一个监听者。
9.4.2 委托模型
从没有注册的监听者的组件中发出的事件不会被传播。
例如,这是一个只含有单个Button的简单Frame。
1.import java.awt.*;
2.
3.public class TestButton {
4.public static void main(String args[]) {
5.Frame f = new Frame("Test");
6.Button b = new Button("Press Me!");
7.b.addActionListener(new ButtonHandler());
8.f.add(b,"Center");
9.f.pack();
10.f.setVisible(true);
11.}
12.}
ButtonHandler类是一个处理器类,事件将被委托给这个类。
1.import java.awt.event.*;
2.
3.public class ButtonHandler implements ActionListener {
4.public void actionPerformed(ActionEvent e) {
5.System.out.println("Action occurred");
6.System.out.println("Button's label is : '
7.+ e.getActionCommand());
8.}
9.}
这两个范例的特征如下:
- Button类有一个addActionListner(ActionListener)方法。
- AddActionListner接口定义了一个方法actionPerformed,用来接收一个ActionEvent。
- 创建一个Button对象时,这个对象可以通过使用addActionListener方法注册为ActionEvents的监听者。调用这个方法时带有一个实现了ActionListener接口的类的参数。
委托模型(JDK1.1或更高版本) 优点 - 事件不会被意外地处理。 - 有可能创建并使用适配器 (adapter)类对事件动作进行分类。 - 委托模型有利于把工作分布到各个类中。 缺点 - 不容易将JDK1.0代码移植到JDK1.1上。 |
委托模型(JDK1.1或更高版本)(续)
- 在Button对象上用鼠标进行点击时,将发送一个ActionEvent事件。这个ActionEvent事件会被使用addActionListener()方法进行注册的所有ActionListener的actionPerformed()方法接收。
- ActionEvent类的getActionCommand()方法返回与动作相关联的命令名称。以按钮的点击动作为例,将返回Button的标签。
这种方法有若干优点:
- 事件不会被意外地处理。在层次模型中,一个事件可能传播到容器,并在非预期的层次被处理。
- 有可能创建并使用适配器(adapter)类对事件动作进行分类。
- 委托模型有利于把工作分布到各个类中。
- 新的事件模型提供对JavaBeans的支持。
这种方法也有一个缺点:
- 尽管当前的JDK支持委托模型和JDK1.0事件模型,但不能混合使用JDK1.0和JDK1.1。
第五节 图形用户界面的行为
9.5.1 事件类型
我们已经介绍了在单一类型事件上下文中从组件接收事件的通用机制。事件类的层次结构图如下所示。许多事件类在java.awt.event包中,也有一些事件类在API的其他地方。
对于每类事件,都有一个接口,这个接口必须由想接收这个事件的类的对象实现。这个接口还要求定义一个或多个方法。当发生特定的事件时,就会调用这些方法。表9-1列出了这些(事件)类型,并给出了每个类型对应的接口名称,以及所要求定义的方法。这些方法的名称是易于记忆的,名称表示了会引起这个方法被调用的源或条件。
表9-1方法类型和接口
Category | Interface Name | Methods |
Action | ActionListener | actionPerformed(ActionEvent) |
Item | ItemListener | itemStateChanged(ItemEvent) |
Mouse motion | MouseMotionListener | mouseDragged(MouseEvent) |
|
| mouseMoved(MouseEvent) |
Mouse button | MouseListener | mousePressed(MouseEvent) |
|
| mouseReleased(MouseEvent) |
|
| mouseEntered(MouseEvent) |
|
| mouseExited(MouseEvent) |
|
| mouseClicked(MouseEvent) |
Key | KeyListener | keyPressed(KeyEvent) |
|
| keyReleased(KeyEvent) |
|
| keyTyped(KeyEvent) |
Focus | FocusListener | focusGained(FocusEvent) |
|
| focusLost(FocusEvent) |
Adjustment | AdjustmentListener | adjustmentValueChanged (AdjustmentEvent) |
Component | ComponentListener | componentMoved(ComponentEvent) |
|
| componentHidden(ComponentEvent) |
|
| componentResized(ComponentEvent) |
|
| componentShown(ComponentEvent) |
表9-1方法类型和接口(续)
9.5.2 复杂的范例
本节将考察一个更复杂的Java软件范例。它将跟踪鼠标被按下时,鼠标的移动情况(鼠标拖动)。这个范例还将监测当鼠标没有按下时,鼠标的移动情况(鼠标移动)。
当鼠标按下或没有按下时,移动鼠标产生的事件会被实现了MouseMotionListener接口的类的对象检取。这个接口要求定义两个方法,mouseDragged()和mouseMoved()。即使你只对鼠标拖动感兴趣,也必须提供这两个方法。然而,mouseMoved()的体可以是空的。
要检取其他鼠标事件,包括鼠标点击,必须定义MouseListener接口。这个接口包括若干个事件,即:mouseEntered, mouseExited, mousePressed, mouseReleased和 mouseClicked。
发生鼠标或键盘事件时,有关鼠标的位置和所按下的键的信息可以从事件中得到。代码如下:
1.import java.awt.*;
2.import java.awt.event.*;
3.
4.public class TwoListen implements
5.MouseMotionListener, MouseListener {
6.private Frame f;
7.private TextField tf;
8.
9.public static void main(String args[]) {
10.TwoListen two = new TwoListen();
11.two.go();
12.}
13.
复杂的范例(续)
1.public void go() {
2.f = new Frame("Two listeners example");
3.f.add (new Label ("Click and drag the mouse"),
4."BorderLayout.NORTH");
5.tf = new TextField (30);
6.f.add (tf, "BorderLayout.SOUTH");
7.
8.f.addMouseMotionListener(this);
9.f.addMouseListener (this);
10.f.setSize(300, 200);
11.f.setVisible(true);
12.}
13.
14.// These are MouseMotionListener events
15.public void mouseDragged (MouseEvent e) {
16.String s =
17."Mouse dragging: X = " + e.getX() +
18." Y = " + e.getY();
19.tf.setText (s);
20.}
21.
22.public void mouseMoved (MouseEvent e) {
23.}
24.
25.// These are MouseListener events
26.public void mouseClicked (MouseEvent e) {
27.}
28.
29.public void mouseEntered (MouseEvent e) {
30.String s = "The mouse entered";
31.tf.setText (s);
32.}
复杂的范例(续)
1.public void mouseExited (MouseEvent e) {
2.String s = "The mouse has left the building";
3.tf.setText (s);
4.}
5.
6.public void mousePressed (MouseEvent e) {
7.}
8.
9.public void mouseReleased (MouseEvent e) {
10.}
11.}
这个范例中的一些地方值得注意。它们将在以下的几节中讨论。
定义多重接口
这个类由第4行中的如下代码声明:
implements MouseMotionListener, MouseListener
声明多个接口时,可以用逗号隔开。
监听多个源
如果你在第11行和第12行中,调用如下方法:
两种类型的事件都会引起TwoListen类中的方法被调用。一个对象可以“监听”任意数量的事件源;它的类只需要实现所要求的接口。
f.addMouseListener(this);
f.addMouseMotionListener(this);
复杂的范例(续)
获取关于事件的细节
调用处理器方法(如mouseDragged())时所带的事件参数可能会包含关于源事件的重要信息。为了确定可以获取关于某类事件的哪些细节时,你应当查看java.awt.event包中合适的类文档。
9.5.3 多监听者
多监听者 - 多监听者可以使一个程序的不相关部分执行同样的动作。 - 事件发生时,所有被注册的监听者的处理器都会被调用 |
AWT事件监听框架事实上允许同一个组件带有多个监听者。一般地,如果你想写一个程序,它基于一个事件而执行多个动作,把那些行为编写到处理器的方法里即可。然而,有时一个程序的设计要求同一程序的多个不相关的部分对于同一事件作出反应。这种情况是有可能的,例如,将一个上下文敏感的帮助系统加到一个已存在的程序中。
监听者机制允许你调用addXXXlistener方法任意多次,而且,你可以根据你的设计需要指定任意多个不同的监听者。事件发生时,所有被注册的监听者的处理器都会被调用。
注-没有定义调用事件处理器方法的顺序。通常,如果调用的顺序起作用,那么处理器就不是不相关的。在这种情况下,只注册第一个监听者,并由它直接调用其它的监听者。
9.5.4 事件Adapters(适配器)
你定义的Listener可以继承Adapter类,而且只需重写你所需要的方法。例如:
实现每个Listener接口的所有方法的工作量是非常大的,尤其是MouseListener接口和ComponentListener接口。
以MouseListener接口为例,它定义了如下方法:
- mouseClicked(MouseEvent)
- mouseEntered(MouseEvent)
- mouseExited(MouseEvent)
- mousePressed(MouseEvent)
- mouseReleased(MouseEvent)
为了方便起见,Java语言提供了Adapters类,用来实现含有多个方法的类。这些Adapters类中的方法是空的。
你可以继承Adapters类,而且只需重写你所需要的方法。例如:
1.import java.awt.*;
2.import java.awt.event.*;
3.
4.public class MouseClickHandler extends MouseAdapter {
5.
6.// We just need the mouseClick handler, so we use
7.// the Adapter to avoid having to write all the
8.// event handler methods
9.public void mouseClicked (MouseEvent e) {
10.// Do something with the mouse click...
11.}
12.}
9.5.5 匿名类
可以在一个表达式的域中,包含整个类的定义。这种方法定义了一个所谓的匿名类并且立即创建了实例。匿名类通常和AWT事件处理一起使用。例如:
1.import java.awt.*;
2.import java.awt.event.*;
3.public class AnonTest {
4.private Frame f;
5.private TextField tf;
6.
7.public static void main(String args[])
8.{
9.AnonTest obj = new AnonTest();
10.obj.go();
11.}
12.
匿名类(续)
1.public void go() {
2.f = new Frame("Anonymous classes example");
3.f.add(new Label("Click and drag the " +
4." mouse",BorderLayout.NORTH);
5.tf = new TextField (30);
6.f.add(tf,BorderLayout.SOUTH);
7.
8.f.addMouseMotionListener ( new
9.MouseMotionAdapter() {
10.public void mouseDragged (MouseEvent e) {
11.String s =
12."Mouse dragging: X = " + e.getX() +
13." Y = " + e.getY();
14.tf.setText (s);
15.}
16.} ); // <- note the closing parenthesis
17.
18.f.addMouseListener (new MouseClickHandler());
19.f.setSize(300, 200);
20.f.setVisible(true);
21.}
22.}
练习:事件
练习目标-你将编写、编译和运行包含事件处理器的Calculator 图形用户界面 和Account 图形用户界面的修改版本。
一、准备
为了很好地完成这个练习,你必须对事件模型是如何工作的有一个清晰的了解。
二、任务
水平1:创建一个Calculator 图形用户界面,第二部分
1. 使用你在模块8中创建的图形用户界面代码,编写一段事件代码,用来连接计算器的用户界面和处理计算器上的函数的事件处理器。
水平3:创建一个Account 图形用户界面,第二部分
1. 创建一个AccountEvent类,类的对象在帐目发生改变时被激活。然后激活送往Bankmanager类的事件。根据你在模块8中创建的Teller.java GUI代码为起点进行练习。
三、练习小结
讨论 - 花几分钟时间讨论一下,在本实验练习过程中你都经历、提出和发现了什么。
经验 解释 总结 应用
四、检查一下你的进度
在进入下一个模块的学习之前,请确认你能够:
- 编写代码来处理在图形用户界面中发生的事件
- 描述Adapter类的概念,包括如何和何时使用它们
- 根据事件对象的细节来确定产生事件的用户动作
- 为各种类型的事件创建合适的接口和事件处理器。
五、思考
你现在已经知道如何为图形化输出和交互式用户输入来创建Java图形用户界面。然而,我们只介绍了能创建图形用户界面的一些组件。其他的组件在图形用户界面中有什么用处呢?