|
|
|
Contents: |
|
|
|
Related content: |
|
|
|
Subscriptions: |
|
|
| Using EventHandler for event listening
John
Zukowski (mailto:jaz@zukowski.net?cc=&subject=Dynamic
event listener proxies) President, JZ Ventures, Inc. 21 October
2003
Many 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
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
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
|
|