IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Java technology  >

Merlin 的魔力: 动态事件监听器代理

用 EventHandler 进行事件监听

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

John Zukowski (jaz@zukowski.net), 总裁, JZ Ventures, Inc

2003 年 11 月 24 日

许多开发人员为事件处理创建匿名内部类。对于简单的事件处理,内部类可能成为真正的争论话题.幸运的是,Java1.4 引入了 EventHandler 类,它依赖于监听器的动态生成以方便地处理手头的任务。尽管这个新功能通常是让IDE 厂商使用的,但是在本文中,专栏作者 John Zukowski 还是为您展示了如何用它进行手工编码。请参与本文的 讨论论坛,与本文作者和其他读者分享您对本文的看法(也可以单击文章顶部或底部的 讨论访问这个论坛)。

所有 Swing 组件都是 JavaBeans 组件。它们有一系列的 setter 和 getter 方法,这些方法的类似于 void setXXX(类型名) Type getXXX() 。关于这些方法没有什么特别之处,并且正如所预期的,它们遵循 JavaBeans 的属性命名规范。我们今天要讨论的是JavaBeans 组件的一个方面,即一对监听器方法 addXXXListener (XXXListener name) removeXXXListener (XXXListener name)XXListener 在这里指的是一个监听器对象,它扩展了 EventListener 接口,等候与监听器关联的组件中的各种事件发生。当事件发生时,所有注册的监听器都会得到事件的通知(没有特定的顺序)。通过魔术般的一个小反射(reflection)和一个新的 java.beans.EventHandler 类,您可以将一个监听器附加到一个 bean 上,而无需直接实现这个监听器接口或者创建那些烦人的小匿名内部类。

以前的方法

在深入到使用新的 EventHandler 类的细节之前,让我们回顾一下不使用这个类时是如何进行工作的。我们举一个对 Swing 框架中的按钮选择做出响应的简单例子。选择一个按钮生成一个 ActionEvent 。要对这个事件做出响应,需要将 ActionListener 附加到这个按钮上,如清单 1 所示:


清单 1. 监听标准按钮选择
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ButtonSelection extends JFrame {
  public ButtonSelection() {
    super("Selection");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    JButton button = new JButton("Pick Me");
    Container contentPane = getContentPane();
    contentPane.add(button, BorderLayout.CENTER);
    button.addActionListener(
      new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          System.out.println("Hello, World!");
        }
      }
    );
  }
  public static void main(String args[]) {
    JFrame frame = new ButtonSelection();
    frame.setSize(200, 100);
    frame.show();
  }
}

这里没有任何神奇之处,您可能已经熟悉这种代码了。这里, ActionListener 实现是适时定义的,它定义为一个匿名内部类,并直接附加到按钮上。在选择这个按钮时,字符串 Hello, World! 就打印到控制台中。与程序关联的屏幕如图 1 所示:


图 1. 带 ActionListener 的按钮选择
带 ActionListener 的按钮选择

在 JavaBeans 规范中,没有要求您创建匿名内部类进行事件监听。 IDE 工具常常采用这种行为:您说要一个监听器,它就生成一个 stub,然后您填入细节。完成同样工作的其他方式包括在调用类中提供指定的实现或者实现您自己的接口。

定义了每一个实现类后,就会创建一个单独的.class 文件。所以,在前面的 ButtonSelection 程序中,您会看到编译器生成两个 .class 文件:ButtonSelection.class 和 ButtonSelection$1.class。 $1 是 Sun 编译器命名匿名内部类的方式,计数随着每一个类增加。其他编译器可能有不同的命名方式。





回页首


用 EventHandler 注册监听器

EventHandler 类提供了另一种将监听器注册到 JavaBeans 组件上的方法。它不是创建一个实现了接口的类、并将这个实现注册到需要监听的事件所在的组件上,而是创建一个 EventHandler 实例并注册它。虽然这个类有一个公共构造函数,但一般不使用它而是使用三个静态 create() 方法,如清单 2 所示:


清单 2. EventHandler 的 create() 方法
public static Object create(Class listenerInterface,
                            Object target,
                            String action)
public static Object create(Class listenerInterface,
                            Object target,
                            String action,
                            String eventPropertyName)
public static Object create(Class listenerInterface,
                            Object target,
                            String action,
                            String eventPropertyName,
                            String listenerMethodName)

让我们更详细地看一下这三种方法。

使用 create(Class, Object, String)

因为第一种方法的参数最少,所以它最简单。第一个参数是 EventListener 类型,您要实现的就是它的接口,例如,要响应按钮选择,这个参数应该是 ActionListener.class ,以表示这个接口的 Class 对象。虽然 ActionListener 只有接口中的一个方法,但是以这种方式创建接口的一个实现意味着这个接口实现的所有方法都将执行同样的代码。

第二和第三个参数是相互关联的。它们结合在一起说明调用 Object 目标的 String 操作方法。然后使用反射,您有一个 ActionListener 实现,但是没有在文件系统中增加一个 .class 文件。清单 3 重复了前面 图 1 中的按钮选择例子,使用了一个 EventHandler 。注意 println() 调用需要转移到一个方法中,这样就可以从处理程序中调用它。


