对于GUI的应用程序来说,事件处理是必不可少的,因此我们需要熟练地掌握事件处理模型。对于事件我们需要了解两个名词:事件源对象与监听器对象。从字面上我们就可以理解个大概,下面我们系统说明一下:
首先我们将监听器对象注册给事件源对象,这样当事件触发时系统便可以通过事件源访问相应的监听器。如下图:
当事件源触发事件后,系统便将事件的相关信息封装成相应类型的事件对象,并将其发送给注册到事件源的相应监听器。如下图:
当事件对象发送给监听器后,系统调用监听器的相应事件处理方法对事件进行处理,也就是做出响应。如下图:
注意:监听器与事件源之间是“多对多”的关系。
在事件对象中最高层是java.util.EventObject,所有事件状态对象都将从其派生的根类。这个类除了从Object类中继承下来的类之外还有一个就是getSource() 方法,其功能就是返回最初发生 Event 的对象。
除了这个类是在util包中,其它都在java.awt、java.awt.event包或java.swing、java.swing.event包中,值得注意的是并不是说Swing控件只使用Swing事件。
AWTEvent类提供了getID() 方法返回事件本性的标识符。例如,如果鼠标事件发生,能够查出是单击、拖拉、按、还是其他操作。
常用的几个事件类的说明:
监听器对象就是一个实现了特定监听器接口的类的实例,那么监听器接口就是我们所关心的问题了。在监听器接口的最顶层接口是java.util.EventListener,这个接口是所有事件侦听器接口必须扩展的标记接口。感到诧异的是这个接口完全是空的,里面没有任何的抽象方法的定义,查看源代码里面空空如也啊!
事件监听器的接口命名方式为:XXListener,而且,在java中,这些接口已经被定义好了。用来被实现,它定义了事件处理器(即事件处理的方法原型,这个方法需要被重新实现)。
例如:ActionListener接口、MouseListener接口、WindowListener接口、KeyListener接口、ItemListener接口、MouseMotionListener接口、FocusListener接口、ComponentListener接口等
事件最初由事件源产生,事件源可以是GUI组件Java Bean或由生成事件能力的对象,在GUI组件情况下,事件源或者是组件的同位体(对于Abstract Window Toolkit[awt]GUI组件来说)或组件本身(对于Swing组件来说)。
在java中,每一个组件会产生什么样的事件,已经被定义好了。或者说,对于任何一个事件来说,哪些组件可以产生它,已经是确定的了。
AWT将事件分为低级事件和语义事件。
java.awt.event包中常见的语义事件类:
常用的5个低级事件类:
java.awt.event包中定义的常用事件适配器类包括以下几个:
1.ComponentAdapter( 组件适配器)
2.ContainerAdapter( 容器适配器)
3.FocusAdapter( 焦点适配器)
4.KeyAdapter( 键盘适配器)
5.MouseAdapter( 鼠标适配器)
6.MouseMotionAdapter( 鼠标运动适配器)
7.WindowAdapter( 窗口适配器)
下图是Java实际开发中的对应关系图:
常见的事件对象与监听器对照表:
监听器接口 | 事件源 |
---|---|
ActionListener | AbstractButton JcomboBox JTextField Timer |
AdjustmentListener | JscrollBar |
ItemListener | AbstractButton JComboBox |
FocusListener | Component |
KeyListener | Component |
MouseListener | Component |
MouseMotionListener | Component |
MouseWheelListener | Component |
WindowListener | Window |
WindowFocusListener | Window |
WindowStateListener | Window |
建议:对这一部分了解需要先了解Java可视化组件与容器之间的大致关系,这样方便我们的理解。
作为一个程序开发者,我们所要做的是创建事件监听器对象并且在被激活(new)事件的组件中进行注册。所谓的创建事件监听器对象,就是创建一个类,而这个类必须实现形如XXListener的接口(或者继承”已经实现了XXListener的类”),当然,实现这个接口就意味着重写XXListener的方法。
例如,对于ActionListener, 只有一个actionPerformed方法:
class B1 implements ActionListener{// 实现ActionListener
@override
public void actionPerformed(ActionEvent e){
//重写actionPerformed
//do somthing....
}
}
在被激活事件的组件中注册事件监听器: 即调用形如addXXListener()的方法。
例如:
Button b1 = new Button("Button 1");
b1.addActionListener(new B1()); //注册事件监听器
b1就是被激活事件的组件这样一来,当事件被激活时,处理流程如下:由于已经通过addActionListener进行了事件监听器的注册,所以,就会调用到特定的事件处理方法,即actionPerformed()函数。这样,执行的结果就要看actionPerformed是具体做什么工作了。
一个监听器只监听一个特定的事件源,则我们可以采用匿名对象的方式来注册监听器。
例如:
JButton b=new JButton("jjjj");
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//重写actionPerformed
//do somthing....
}
});
Java Swing中处理各组件事件的一般步骤是:
1. 新建一个组件(如JButton)。
2. 将该组件添加到相应的面板(如JPanel)。
3. 注册监听器以监听事件源产生的事件(如通过ActionListener来响应用户点击按钮)。
4. 定义处理事件的方法(如在ActionListener中的actionPerformed中定义相应方法)。
上面第一种创建的监听器类可以是一个内部类,第二种创建监听器的方式为匿名内部类,很好理解。
我们将事件的处理与界面写在一起,一个是不便于维护,在一个会导致当前代码文件的臃肿,所以我还是建议大家讲事件的相应于界面分离,这样既可以弥补前面两种缺陷,同时也提高了代码的复用性。但是也不要忽略内部类与匿名内部类的使用,因为各有优缺点,我们要综合考虑。
Swing包提供了一种非常实用的机制来封装命令,并将它们连接到多个事件源,这就是Action接口。一个动作是一个封装下列内容的对象:
详细的内容请参考API。
需要注意,Action是一个接口,而不是一个类。实现这个接口的所有类都必须实现其中的7个方法。庆幸的是有一个类实现了这个接口除actionPerformed方法之外的所有方法,它就是AbstractAction。这个类存储了所有名/值对,并管理着属性变更监听器。我们可以直接扩展AbstractAction类,并在扩展类中实现actionPerformed方法。
我见过的一个比较好的设计方式是:监听器类继承javax.swing.AbstractAction,在组件上注册的时候讲组件传递到这个监听器中。
监听器:
public class AC_temp extends AbstractAction{
private static final long serialVersionUID = 1L;
MainFrame main;
public AC_EditVideoInfo(MainFrame temp,Color color) {
main=temp;
putValue(Action.NAME, "name");
putValue(Action.SMALL_ICON, new ImageIcon("images/edit.png"));
putValue(Action.SHORT_DESCRIPTION, "description");
putValue("color",color);
//....
}
@Override
public void actionPerformed(ActionEvent e) {
// do something
}
}
事件源:
组件.addActionListener(new AC_EditVideoInfo(this));//注册监听器
java.beans.EventHandler从命名来看是一个事件管理器。官方API给出的解释是:EventHandler 类为动态生成事件侦听器提供支持,这些侦听器的方法执行一条涉及传入事件对象和目标对象的简单语句。
更加详细的解释参考:————————>>>>>>>>>
使用 EventHandler 的示例:
EventHandler 最简单的使用方法是安装一个侦听器,不带参数地在目标对象上调用某个方法。在以下示例中,将创建一个在 javax.swing.JFrame 实例上调用 toFront 方法的 ActionListener。
myButton.addActionListener(
(ActionListener)EventHandler.create(ActionListener.class, frame, "toFront"));
当按下 myButton 时,将执行 frame.toFront() 语句。通过定义 ActionListener 接口的新实现并将其实例添加到按钮中,用户可以获得同样的效果,且具有额外的编译时类型安全:
//Equivalent code using an inner class instead of EventHandler.
myButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
frame.toFront();
}
});
EventHandler 的另一种最简单用法是从侦听器接口(通常是一个事件对象)中的方法的第一个参数中提取属性值,并用其设置目标对象中的属性值。在以下示例中,将创建一个 ActionListener,它将目标 (myButton) 对象的 nextFocusableComponent 属性设置为事件的 "source" 属性的值。
EventHandler.create(ActionListener.class, myButton, "nextFocusableComponent", "source")
这将对应于以下内部类实现:
//Equivalent code using an inner class instead of EventHandler.
new ActionListener() {
public void actionPerformed(ActionEvent e) {
myButton.setNextFocusableComponent((Component)e.getSource());
}
}
也可以创建一个只是将传入事件对象传递给目标动作的 EventHandler。如果 EventHandler.create 中的第四个参数为空字符串,则事件的传递方式如下:
EventHandler.create(ActionListener.class, target, "doActionEvent", "")
这将对应于以下内部类实现:
//Equivalent code using an inner class instead of EventHandler.
new ActionListener() {
public void actionPerformed(ActionEvent e) {
target.doActionEvent(e);
}
}
EventHandler 最常见的用法可能是从事件对象的 source 中提取属性值,并将此值设置为目标对象的属性值。在以下示例中,将创建一个 ActionListener,它将目标对象的 "label" 属性设置为事件源的 "text" 属性的值("source" 属性的值)。
EventHandler.create(ActionListener.class, myButton, "label", "source.text")
这将对应于以下内部类实现:
//Equivalent code using an inner class instead of EventHandler.
new ActionListener {
public void actionPerformed(ActionEvent e) {
myButton.setLabel(((JTextField)e.getSource()).getText());
}
}
可以使用以 "." 字符分隔的任意数量的属性前缀来“限定”事件属性。采用出现在 "." 字符前面的“限定”名称作为将应用于事件对象的属性名称,最左边的最先应用。
例如,以下动作侦听器
EventHandler.create(ActionListener.class, target, "a", "b.c.d")
可以写成以下内部类(假定所有属性都有规范的获取方法并返回适当的类型):
//Equivalent code using an inner class instead of EventHandler.
new ActionListener {
public void actionPerformed(ActionEvent e) {
target.setA(e.getB().getC().isD());
}
}
也可以使用以 "." 字符分隔的任意数量的属性前缀来“限定”目标属性。例如,以下动作侦听器:
EventHandler.create(ActionListener.class, target, "a.b", "c.d")
可以写成以下内部类(假定所有属性都有规范的获取方法并返回适当的类型):
//Equivalent code using an inner class instead of EventHandler.
new ActionListener {
public void actionPerformed(ActionEvent e) {
target.getA().setB(e.getC().isD());
}
}
由于 EventHandler 最终依赖反射来调用方法,所以建议不要以重载方法为目标。例如,如果目标是类 MyTarget 的一个实例,而 MyTarget 定义如下:
public class MyTarget {
public void doIt(String);
public void doIt(Object);
}
那么方法 doIt 被重载。EventHandler 将基于源调用恰当的方法。如果源为 null,那么两个方法都可以,具体调用哪个方法是不确定的。因此,建议不要以重载方法为目标。
《Java核心技术卷一》上面显示可以更改JDK的配置文件来实现,但是我在我的电脑上找不到那个配置文件,我的JDK是1.8。
官方提供:Modifying the Look and Feel
查看已有观感器:
UIManager.LookAndFeelInfo[] info = UIManager.getInstalledLookAndFeels();
for (LookAndFeelInfo lookAndFeelInfo : info) {
System.out.println("name:" + lookAndFeelInfo.getName());
System.out.println("class:" + lookAndFeelInfo.getClassName());
}
更多详细内容请参考:Java皮肤详解
配置文件在Java安装目录的子目录jre/lib下,一个名为swing.properties的文件,在这个文件中,将属性swing.defaultlaf设置为所希望的观感器类名。
注意:Metal观感器位于javax.swing包中。其他的观感器包位于com.sun.java包中。采用这种方式开启观感器时必须重新启动程序。Swing程序只在启动时读取该配置文件。
方法很简单:调用UIManager.setLookAndFeel方法,并提供所想要的观感器类名,然后调用静态方法SwingUtilities.updateComponentTreeUI来刷新全部的组件集。这里需要向这个方法提供一个组件,并由此找到其他的所有组件。
整理的对照表:
通过上文我们可以看出Java对事件的处理机制非常类似于设计模式中的观察者模式 ,如果不了解,可以参考:设计模式之观察者模式
在Java中,处理事件采用了监听器类,每个事件类都有相关联的监听器接口。事件从事件源到监听者的传递是通过对目标监听者对象的Java方法调用进行的。实现了事件监听者接口中一些或全部方法的类就是事件监听者。伴随着事件的发生,相应的状态通常都封装在事件对象中。事件对象作为单参传递给应响应该事件的监听者方法中。
Java事件生命周期的示意图:(AWTEvent类的子类的生命周期)
事件生成后放在系统事件队列内部->现在事件处于事件分发线程的控制下-> 事件在队列中等待处理,然后事件从事件队列中选出,送到dispatchEvent()方法,dispatchEvent()方法调用 processEvent()方法并将事件的一个引用传递给processEvent()方法。此刻,系统会查看是否有送出事件的位置,如果没有这种事件类型相应的已经注册的监听器,或者如果没有任何组件受到激活来接收事件类型,事件就被抛弃。
注意:dispatchEvent()方法与processEvent()是java.awt.Component中的方法。
当然上图显示的是AWTEvent类的子类的生命周期。dispatchEvent()方法和processEvent()方法把AWTEvent作为一个参数。但对,javax.swing.event并不是AWTEvent子类,而是从EventObject直接继承过来,生成这些事件的对象也会定义fireEvent()方法,此方法将事件送到包含在对象监听器列表内的那种类型的任何监听器。
任何事件产生到dispatchEvent()方法分发方法前,所有的事件都是存放在系统事件的队列中,而且所有的事件都由dispatchEvent()方法来分派。所以只要能重载dispatchEvent()方法就可以获取系统的所有事件,包括用户输入事件。一般来说, 系统事件队列的操作对用户来说是可以控制。它在后台自动完成所要完成的事情,使用EventQueue类可以查看甚至操纵系统事件队列。
Java提供了EventQueue类来访问甚至操纵系统事件队列。EventQueue类中封装了对系统事件 队列的各种操作,除 dispatchEvent()方法外,其中最关键的是提供了push()方法,允许用特定的EventQueue来代替当前的EventQueue。只 要从EventQueue类中派生一个新类,然后通过push()方法用派生类来代替当前的EventQueue类即可。这样,所有的系统事件都会转发到 派生EventQueue类。然后,再在派生类中重载dispatchEvent()方法就可以截获所有的系统事件,包括用户输入事件。
下面一段代码给出一个操纵EventQueue的实例:
import java.awt.*;
import java.awt.event.*;
public class GenerateEventQueue extends Frame implements ActionListener {
private static final long serialVersionUID = 1L;
Button button1 = new Button();
TextField textField1 = new TextField();
public GenerateEventQueue() {
try {
jbInit();
} catch (Exception e) {
e.printStackTrace();
}
}
private void jbInit() throws Exception {
button1.setLabel("button1");
button1.addActionListener(this);
textField1.setText("textField1");
this.add(button1, BorderLayout.SOUTH);
this.add(textField1, BorderLayout.CENTER);
EventQueue eq = getToolkit().getSystemEventQueue();// 事件捕获1
eq.postEvent(new ActionEvent(button1, ActionEvent.ACTION_PERFORMED,
"我爱你中国"));
setBounds(100, 100, 300, 200);
setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
textField1.setText("event is :" + e.getActionCommand());
}
public static void main(String[] args) {
new GenerateEventQueue();// 创建实例
}
}
运行结果:
在文本域中首先出现的是"event is :我爱你中国",这是因为首先得到处理的是EventQueue对象发送到系统事件队列上的ActionEvent。
----->>>待完成
后期提示!!--->>统计一下JDK1.6之后的发展
参考资料: