Java程序设计11——GUI设计与事件处理B

4 Java事件模型的流程

  为了使图形界面能够接收用户的操作,必须给各个组件加上事件处理机制。
  在事件处理的过程中,主要涉及3类对象:
  1.Event Source(事件源):事件发生的场所,通常就是各个组件,例如按钮、窗口、菜单等。
  2.Event(事件):事件封装了GUI组件上发生的特定时期(通常就是一次用户操作)。如果程序需要获得GUI组件上所发生事件的相关信息,都通过Event对象来取得
  3.EventListener(事件监听器):负责监听事件源所发生的时间,并对各种事件做出响应处理

  事件响应的动作实际上就是一系列的程序语句,通常以方法的形式组织起来。但Java是面向对象的编程语言,方法不能独立存在,所以必须以类的形式来组织这些方法。因此事件监听器的核心就是它所包含的方法——这些方法也被称为事件处理器(Event Handler).当事件源上的事件发生时,事件对象会作为参数传给事件处理器(即事件监听器的实例方法)
  当用户按下一个按钮,或者单击某个菜单项,或单击窗口右上角的状态按钮时,这些动作就会激发一个相应的事件,这个事件由AWT封装成一个相应的Event对象,该事件就会触发事件源上注册的事件监听器(特殊的Java对象),事件监听器就会调用对应的事件处理器来响应。

  AWT的事件处理机制是一种委派式事件处理方式:普通组件(事件源)将整个事件处理委托给特定的对象(事件监听器);当该事件源发生指定的事件时,就通知所委托的事件监听器,由事件监听器来处理这个事件。

  每个组件均可以针对特定的事件指定一个或多个事件监听对象,每个事件监听器也可以监听一个或多个事件源。因为同一个事件源上可能发生多个事件源。因为同一个事件源上可能发生多种事件,委派式事件处理方式可以把事件源上所有可能发生的事件分别授权给不同的事件监听器来处理;同时也可以让一类事件都使用同一个事件监听器来处理。
  下面是AWT事件处理流程示意图:

Java程序设计11——GUI设计与事件处理B

package chapter11;

import java.awt.*;
import java.awt.event.*; public class EventQs {
private Frame f = new Frame("测试事件");
private Button ok = new Button("确定");
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{
//下面定义的方法是事件处理器,用于处理特定的事件
public void actionPerformed(ActionEvent e){
System.out.println("用户单击了OK按钮");
tf.setText("Hello World");
}
}
public static void main(String[] args){
new EventQs().init();
}
};

  上面的代码用于注册事件监听器,定义的方法就是事件处理器,当程序中的OK按钮被单击时,该处理器被触发,将看到程序中tf文本框变为Hello World,而程序控制台打印出"用户单击了OK按钮"字符串。
从上面程序可以看出,实现AWT事件处理机制的步骤如下:
1. 实现事件监听器类,该监听器类是一个特殊的Java类,必须实现一个XxxListener接口
2. 创建普通组件(事件源),创建事件监听器对象
3. 调用addXxxListener方法将事件监听器对象注册给普通组件(事件源)。这样当事件源发生指定事件时,AWT会触发事件监听器,由事件监听器调用相应的方法(事件处理器)来处理事件,事件源上所发生的事件会作为参数传入事件处理器。

5 事件和事件监听器

  当外部动作在AWT组件上进行操作时,系统会自动生成事件对象,这个事件对象是EventObject子类的实例,该事件对象会自动触发注册到事件源上的事件监听器。
  AWT事件机制涉及到三个成员:事件源、事件和事件监听器,其中事件源最容易创建,只要通过new来创建一个AWT组件,该组件就是事件源,事件的产生无须程序员关心,它是由虚拟机自动产生的,所以,实现事件监听器是整个事件处理的核心。
  事件监听器必须实现事件监听器接口,AWT提供了大量的事件监听器接口用于实现不同类型的事件监听器,用于监听不同类型的事件。AWT中提供了丰富的事件类,用于封装不同组件上所发生的特定操作:AWT的事件类都是AWTEvent类的子类,它是EventObject类的子类。
EventObject类代表更广义的事件对象,包括Swing组件上所引发事件、数据库连接所引发的事情等。
AWT事件分为两大类:低级事件和高级事件。

5.1 低级事件

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

