IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Java technology
developerWorks
Magic with Merlin: Dynamic event listener proxies
Discuss81 KBe-mail it!
Contents:
The way we were
Registering listeners with EventHandler
Summary
Resources
About the author
Rate this article
Related content:
Long-term persistence
Reflecting, introspecting, and customizing JavaBeans
IBM developer kits for Java (downloads)
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
Using EventHandler for event listening

Level: Introductory

John Zukowski (mailto:jaz@zukowski.net?cc=&subject=Dynamic event listener proxies)
President, JZ Ventures, Inc.
21 October 2003

Column iconMany developers create anonymous inner classes for event handling. For simple event handling, inner classes can be a real hassle. Luckily, Java 1.4 introduces the EventHandler class, which relies on the dynamic generation of listeners to ease the task at hand. Though the new features are typically meant for the IDE vendor to use, in this article columnist John Zukowski shows you how you can use them for hand coding, too. Share your thoughts on this article with the author and other readers in the accompanying discussion forum. (You can also click Discuss at the top or bottom of the article to access the forum.)

All Swing components are JavaBeans components. They have a series of setter and getter methods that look like void setXXX(Type name) and Type getXXX(). There is nothing remarkable about the methods, and, as expected, they follow the JavaBeans naming conventions for properties. One aspect of JavaBeans components, which sets the stage for our discussion, is a pair of listener methods, addXXXListener (XXXListener name) and removeXXXListener (XXXListener name). The XXXListener referenced here is a listener object, extending the EventListener interface, that waits for various events to happen within the component associated with the listener. When that event happens, all registered listeners are notified of the event (in no particular order). Through the magic of a little reflection and the new java.beans.EventHandler class, you can attach a listener to a bean without directly implementing the listener interface or creating those annoying little anonymous inner classes.

The way we were
Before delving into the details of using the new EventHandler class, let's review how we do things now without benefit of the class. Let's take a simple example of responding to button selection within a Swing frame. Selecting a button generates an ActionEvent. To respond to the event, you need to attach an ActionListener to the button, as demonstrated in Listing 1:

Listing 1. Standard button selection listening

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();
  }
}

There is nothing magical here, and you're probably already familiar with this type of code. Here, the ActionListener implementation is defined in place, as an anonymous inner class, and directly attached to the button. When the button is selected, the string Hello, World! is printed to the console. The screen associated with the program is shown in Figure 1:

Figure 1. Button selection with ActionListener
 Button Selection with ActionListener

There is nothing in the JavaBeans specification that requires you to create anonymous inner classes for event listening. This behavior is commonly done with IDE tools; you say you need a listener, it generates the stub, and you fill in the details. Other ways of doing the same thing include providing named implementations or implementing the interface yourself in the calling class.

With each implementation class defined, a separate .class file is created. So, in the prior ButtonSelection program, you'll find two .class files generated by the compiler: ButtonSelection.class and ButtonSelection$1.class. The $1 is the Sun compiler's way of naming anonymous inner classes, incrementing the count for each class. Other compilers may name things differently.

Registering listeners with EventHandler
The EventHandler class introduces another way to register listeners to JavaBeans components. Instead of creating a class that implements an interface and registering that implementation with the component whose event you are interested in, you create an EventHandler instance and register it. While there is a public constructor for the class, instead of using it, you typically use one of the three static create() methods shown in Listing 2:

Listing 2. The create() methods of EventHandler

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)

Let's look at the three versions in more detail.

Using create(Class, Object, String)
Because it has the fewest arguments, the first version is the simplest. The first argument is the EventListener type whose interface you are implementing. For instance, to respond to a button selection, the argument would be ActionListener.class to represent the Class object for the interface. While ActionListener only has one method in the interface, creating an implementation of an interface in this manner means all methods of that interface implementation will execute the same code.

The second and third arguments are interrelated. Combined, they say to invoke the String action method of the Object target. Using reflection then, you have an ActionListener implementation, but don't have an added .class file in the file system. Listing 3 duplicates the earlier button selection example shown in Figure 1, using an EventHandler. Note that the println() call needs to be moved into a method so that it can be invoked from the handler.

Listing 3. Demonstrating 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();
  }
}

The code in the create() call of EventHandler simply says, "call our print() method (this) when the attached ActionListener of the button needs to be notified." There are, however, a couple side effects. The first is that the call requires casting to return the appropriate listener type to satisfy the compiler. The other side effect is that because the invocation of print() is done indirectly through reflection, the method must be public (and accept no arguments). This latter feature of using EventHandler is less of an issue with the other versions of create().

Using create(Class, Object, String, String)
The next version of create() adds a fourth argument, along with an additional use for the third. The first String argument now can also represent the name of a writeable JavaBeans property of the Object argument. So, in the case of a JButton, if the third argument was text, then this would equate to a setText() call, where the argument to the method was represented by the String sent into the fourth argument.

The fourth argument allows you to access a readable property of the incoming event to set the writeable property passed in as the third argument. To demonstrate, Listing 4 offers a JTextField component for input and a JLabel component for text display. When you press the Return key in the JTextField, an ActionEvent is generated and the text of the label is changed to the contents of the JTextField.

Listing 4. Demonstrating 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();
  }
}

Figure 2 shows what the program looks like. Just enter text in the text field at the top and press Return. This triggers the ActionListener generated from the EventHandler.create(ActionListener.class, label, "text", "source.text") call, where source.text says to get the text property of the event source, mapping directly to the label.setText((JTextField(event.getSource())).getText()) code.

Figure 2. Handling text field entry
Handling Text Field Entry

Using create(Class, Object, String, String, String)
The final version of create() is what the other two wind up using in the end, passing null for arguments that aren't available in the other calls. Where the other versions of create() required you to do the same thing for all methods of the listener interface, this last one allows you to specify different actions to invoke for each listener interface method. So, with a MouseListener, you could invoke one action for mousePressed(), another for mouseReleased(), and yet another for mouseClicked(). Listing 5 demonstrates this final version of create() with just a couple of simple printing methods for mouse pressed/released events:

Listing 5. Demonstrating 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();
  }
}

There is nothing really remarkable about this program, just a big empty screen where you press and release the mouse. Notice that two mouse listeners are attached to the screen, though, instead of just one. For each of the listeners, the other methods are essentially stubbed out. Also, notice that the pressed() and released() methods get an argument of the event's Point. For those methods to accept no argument would require a null where point was specified.

Summary
That's all there is to using EventHandler. Should you use it? Personally, I think it is a matter of style. It internally involves reflection so it could be slightly slower. It also requires the invoked methods to be public. If an IDE generates the code for me, I'd probably just leave it instead of recoding the listeners as anonymous inner classes.

Resources

About the author
John Zukowski conducts strategic Java consulting with JZ Ventures, Inc., offers technical support through AnswerSquad.com, and is working with SavaJe Technologies to develop a next-generation mobile phone platform. His latest books are Mastering Java 2, J2SE 1.4 (Sybex, April 2002) and Learn Java with JBuilder 6 (Apress, March 2002). Reach him at mailto:jaz@zukowski.net?Subject=Magic with Merlin.


Discuss81 KBe-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Send us your comments or click Discuss to share your comments with others.



developerWorks > Java technology
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact