Innovative ways to handle events in AWT and JFC

Learn six different ways to connect event sources with target objects

1 2 3 Page 2
Page 2 of 3

ButtonTest4: Using EventHandler

You should use the static methods provided in this class to connect the event source and target object together. This class implements static methods to handle all types of AWT event listeners. Internally, this class uses a single implementation object to register which object is interested in which type of event from which event source. It attaches a listener to the event source you passed in. Whenever an event is received through the listener, it broadcasts the events to all the target objects that are interested in receiving the event. At the time of broadcast, it uses the Reflection API to find a matching listener method signature on the target object. If an object is interested in receiving an action event, for example, then

EventHandler

expects an implementation of the

actionPerformed (ActionEvent ae)

method in the target object. If such method is not found on the target, then the broadcast will have no effect on the target object. All it means is that the target object is not interested in providing an implementation to handle the event. This is quite handy for developers who are

only

interested in implementing a particular method in a event listener.

EventHandler

gives you this flexibility by not allowing you to implement all methods in the event listener and still be able to receive events. Initially, this might look like magic, but believe me, this trick is possible due to the power of the Reflection API.

The following program reimplements our toggle button example using the EventHandler class.

package test.event;

import java.awt.*; import java.awt.event.*;

/** Uses EventHandler class. */ public class ButtonTest4 extends Frame { Button top = new Button("Toggle bottom button"); Button bottom = new Button("Toggle top button");

public ButtonTest4() { super("ButtonTest4 uses EventHandler"); setLayout(new BorderLayout()); add("North", top); add("Center", bottom);

bottom.setEnabled(true); top.setEnabled(false);

EventHandler.addActionListener(top, this); EventHandler.addActionListener(bottom, this); EventHandler.addWindowListener(this, this); pack(); show(); } public void windowClosing(WindowEvent e) { System.exit(0); }

public void actionPerformed(ActionEvent event) { Button b = (Button)event.getSource(); if(b == bottom) { bottom.setEnabled(false); top.setEnabled(true); } else { bottom.setEnabled(true); top.setEnabled(false); } } public static void main(String[] args) { new ButtonTest4(); } }

Disadvantages

This approach does not scale well for large UI containers. As you can see, the container object (

this

) is passed to the

addActionListener()

method as a target object.

Frames

or

Panels

that contain a lot of UI controls can optionally use inner classes to implement methods defined in event listeners and register these inner class objects as target objects. The small advantage here is that the inner class can derive from any class other than the

EventAdapter

class.

Advantages

This approach is good for small, simple programs. There is no need to implement any interface or extend any adapter. It results in the smallest possible compiled code size.

ButtonTest5: Using EventListener

This example uses the

EventListener

object as a valid listener to attach an event-handling method to all AWT-based UI controls. The

EventListener

class implements all event listener interfaces. Thus, it is always a valid argument to any

addXXXListener()

methods.

Since EventListener implements all possible listener interfaces in AWT, it is able to receive all predefined events. Those events will be forwarded to the target object. You have the choice of hooking any method that accepts that event as an argument. Unlike the EventHandler class, EventListener is unforgiving; you have to implement the target method specified as an argument to the constructor. The EventListener class constructor throws an exception if that method does not exist on the target object. You can remove this constraint if doing so would be more suitable to your development framework.

package test.event;

import java.awt.*; import java.awt.event.*; import java.util.*; import java.awt.event.EventListener;

/** Uses EventListener class. */ public class ButtonTest5 extends Frame { Button top = new Button("Toggle bottom button"); Button bottom = new Button("Toggle top button");

public ButtonTest5() { super("ButtonTest5 uses EventListener"); setLayout(new BorderLayout()); add("North", top); add("Center", bottom);

bottom.setEnabled(true); top.setEnabled(false);

top.addActionListener(new EventListener(this, "button1Action","actionPerformed")); bottom.addActionListener(new EventListener(this, "button2Action", "actionPerformed" )); this.addWindowListener(new EventListener(this, "windowClosingMethod", "windowClosing" ));

pack(); show(); } public void windowClosingMethod(WindowEvent e) { System.exit(0); }

public void function(ActionEvent event) { Button b = (Button)event.getSource(); if(b == bottom) { bottom.setEnabled(false); top.setEnabled(true); } else { bottom.setEnabled(true); top.setEnabled(false); } }

public void button1Action(ActionEvent e) { function(e); }

public void button2Action(ActionEvent e) { function(e); }

public static void main(String[] args) { new ButtonTest5(); } }

In the above example, the topbuttonAction and bottombuttonAction methods on the container object are connected with the actionPerformed method expected on the action event listener for the top button and the bottom button, respectively. If necessary, these methods can be implemented on any class other than the container class.

Disadvantages

EventHandler

and

EventListener

classes are predefined to work with AWT only. They can not be used to receive events from any other event sources.

Advantages

This method allows users to map a target method for each event generated on an event source. It avoids huge

if-else

blocks, and it scales well with any number of GUI components. With this approach, there is no need to use inner or anonymous classes. It results in a small class file footprint, and can be used with IDEs to reduce the total footprint of GUI classes.

ButtonTest6: Using GenericListener

The

GenericListener

utility class, developed by engineers at JavaSoft, can handle any type of listener. It is not limited to AWT and JFC. To get a sense of its full potential, take a look at