清单 3. 展示 create(Class, Object, String)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
public class ButtonEventHandler extends JFrame {
  public ButtonEventHandler() {
    super("Selection");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    JButton button = new JButton("Pick Me");
    Container contentPane = getContentPane();
    contentPane.add(button, BorderLayout.CENTER);
    button.addActionListener(
      (ActionListener)EventHandler.create(
        ActionListener.class,
	this,
	"print")
    );
  }
  public void print() {
    System.out.println("Hello, World!");
  }
  public static void main(String args[]) {
    JFrame frame = new ButtonEventHandler();
    frame.setSize(200, 100);
    frame.show();
  }
}

create() 中调用 EventHandler 的代码只是表示“在需要通知按钮所附的 ActionListener 时,调用我们的 print() 方法( this )”。不过有一些副作用。第一个是调用需要强制类型转换,以返回正确的监听器类型,从而满足编译器要求。另一个副作用是由于对 print() 的调用是通过反射间接进行的,所以这个方法必须是公共的(并且不接受参数)。使用 EventHandler 的另一个特点是对于其他版本的 create() 来说,很少出现问题。

使用 create(Class, Object, String, String)

下一版本的 create() 添加了第四个参数,并增加了第三个参数的用途。第一个 String 参数现在也可以表示 Object 参数的可写 JavaBeans 属性的名字。所以,对于 JButton ,如果第三个参数是 text ,那么这相当于一个 setText() 调用,该方法所需要的参数是由传递给第四个参数的 String 来表示的。

第四个参数使您可以访问事件的可读属性,用第三个参数传递的值设置可写属性。为了示范这一点,清单 4 提供了一个用于输入的 JTextField 组件和一个用于文本显示的 JLabel 组件。在 JTextField 中的按 Return 键时,生成一个 ActionEvent ,并且标签的文字变为 JTextField 中的内容。


清单 4. 展示 create(Class, Object, String, String)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
public class TextFieldHandler extends JFrame {
  public TextFieldHandler() {
    super("Selection");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    JTextField text = new JTextField();
    JLabel label = new JLabel();
    Container contentPane = getContentPane();
    contentPane.add(text, BorderLayout.NORTH);
    contentPane.add(label, BorderLayout.CENTER);
    text.addActionListener(
      (ActionListener)EventHandler.create(
        ActionListener.class,
	label,
	"text",
	"source.text")
    );
  }
  public static void main(String args[]) {
    JFrame frame = new TextFieldHandler();
    frame.setSize(200, 150);
    frame.show();
  }
}

图 2 显示了这个程序的外观。在文本字段中输入文字并按 Return 键。这会使 ActionListener 产生 EventHandler.create(ActionListener.class, label, "text", "source.text") 调用,其中 source.text 表明要得到事件源的 text 属性,直接映射到 label.setText((JTextField(event.getSource())).getText()) 代码。


图 2. 处理文本字段输入
处理文本字段输入

使用 create(Class, Object, String, String, String)

最后一种版本的 create() 是将另外两种方法结合在一起使用,对于在其他调用中没有的参数,则传递 null 。其他版本的 create() 要求您对所有监听器接口方法做同样的事情,这最后一种方法让您可以指定对每一种监听器方法调用不同的操作。所以,对于一个 MouseListener ,您可以为 mousePressed() 调用一种操作,为 mouseReleased() 调用另一种操作、还可以为 mouseClicked() 调用其他的操作。清单 5 展示了最后一种版本的 create() ,它只有用于鼠标按下/释放事件的两种简单的打印方法:


清单 5. 展示 create(Class, Object, String, String, String)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
public class MouseHandler extends JFrame {
  public MouseHandler() {
    super("Press and Release Mouse");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    Container contentPane = getContentPane();
    contentPane.addMouseListener(
      (MouseListener)EventHandler.create(
        MouseListener.class,
        this,
        "pressed",
        "point",
        "mousePressed")
    );
    contentPane.addMouseListener(
      (MouseListener)EventHandler.create(
        MouseListener.class,
        this,
        "released",
        "point",
        "mouseReleased")
    );
  }
  public void pressed(Point p) {
    System.out.println("Pressed at: " + p);
  }
  public void released(Point p) {
    System.out.println("Released at: " + p);
  }
  public static void main(String args[]) {
    JFrame frame = new MouseHandler();
    frame.setSize(400, 400);
    frame.show();
  }
}

这个程序没有什么不寻常的地方,只有一个大的空屏幕,可以在其中按下和释放鼠标。不过要注意屏幕附加了两个鼠标监听器,而不是一个。对每个监听器,其他的方法实质上都是沉寂的(stubbed out)。还要注意 pressed() released() 方法有一个参数是事件的 Point 。对于不接受参数的方法,在指定 point 的地方需要一个 null





回页首


结束语

这就是有关使用 EventHandler 全部内容。是否要使用它呢?我个人认为这是一种风格问题。在内部它用到了反射,所以可能会稍微慢一些。它还要求调用方法为公共的。如果 IDE 替我生成了代码,那么我可能就让它保持原样,而不会将编码监听器重新编码为匿名内部类。



参考资料



关于作者

John Zukowski 在 JZ Ventures, Inc 从事战略上的 Java 咨询,同时通过 AnswerSquad.com 网站提供技术支持,并且正在与 SavaJe Technologies 公司合作开发下一代移动电话平台。他的最新著作是 Mastering Java 2, J2SE 1.4 (Sybex 出版,2002年4月)和 Learn Java with JBuilder 6 (Apress出版,2002年3月)。可以通过 jaz@zukowski.net 和 John 联系。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?




回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款