文档

Java™教程
隐藏目录
关于编写事件监听器的常规信息
路径: 使用Swing创建GUI
课程: 编写事件监听器

关于编写事件监听器的一般信息

本节讨论在实现应用程序的事件处理程序时需要考虑的几个设计要点。然后,我们介绍了事件对象,即描述每个事件的小型对象。特别是,我们讨论了所有AWT和Swing事件的超类EventObject。接下来,我们介绍了低级事件和语义事件的概念,并建议在可能的情况下优先选择语义事件。本节的其余部分讨论了一些事件侦听器中可能使用的实现技术,或者在其他人或GUI构建器创建的事件侦听器中可能看到的技术。

设计要点

关于事件侦听器,最重要的规则是它们应该执行非常快。因为所有的绘制和事件侦听方法都在同一个线程中执行,一个慢的事件侦听器方法会使程序看起来没有响应,并且重新绘制自身的速度很慢。如果需要执行一些耗时操作作为事件的结果,可以通过启动另一个线程(或以某种方式向另一个线程发送请求)来执行该操作。有关使用线程的帮助,请参阅Swing中的并发

在实现事件侦听器时,您有许多选择。我们不能推荐一个特定的方法,因为一个解决方案不适用于所有情况。然而,我们可以给出一些提示,展示一些可能在您的程序中看到的技术,即使您没有使用相同的解决方案。

例如,您可以选择为不同类型的事件侦听器实现单独的类。这可能是一个容易维护的架构,但也可能意味着性能降低。

在设计程序时,您可能希望将事件侦听器实现在一个不是公共的类中,而是更隐藏的地方。私有实现是更安全的实现。

如果您有一种非常特定的简单事件侦听器,您可能可以通过使用EventHandler类而完全避免创建一个类。

获取事件信息:事件对象

每个事件侦听器方法都有一个单一的参数,该参数继承自EventObject类的对象。虽然参数总是继承自EventObject,但其类型通常更具体地指定。例如,处理鼠标事件的方法的参数是MouseEvent的实例,其中MouseEventEventObject的间接子类。

EventObject类定义了一个非常有用的方法:

Object getSource()
返回触发事件的对象。

请注意,getSource方法返回一个Object。事件类有时会定义类似于getSource的方法,但其返回类型更为具体。例如,ComponentEvent类定义了一个getComponent方法,与getSource类似,返回触发事件的对象。不同之处在于,getComponent始终返回一个Component。每个事件监听器的"how-to"页面都会提到您应该使用getSource还是其他方法来获取事件源。

通常,事件类会定义返回有关事件的信息的方法。例如,您可以查询MouseEvent对象以获取事件发生的位置、用户点击次数、按下的修饰键等等。

概念:低级事件和语义事件

事件可以分为两组:低级事件和语义事件。低级事件代表窗口系统的发生或低级输入。其他所有事件都是语义事件。

低级事件的示例包括鼠标事件和键盘事件,这两种事件直接由用户输入引发。语义事件的示例包括动作事件和项目事件。语义事件可能由用户输入触发;例如,当用户点击按钮时,按钮通常会触发一个动作事件,当用户按下Enter时,文本字段会触发一个动作事件。然而,并不是所有的语义事件都由低级事件触发。例如,当表格模型从数据库接收到新数据时,可能会触发一个表格模型事件。

在可能的情况下,您应该监听语义事件而不是低级事件。这样,您的代码就可以尽可能健壮和可移植。例如,监听按钮上的动作事件而不是鼠标事件,意味着当用户尝试使用键盘替代方案或特定外观和感觉手势激活按钮时,按钮将作出适当的反应。在处理复合组件(如下拉框)时,务必坚持使用语义事件,因为您无法可靠地在所有可能用于构成复合组件的特定外观和感觉组件上注册监听器。

事件适配器

一些监听器接口包含多个方法。例如,MouseListener接口包含五个方法:mousePressedmouseReleasedmouseEnteredmouseExitedmouseClicked。即使您只关心鼠标点击,如果您的类直接实现MouseListener,那么您必须实现所有五个MouseListener方法。您不关心的那些事件的方法可以留空。以下是一个示例:

//一个直接实现监听器接口的示例。
public class MyClass implements MouseListener {
    ...
        someObject.addMouseListener(this);
    ...
    /* 空方法定义。 */
    public void mousePressed(MouseEvent e) {
    }

    /* 空方法定义。 */
    public void mouseReleased(MouseEvent e) {
    }

    /* 空方法定义。 */
    public void mouseEntered(MouseEvent e) {
    }

    /* 空方法定义。 */
    public void mouseExited(MouseEvent e) {
    }

    public void mouseClicked(MouseEvent e) {
        ...//事件监听器实现在这里...
    }
}

一系列空方法体的结果会使代码更难阅读和维护。为了帮助你避免实现空方法体,API通常为每个具有多个方法的监听器接口包含一个适配器类(监听器API表列出了所有监听器及其适配器)。例如,MouseAdapter类实现了MouseListener接口。适配器类实现了其接口的所有方法的空版本。

要使用适配器,你可以创建它的子类并仅覆盖感兴趣的方法,而不是直接实现监听器接口的所有方法。以下是修改前面代码以扩展MouseAdapter的示例。通过扩展MouseAdapter,它继承了MouseListener包含的所有五个方法的空定义。

/*
 * 扩展适配器类而不是直接实现监听器接口的示例。
 */
public class MyClass extends MouseAdapter {
    ... 
        someObject.addMouseListener(this);
    ... 
    public void mouseClicked(MouseEvent e) {
        ...//事件监听器实现在这里...
    }
}

内部类和匿名内部类