Demo.java

. My example here is a simple utility method written on top of the code Sun's engineers have already provided. For your convenience, the source code for this implementation is included in the zip file listed in the

Resources

section below.

Please note that the source included in this article has been modified to work with JDK 1.2 with the optimizer switch turned on. (By the time this article went live, Sun engineers had decided to include proxy class generation functionality in the JDK 1.3 prerelease. See Resources for more details.)

package test.event;

import java.awt.*; import java.awt.event.*; import java.util.*;

public class ButtonTest6 extends Frame { Button top = new Button("Toggle bottom button"); Button bottom = new Button("Toggle top button");

public ButtonTest6() { super("ButtonTest6 uses Generic Listener"); setLayout(new BorderLayout()); add("North", top); add("Center", bottom);

bottom.setEnabled(true); top.setEnabled(false);

GenericListener.connect(top, this,ActionListener.class, "actionPerformed", "button1Action"); GenericListener.connect(bottom,this,ActionListener.class, "actionPerformed", "button2Action"); GenericListener.connect(this,this,WindowListener.class, "windowClosing", "windowClosingMethod");

pack(); show(); } public void windowClosingMethod(WindowEvent e) { System.exit(0); }

public void function(ActionEvent event) { Button b = (Button)event.getSource(); if(b == bottom) { bottom.setEnabled(false); top.setEnabled(true); } else { bottom.setEnabled(true); top.setEnabled(false); } }

public void button1Action(ActionEvent e) { function(e); }

public void button2Action(ActionEvent e) { function(e); }

public static void main(String[] args) { new ButtonTest6(); } }

Since GenericListener generates the actual listener implementation at runtime, you must provide the class object for the listener in which you are interested. In the above example, we are passing the ActionListener.class and WindowListener.class as arguments to the connect method on the GenericListener class. connect() is a static method that uses the GenericListener implementation internally to create the listener class at runtime. These classes will be loaded through the class loader only once for each listener-event combination.

Advantages

This approach allows users to map a target method for each event generated on an event source.

GenericListener

gives us all the benefits of

EventListener

. Since it is not predefined to work with AWT or JFC, you can use this approach to work with any event-generating source (including non-UI sources). It avoids huge

if-else

blocks, and it scales well with any number of GUI components. There is no need to use inner or anonymous classes. It results in a small class file footprint, and can be used with IDEs to reduce total footprint of GUI classes. Listeners are created and loaded at runtime. In addition, IDEs and bean environments can use this approach to hook up components at runtime.

The total footprint for each of the above examples is listed in the table below. The table shows the bytes occupied by the class files for all the examples I have discussed so far. Notice that ButtonTest4 has the smallest footprint among all examples.

Comparison of example techniques
Example nameFootprint (in bytes)Remarks
ButtonTest13,253Uses anonymous class
ButtonTest23,847Uses inner class
ButtonTest31,759Main class implements all interfaces
ButtonTest41,391Uses EventHandler class
ButtonTest51,679Uses EventListener class
ButtonTest62,169Uses GenericListener class

Advantages with the extended approach

The

EventHandler

and

EventListener

are easy to use in comparison with standard approaches. There is no need to import, use, extend, or implement any interfaces or event adapters. From the table above, it is clear that the total footprint of the compiled classes is smaller when we use

EventHandler

and

EventListener

. The space taken up by the example that uses

GenericListener

is a little larger, but still smaller than the first two examples. Object diagrams for Java projects will be more readable due to the appearance of fewer inner classes. IDEs can use these utility classes to generate code that is easy to read and maintain.

Future work

Currently, the

EventHandler

class implements two modes of execution:

  • Up-front full method caching: In this mode, event delivery is fast, but the program consumes more memory.

  • No method caching: In this mode, method references are not cached, so a performance hit will be taken for every event notification. This is a bit slower than the previous mode, but it consumes less memory.

An interested soul can implement adaptive method caching. In this mode, method references that can be cached as events are generated. This mode is certainly better than either no caching or full caching.

Smaller UI implementations

This articles sums up six possible ways to handle events in AWT and JFC, and discusses the advantages and issues with each approach. You should understand that no one method is good for all situations. Care should be taken in selecting the event-handling method while developing large, UI-extensive applications. As shown above,

EventHandler

,

EventListener

, and

GenericListener

classes enable you to create compact UI implementations with a minimal amount of code.

Eliminating inner classes will lead to non-object-oriented programming. It should not be attempted unless the benefits are profound (if you develop large applets with lots of UI and want to reduce the download time, for example). The new utility classes offered in this article may be of more interest to developers who are trying to reduce class size or who need to connect objects at runtime.

Satheesh A. Donthy received his Masters degree from I.I.T. Kharagpur in India. He has been developing software using object-oriented systems since 1992. After working for four years at Intergraph developing CAD/CAM applications, he switched to consulting. Since 1996, he has been consulting in and around the Boston area with the Universal Software Corporation. Satheesh has worked on many exciting Java projects as an architect and lead developer. He is the author of Source Explorer for Java, a complete source code and bytecode browsing system. Currently, he is working with the Netscape Professional Services group to help companies deploy ecommerce products.
1 2 3 Page 2
Page 2 of 3