5.2 高级事件(语义事件)

  高级事件是基于语义的事件,它可以不和特定的动作相关联,而依赖于触发此事件的类。比如,在TextField中按Enter键会触发ActionEvent事件,滑动滚动条会触发AdjustmentEvent事件,选中项目列表的某一条就会触发ItemEvent事件。
ActionEvent:动作事件,当按钮、菜单项被单机,TextField中按Enter键时触发
AdjustmentEvent:调节事件,在滑动条上移动滑块以调节数值时触发
ItemEvent:选项事件,当用户选中某项,或取消选中某项时触发
TextEvent:文本事件,当文本框、文本域里的文本发生改变时触发。

AWT中事件继承层次如图所示:

Java程序设计11——GUI设计与事件处理B

  上图常用的AWT事件使用粗线框框出,对于系统没有用粗线框框出的事件,程序员很少使用它们,它们可能被作为事件基类或作为系统内部实现来使用。
不同事件需要使用不同的监听器监听,不同的监听器需要实现不同的监听器接口,当指定事件发生后,事件监听器就会调用所包含的事件处理器(实例方法)来处理事件。下表显示了常用事件、监听器接口和处理器之间的关系。

Java程序设计11——GUI设计与事件处理B

Java程序设计11——GUI设计与事件处理B

  通过上表,可以大致知道常用组件可能发生哪些事件,以及该事件对应的监听器接口,通过实现该监听器接口就可以实现对应的事件处理器,然后通过addXxxListener方法来将事件监听器注册给指定的组件(事件源)。当事件源组件上发生特定事件时,被注册到该组件的事件监听器里的对应方法(事件处理器)将被触发。
  实际上可以把事件处理模型简化成如下理解:当事件源组件发生事件时,系统将会执行该事件源组件的所有监听器里的对应方法。与前面编程方式不同的是:普通Java程序里的方法由程序主动调用,事件处理中的事件处理器方法由系统负责调用。
  下面程序示范了一个监听器监听多个组件,一个组件被多个监听器监听的效果:


  上面程序详细监听了窗口的每个动作,当用户单击窗口右上角的每个按钮时,程序作出相应的响应,当用户单击窗口的X按钮时,程序将正常退出。

  大部分时候,程序无须监听窗口的每个动作,程序只需要为用户单击窗口的X按钮提供响应,无须为每个窗口事件提供响应——即程序只想重写windowClosing事件处理器,但因为该监听器实现了WindowListener接口,实现该接口就不得不实现该接口里的每个抽象方法,这是非常繁琐的事情。为此,AWT提供了事件适配器。

5.3 事件适配器

  事件适配器是监听器接口的空实现:事件适配器实现了监听器接口,并为该接口里每个方法都提供了实现,这种实现是一种空实现(方法体内没有任何代码的实现)。当需要创建监听器时,可以通过继承事件适配器,而不是实现监听器接口,因为事件适配器已经为监听器接口的每个方法提供了空实现,所以程序自己的监听器无须实现监听器接口里的每个方法,只需要重写自己感兴趣的方法,从而可以简化事件监听器的实现类代码。
  如果某个监听器接口只有一个方法,则该监听器接口就无须提供适配器,因为该接口对应的监听器别无选择,只能重写该方法。如果不重写该方法,就没有必要实现该监听器。

Java程序设计11——GUI设计与事件处理B

  从上表可以看出,所有包含多个方法的监听器接口都有一个对应的适配器,但只包含一个方法的监听器接口没有对应的适配器。
下面通过事件适配器来创建事件监听器,程序如下:

上面程序只重写了windowClosing方法,其他方法没有重写。

5.4 事件监听器的实现形式

  事件监听器是一个特殊的Java对象,实现事件监听器对象有如下几种形式:
1.内部类形式:将事件监听器类定义成当前类的内部类
2.*类形式:将事件监听器定义成一个*类
3.类本身作为事件监听器类:让当前类本身实现监听器接口或继承事件适配器。

4.匿名内部类形式:使用匿名内部类创建事件监听器对象
内部类形式
  前面示例程序中的所有事件监听器类都是内部类形式,使用内部类可以在当前类中复用该监听器类。因为监听器类是外部类的内部类,所以可以*访问外部类的所有GUI组件,这也是内部类的两个优势。
  使用内部类定义事件监听器类的例子可以参看前面的例子程序,此处不再赘述。
