Currently, the Java core consists of 12 event types defined in java.awt.events:
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:
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.
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.
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.
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.
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.