Java Tip 35: Create new event types in Java

Learn how to make event classes for components in JDK 1.1

While JDK 1.1 certainly has streamlined event handling with the introduction of the delegation event model, it does not make it easy for developers to create their own event types. The basic procedure described here is actually rather straightforward. For the sake of simplicity, I will not discuss concepts of event enabling and event masks. Plus, you should know that events created using this procedure will not be posted to the event queue and will work only with registered listeners.

Currently, the Java core consists of 12 event types defined in java.awt.events:

  • ActionEvent
  • AdjustmentEvent
  • ComponentEvent
  • ContainerEvent
  • FocusEvent
  • InputEvent
  • ItemEvent
  • KeyEvent
  • MouseEvent
  • PaintEvent
  • TextEvent
  • WindowEvent

Because creating new event types is a non-trivial task, you should examine events that are part of core Java. If possible, try to use those types rather than create new ones.

There will be times, however, when a new event type will need to be developed for a new component. For purposes of this discussion, I will use the example of a simple component, a wizard panel, as a means to demonstrate how to create a new event type.

A wizard panel implements a simple wizard interface. The component consists of a card panel that can be advanced using the NEXT button. The BACK button allows you to flip to the previous panel. FINISH and CANCEL buttons are also provided.

In order to make the component flexible, I wanted to provide full control over the actions taken by all the buttons to the developer who uses it. For example, when the NEXT button is pressed, it should be possible for the developer to first check if the required data was entered on the component currently visible before advancing to the next component.

There are five main tasks in creating your own event type:

  • Create an event listener

  • Create a listener adapter

  • Create an event class

  • Modify the component

  • Managing multiple listeners

We'll examine each of these tasks in turn and then put them all together.

Create an event listener

One way (and there are many) of informing objects that a certain action has occurred is to create a new event type that could be delivered to registered listeners. In the case of the wizard panel, a listener should support four different event cases, one for each button.

I start by creating a listener interface. For each button, I define a listener method in the following fashion:

import java.util.EventListener;
public interface WizardListener extends EventListener {
    public abstract void nextSelected(WizardEvent e);
    public abstract void backSelected(WizardEvent e);
    public abstract void cancelSelected(WizardEvent e);
    public abstract void finishSelected(WizardEvent e);
}

Each method takes one argument: WizardEvent, which is defined next. Note that the interface extends EventListener, used to identify this interface as an AWT listener.

Create a listener adapter

Creating a listener adapter is an optional step. In AWT, a listener adapter is a class that provides a default implementation for all methods of a certain listener type. All adapter classes in the java.awt.event package provide empty methods that do nothing. Here is an adapter class for WizardListener:

public class WizardAdapter implements WizardListener {
    public void nextSelected(WizardEvent e) {}
    public void backSelected(WizardEvent e) {}
    public void cancelSelected(WizardEvent e) {}
    public void finishSelected(WizardEvent e) {}
}

When writing a class that is to be a wizard listener, it is possible to extend the WizardAdapter and provide implementation for (or override) only those listener methods that are of interest. This is strictly a convenience class.

Create an event class

The next step is to create the actual Event class here: WizardEvent.

import java.awt.AWTEvent;
public class WizardEvent extends AWTEvent {
    public static final int WIZARD_FIRST = AWTEvent.RESERVED_ID_MAX + 1;
    public static final int NEXT_SELECTED   = WIZARD_FIRST;
    public static final int BACK_SELECTED   = WIZARD_FIRST + 1;
    public static final int CANCEL_SELECTED = WIZARD_FIRST + 2;
    public static final int FINISH_SELECTED = WIZARD_FIRST + 3;
    public static final int WIZARD_LAST = WIZARD_FIRST + 3;
    public WizardEvent(Wizard source, int id) {
        super(source, id);
    }
}

Two constants, WIZARD_FIRST and WIZARD_LAST, mark the inclusive range of masks used by this Event class. Note that the event IDs use the RESERVED_ID_MAX constant of class AWTEvent to determine the range of IDs that will not conflict with the event ID values defined by the AWT. As more AWT components are added, the RESERVED_ID_MAX may increase in the future.

The remaining four constants represent four event IDs, each corresponding to a different action type, as defined by the wizard's functionality.

Event ID and event source are two arguments for the wizard event constructor. Event source must be of type Wizard -- that is the component type the event is defined for. The reasoning is that only a wizard panel can be a source of wizard events. Note that the WizardEvent class extends AWTEvent.

Modify the component

The next step is to equip our component with methods allowing it to register and remove listeners for the new event.

To deliver an event to a listener, normally one would call the appropriate event listener method (depending on the event mask). I can register an action listener to receive action events from the NEXT button and relay them to registered WizardListener objects. The actionPerformed method of the action listener for the NEXT (or other actions) button could be implemented as follows:

<a name=actionPerformed>

public void actionPerformed(ActionEvent e) {
    //do nothing if no listeners are registered
    if (wizardListener == null) return;
    WizardEvent w;
    Wizard source = this;
    if (e.getSource() == nextButton) {
       w = new WizardEvent(source, WizardEvent.NEXT_SELECTED);
       wizardListener.nextSelected(w);
    }
    //handle the rest of the wizard buttons in a similar fashion
}

Note: In the above example, the Wizard panel itself is the listener for the NEXT button.

When the NEXT button is pressed, a new WizardEvent is created with the appropriate source and mask that corresponds to the NEXT button being pressed.

In the example, the line

              wizardListener.nextSelected(w);

refers to the wizardListener object that is a private member variable for Wizard and is of type WizardListener. We have defined this type as the first step in creating a new component event.