*类形式
  使用*类定义事件监听器类的形式比较少见,主要有如下两个原因:
1. 事件监听器通常属于特定的GUI界面,定义成*类不利于提高程序的内聚性。
2. *类形式的事件监听器不能*访问创建GUI界面的类中的组件,编程不够简洁。
  但如果某个事件监听器确实需要被多个GUI界面所共享,而且主要是完成某种业务逻辑的实现,则可以考虑使用*类的形式来定义事件监听器类。下面程序定义了一个*类作为事件监听器类,该事件监听器类实现了发送邮件的功能:

  上面监听器事件类没有与任何GUI界面耦合,创建该监听器对象时传入一个TextField对象,该文本框里的字符串被作为收件人地址。下面程序使用了该事件监听器来监听窗口中的按钮:

匿名内部类形式
  大部分时候,事件处理器都没有什么复用价值(可复用代码通常都被抽象成了业务逻辑方法),因此大部分事件监听器只是临时使用一次,所以使用匿名内部类形式的事件监听器更合适。实际上,这种形式是目前使用最广泛的事件监听器形式。下面程序使用匿名内部类来创建事件监听器:

6 AWT的菜单

  前面介绍了创建GUI界面的方式:将AWT组件按某种布局摆放在容器内即可。创建AWT菜单的方式与此完全类似:将菜单条、菜单、菜单项组合放在一起即可。
菜单条、菜单和菜单项
AWT中的菜单由如下几个类组合而成:
MenuBar:菜单条,菜单的容器
Menu:菜单组件,菜单项的容器
PopupMenu:上下文菜单组件(右键菜单组件)
MenuItem:菜单组件
CheckboxMenuItem:复选框菜单项组件
MenuShortcut:菜单快捷键组件

  AWT菜单组件类之间的继承、组合关系,从下图可以看出MenuBar和Menu都实现了菜单容器接口,所以MenuBar可用于盛装Menu,而Menu可用于盛装MenuItem(包括Menu和CheckMenuItem两个子类对象),Menu还有一个子类:PopupMenu代表上下文菜单,上下文菜单无须使用MenuBar盛装。

Java程序设计11——GUI设计与事件处理B

  Menu、MenuItem的构造器都可接受一个字符串参数,该字符串作为其对应菜单、菜单项上的标签文本。除此之外,MenuItem还可以接受一个MenuShortcut对象,该对象用于指定该菜单的快捷键。MenuShortcut类使用虚拟键代码(而不是字符)来创建快捷键。例如,Ctrl-A(通常都以Ctrl键作为快捷键的辅助键)快捷方式通过以下的代码创建:
MenuShortcut ms = new MenuShortcut(KeyEvent.VK_A);
  如果该快捷键还需要Shift键的辅助,则可使用如下代码:
MenuShortcut ms = new MenuShortcut(keyEvent.VK_A, true);
  有时候我们还希望将某个菜单进行分组,将功能相似的菜单分成一组,此时需要使用菜单分隔符,AWT中添加菜单分隔符有两种方法:
调用Menu对象的addSeparator方法来添加菜单分割线
添加new MenuItem("-")菜单项来添加菜单分隔线。
创建了MenuItem、Menu和MenuBar对象之后,然后调用Menu的add方法将多个MenuItem组合成菜单(也可将另一个Menu对象组合进来,从而形成二级菜单),再调用MenuBar的add方法将多个Menu组合成菜单条,最后调用Frame对象的setMenuBar为该窗口添加菜单条。

右键菜单
  右键菜单使用PopupMenu对象表示,创建右键菜单的步骤如下:
1. 创建PopupMenu的实例
2. 创建多个MenuItem的多个实例,依次将这些实例加入PopupMenu中
3. 将PopupMenu加入到目标组件之中
4. 为需要出现上下文菜单的组件编写鼠标监听器,当用户释放鼠标右键时弹出右键菜单
  下面程序创建了一个右键菜单,该右键菜单就是"借用"前面SimpleMenu中edit菜单中的所有菜单项,程序如下:

上一篇:java web 学习 --第一天(Java三级考试)


下一篇:HDU5937 Equation(DFS + 剪枝)