如果你想使用适配器类,但又不希望你的公共类继承适配器类怎么办?例如,假设你编写了一个小程序,希望你的Applet子类包含一些处理鼠标事件的代码。由于Java语言不允许多继承,你的类无法同时扩展AppletMouseAdapter类。解决方案是定义一个内部类,即在Applet子类内部扩展MouseAdapter类。

内部类对于直接实现一个或多个接口的事件监听器也很有用。

//使用内部类的示例。
public class MyClass extends Applet {
    ...
        someObject.addMouseListener(new MyAdapter());
    ...
    class MyAdapter extends MouseAdapter {
        public void mouseClicked(MouseEvent e) {
            ...//事件监听器实现在这里...
        }
    }
}

性能注意事项: 

在考虑是否使用内部类时,请记住应用程序启动时间和内存占用通常与加载的类数成正比。创建的类越多,程序启动所需的时间越长,占用的内存越多。作为应用程序开发人员,您必须在这一点上权衡其他设计约束。我们并不建议您将应用程序变成一个单独的庞大类,以期减少启动时间和内存占用,这样只会带来不必要的麻烦和维护负担。


您可以创建一个没有指定名称的内部类,这被称为匿名内部类。虽然乍一看可能会很奇怪,但匿名内部类可以使您的代码更易于阅读,因为类在引用它的位置被定义。然而,您需要权衡方便性与增加类数量可能带来的性能影响。

下面是使用匿名内部类的示例:

//使用匿名内部类的示例。
public class MyClass extends Applet {
    ...
        someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                ...//事件监听器的实现在这里...
            }
        });
    ...
    }
}

注意: 

匿名内部类的一个缺点是它们无法被长期持久性机制看到。有关更多信息,请参阅JavaBeans™包Bean持久化教程中的API文档。


即使事件监听器需要访问封闭类的私有实例变量,内部类也可以正常工作。只要不将内部类声明为static,内部类就可以像其代码在包含类中一样引用实例变量和方法。要使局部变量可用于内部类,只需将变量的副本保存为final局部变量。

要引用封闭实例,可以使用EnclosingClass.this。有关内部类的更多信息,请参见嵌套类

EventHandler类

EventHandler类支持动态生成简单的一句话事件监听器。虽然EventHandler仅对某种类型的极其简单的事件监听器有用,但出于两个原因,它值得一提。它对以下情况有用:

手动创建EventHandler是困难的。必须仔细构造EventHandler。如果出现错误,编译时不会通知您,而是在运行时抛出一个晦涩的异常。因此,最好由GUI构建器创建EventHandler。应该仔细记录EventHandler,否则会产生难以阅读的代码。

EventHandler类的用途是供交互工具使用,例如应用程序构建器,允许开发人员在bean之间建立连接。通常,连接是从用户界面bean(事件源)到应用程序逻辑bean(目标)进行的。这种类型的最有效连接将应用程序逻辑与用户界面隔离。例如,对于从JCheckBox到接受布尔值的方法的连接,EventHandler可以处理提取复选框的状态并直接传递给方法,从而使方法与用户界面层隔离。

内部类是处理来自用户界面的事件的另一种更通用的方法。EventHandler类仅处理使用内部类可能实现的子集。然而,EventHandler与长期持久性方案相比,使用更好。此外,在许多次实现相同接口的大型应用程序中使用EventHandler可以减少应用程序的磁盘和内存占用。

使用EventHandler的示例 最简单的使用EventHandler的方法是安装一个调用目标对象上没有参数的方法的监听器。在下面的示例中,我们创建一个ActionListener,它调用javax.swing.JFrame的toFront方法的实例。

    myButton.addActionListener(
        (ActionListener)EventHandler.create(ActionListener.class, frame, "toFront"));

当按下myButton时,将执行frame.toFront()语句。通过定义ActionListener接口的新实现并将其实例添加到按钮中,可以获得相同的效果,并获得一些额外的编译时类型安全性:

    //使用内部类而不是EventHandler的等效代码。
    myButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            frame.toFront();
        }
    });

EventHandler的下一个最简单的用法是从监听器接口中的第一个参数(通常是事件对象)中提取属性值,并将其用于设置目标对象中的属性值。在下面的示例中,我们创建一个ActionListener,将事件的"source"属性的值设置为目标(myButton)对象的nextFocusableComponent属性的值。

    EventHandler.create(ActionListener.class, myButton, "nextFocusableComponent", "source")

这对应于以下的内部类实现:

    //使用内部类替代EventHandler的等效代码。
    new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            myButton.setNextFocusableComponent((Component)e.getSource()); 
        }
    }

还可以创建一个只将传入的事件对象传递给目标动作的EventHandler。如果第四个EventHandler.create参数是一个空字符串,那么事件就会被直接传递:

    EventHandler.create(ActionListener.class, target, "doActionEvent", "")

这对应于以下的内部类实现:

    //使用内部类替代EventHandler的等效代码。
    new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            target.doActionEvent(e);
        }
    }

EventHandler最常见的用法可能是从事件对象的源中提取属性值,并将该值设置为目标对象的属性值。在下面的示例中,我们创建一个ActionListener,将目标对象的"label"属性设置为事件源的"text"属性的值(即"source"属性的值)。

    EventHandler.create(ActionListener.class, myButton, "label", "source.text")

这对应于以下的内部类实现:

    //使用内部类替代EventHandler的等效代码。
    new ActionListener {
        public void actionPerformed(ActionEvent e) {
            myButton.setLabel(((JTextField)e.getSource()).getText()); 
        }
    }

上一页: 事件监听器简介
下一页: Swing组件支持的监听器