At first glance, the code above seems to restrict the number of listeners to one. The private variable wizardListener is not an array, and only one nextSelected call is made. To explain why the code above actually does not pose that restriction, let's examine how listeners are added.

Each new component that generates events (predefined or new) needs to provide two methods: one to support listener addition and one to support listener removal. In the case of the Wizard class, these methods are:

<a name=add_remove>

    public synchronized void addWizardListener(WizardListener l) {
        wizardListener = WizardEventMulticaster.add(wizardListener, l);
    }
    public synchronized void removeWizardListener(WizardListener l) {
        wizardListener = WizardEventMulticaster.remove(wizardListener, l);
    }

Both methods make a call to static method members of class WizardEventMulticaster.

Managing multiple listeners

While it is possible to use a Vector to manage multiple listeners, JDK 1.1 defines a special class for maintaining a listener list: AWTEventMulticaster. A single multicaster instance maintains references to two listener objects. Because the multicaster is also a listener itself (it implements all listener interfaces), each of the two listeners it keeps track of can also be multicasters, thus creating a chain of event listeners or multicasters:

If a listener is also a multicaster, then it represents a link in the chain. Otherwise, it is merely a listener and is thus the last element in the chain.

Unfortunately, it is not possible simply to reuse the AWTEventMulticaster to handle event multicasting for new event types. The best that can be done is to extend the AWT multicaster, although this operation is rather questionable. AWTEventMulticaster contains 56 methods. Of these, 51 methods provide support for the 12 event types and their corresponding listeners that are part of AWT. If you subclass AWTEventMulticaster, you will never use them anyway. Out of the remaining five methods, addInternal(EventListener, EventListener), and remove(EventListener) need to be recoded. (I say recoded because in AWTEventMulticaster, addInternal is a static method and therefore cannot be overloaded. For reasons unknown to me at this time, remove makes a call to addInternal and it needs to be overloaded.)

Two methods, save and saveInternal, provide support for object streaming and can be reused in the new multicaster class. The last method that supports listener remove routines, removeInternal, can also be reused, provided that new versions of remove and addInternal have been implemented.

For the sake of simplicity, I am going to subclass AWTEventMulticaster, but with very little effort, it is possible to code remove, save, and saveInternal and have a fully functional, standalone event multicaster.

Here is the event multicaster as implemented to handle WizardEvent:

import java.awt.AWTEventMulticaster;
import java.util.EventListener;
public class WizardEventMulticaster
    extends AWTEventMulticaster
    implements WizardListener
{
    protected WizardEventMulticaster(EventListener a, EventListener b) {
        super(a, b);
    }
    public static WizardListener add(WizardListener a, WizardListener b) {
        return (WizardListener) addInternal(a, b);
    }
    public static WizardListener remove(WizardListener l,
                                        WizardListener oldl) {
        return (WizardListener) removeInternal(l,oldl);
    }
    public void nextSelected(WizardEvent e) {
        //casting exception will never occur in this case
        //casting _is_ needed because this multicaster may
        //handle more than just one listener
        if (a != null) ((WizardListener) a).nextSelected(e);
        if (b != null) ((WizardListener) b).nextSelected(e);
    }
    public void backSelected(WizardEvent e) {
        if (a != null) ((WizardListener) a).backSelected(e);
        if (b != null) ((WizardListener) b).backSelected(e);
    }
    public void cancelSelected(WizardEvent e) {
        if (a != null) ((WizardListener) a).cancelSelected(e);
        if (b != null) ((WizardListener) b).cancelSelected(e);
    }
    public void finishSelected(WizardEvent e) {
        if (a != null) ((WizardListener) a).finishSelected(e);
        if (b != null) ((WizardListener) b).finishSelected(e);
    }
    protected static EventListener addInternal(EventListener a,
                                               EventListener b) {
        if (a == null) return b;
        if (b == null) return a;
        return new WizardEventMulticaster(a, b);
    }
    protected EventListener remove(EventListener oldl) {
        if (oldl == a) return b;
        if (oldl == b) return a;
        EventListener a2 = removeInternal(a, oldl);
        EventListener b2 = removeInternal(b, oldl);
        if (a2 == a && b2 == b) return this;
        return addInternal(a2, b2);
    }
}

Methods in the multicaster class: A review

Let's review the methods that are part of the multicaster class above. The constructor is protected, and in order to obtain a new WizardEventMulticaster, a static add(WizardListener, WizardListener) method must be called. It takes two listeners as arguments that represent two pieces of a listener chain to be linked:

  • To start a new chain, use null as the first argument.

  • To add a new listener, use the existing listener as the first argument and a new listener as the second argument.

This, in fact, is what has been done in the code for class Wizard that we have already examined.

Another static routine is remove(WizardListener, WizardListener). The first argument is a listener (or listener multicaster), and the second is a listener to be removed.

Four public, non-static methods were added to support event propagation through the event chain. For each WizardEvent case (that is, next, back, cancel, and finish selected) there is one method. These methods must be implemented since the WizardEventMulticaster implements WizardListener, which in turn requires the four methods to be present.

How it all works together

Let's now examine how the multicaster actually is used by the Wizard. Let's suppose a wizard object is constructed and three listeners are added, creating a listener chain.

Initially, the private variable wizardListener of class Wizard is null. So when a call is made to WizardEventMulticaster.add(WizardListener, WizardListener), the first argument, wizardListener, is null and the second is not (it does not make sense to add a null listener). The add method, in turn, calls addInternal. Since one of the arguments is null, the return of addInternal is the non-null listener. The return propagates to the add method that returns the non-null listener to the addWizardListener method. There the wizardListener variable is set to the new listener being added.

1 2 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more