Creating custom event sets

The predefined AWT event sets and custom sets already defined in the JavaBeans Component Library are usually sufficient for most programming needs. On occasion, however, you might want your component to introduce behavior that is entirely different from any other. You need to create a custom event.

Here are the tasks you need to perform to create a new event set.

  1. Select a name for your event set.

    For example, you are developing a timer component and want to have timer events so you select "Timer" as the name for your event set.

  2. Define a TimerListener interface, extending java.util.EventListener or one of its subclasses, and define the one or more methods that it will have.

    For example, you might have timerTick, timerStart and timerStop methods.

  3. Define an event object derived from java.util.EventObject.

    For example, you might choose "TimerEvent."

Here are the tasks you must do to make your component capable of generating TimerEvents:

  1. Create addTimerListener() and removeTimerListener() methods in your component. The add method should attach the TimerListener passed in to a unicast or multicast event-notification mechanism. The remove method should remove a listener from the mechanism.

  2. Determine where in your component's code the event methods should be fired. Sometimes the answer seems obvious. For example, a mouseClick event occurs when the control is clicked with a mouse. But some other events are less clearly tied to specific external events. For example, which occurrences cause the modelContentChanged event of the TextField control to fire? Is it pressing the Enter key, pressing the Tab key to move to another control, both, or neither? For each event you create, you must be sure which occurrences are going to generate the event.

  3. From those appropriate points in your code, call the unicast or multicast notification mechanism to notify the listener(s) that the event method has occured.

BeansExpress can help you with these tasks. For more information, see Creating JavaBeans with BeansExpress.

Component writers can create new program-generated events only. System- or user-generated events must be passed to the component by the AWT, and may optionally be passed on to other components via event source/listener connections.

These are the topics that explain in detail how to create custom event sets, how to add them to a source component, and how to make a component listen for the new events:


Defining a new event object

The event object, which is passed from the source component to the listener component, can contain state information about the event. Often this state information isn't needed, so all that is necessary is a new class that extends java.util.EventObject. To define a new event object,

  1. Define a new class that extends java.util.EventObject or a subclass of it. The class name must end in "Event".

  2. Define any fields needed to hold state information for the event.

  3. Include a constructor that calls the constructor of its parent event object first, and then initializes any fields.

For example, the following is an event class for a timer event (a timer is a component that generates an event at specified intervals, like the ticking of a clock):

public class TimerEvent extends EventObject
{
  protected int tickCount, tickInterval;
  TimerEvent(java.awt.Component source)
  {
    super(source);
    tickCount = 0;
    tickInterval = 100;
  }
}

public int getTickCount()
{
    return tickCount;
}

public int getTickInterval()
{
    return tickInterval;
}

public void setTickInterval(int milliseconds)
{
    tickInterval = milliseconds;
}
Note that the tickCount and tickInterval fields do not have direct public access, but their values are retrieved through the public getTickCount() and getTickInterval() methods, and the tickInterval is set with the setTickInterval() method. Usually, event object states should not be changed, and using accessor methods helps accomplish this. When writing such a method, use the same conventions required for accessor methods for properties.


Defining a new event listening interface

To define a new event-listener interface for a new event set,

  1. Define an interface that extends the java.util.EventListener interface. The class name must end in "Listener".

  2. Declare an event handler method for each event in the group.

For example, this is a new event-listener interface for timer tick events:

public interface TimerListener extends EventListener
{
  public void timerTick(TimerEvent event);
  public void timerStart(TimerEvent event);
  public void timerStop(TimerEvent event);
}
This sample interface declares three timer events: one that generates a tick event at a set interval, one that occurs when the timer starts ticking, and another that occurs when the timer stops ticking.

The components you want to listen to and respond to the new events must implement the new event-listener interface. For example,

public class SomeComponent implements TimerListener
{
  public void timerTick(TimerEvent ev)
  {
    // code that does whatever you want to happen when this method is called
  }

  public void timerStart(TimerEvent ev)
  { 
    // your code goes here
  }

  public void timerStop(TimerEvent ev)
  {
    // your code goes here
  }
You can leave the bodies of the methods empty if you just want your listener component to be able to respond without any special processing. The user of your component will write the specific code that determines what happens when the event occurs.

See also:
Notifying a listening component


Notifying a listening component

If you are creating a new event, you are probably doing so because you want the component you are writing to have new behavior. So your component must be able to generate the event and notify all listening components that the event occurred.

Listening for an event occurrence shows you how a listening component registers itself with the source component by calling the source component's add event-listener method. When the source component generates an event, it notifies all those components that are registered as listeners. Therefore, you must implement an add event-listener method for a source component, and you must provide a remove event-listener method. Once these are in place, listening components can register and unregister themselves with the list of components the source component notifies when it generates an event.

If a source component has only one listening component, the process of event handling looks something like the following diagram:

Unicast event handling
Block diagram of unicast event delegation here.

The source component creates an event object and passes it to the listening component. In the diagram, an event adapter class actually makes the proper method call, but adapter classes are optional. Whether or not adapter classes are used, the source component delegates the handling of the event to the listening component.

What if you want to have several components listening (and responding) to the same event? JavaBeans also have multicast events. Event delegation in multicast events looks like this:

Multicast event handling
Block diagram of multicast event delegation here.

For component users, the difference between multicast and unicast events is invisible. Unicast events use a form of the add event-listener method that throws a TooManyListenersException if more than one component is registered as a listener. Because the registration method signature is the same for both multicast and unicast events, component users don't need to know whether an event is multicast or unicast. If a component is successfully registered as a listener, the component user can be sure the component receives the events.

AWT events and most programmer-defined events should be multicast, as they provide the greatest flexibility to your component users.


Registering a listener with the source component

JBuilder recognizes events through the event-registration methods. If you declare these methods according to the JavaBeans specifications, the events appear on the Events page of the Component Inspector and are available to the component user at design time.

This is a timer component that has the registration methods for the new timer events:

import java.awt.*;

public class MyTimer
{
  private Vector listenerList = new Vector();

  public synchronized void addTimerListener(TimerListener timerListener)
  {
    listenerList.addElement(timerListener);
  }

  public synchronized void removeTimerListener(TimerListener timerListener)
  {
    listenerList.removeElement(timerListener);
  }
  


Declaring a multicast add-listener registration method

Declare the add-listener registration method as follows:
public synchronized void add<event>Listener (<event>Listener <listener>);

The following describes the purpose of each element:

public
The method must be public so that another component can call it.
synchronized
Synchronizing event-registration methods protects the process of listener notification from race and deadlock problems in a multithreaded environment.
void
The add method returns nothing.
add<event>Listener
Name the registration method for multicast events by adding a lowercase "add" to the event-listener interface name. (If your event-registration methods are not declared according to the standard design patterns, you can use the optional BeanInfo interface to explain them to the Component Inspector.)
<event>Listener
The add method takes the new listener object as its argument. <event> is the name of the event.


Declaring a unicast add-listener registration method

Declare the add-listener registration method as follows:
public synchronized void add<event>Listener(<event>Listener <listener>) throws TooMayListeners;

The following describes the purpose of each element:

public
The method must be public so that another component can call it.
synchronized
Synchronizing event-registration methods protects the process of listener notification from race and deadlock problems in a multithreaded environment.
void
The add method returns nothing.
add<event>Listener
Name the registration method for multicast events by adding a lowercase "add" to the event-listener interface name. (If your event-registration methods are not declared according to the standard design patterns, you can use the optional BeanInfo interface to explain them to the Component Inspector.)
<event>Listener
The add method takes the new listener object as its argument. <event> is the name of the event.
throws TooManyListenersException
With a unicast event, if the source component already has a listener registered for this event set, a call to the addListener results in an exception being thrown.


Stopping a component from listening to an event

Once a component is registered to listen to another component's events, you can terminate this relationship by calling the remove event-listener method of the component that generates the event. For example, this code removes myComponent from the list of components that sourceComponent notifies when a key event occurs:
sourceComponent.removeKeyListener (myComponent);

Declaring a remove event-listener method

Declare the remove-listener registration method as follows:

public synchronized void remove<event>Listener(<event>Listener <listener>);

The following describes the purpose of each element:

public
The method must be public so that another component can call it.
synchronized
Synchronizing event-registration methods protects the process of listener notification from race and deadlock problems in a multithreaded environment.
void
The remove method returns nothing.
remove<event>Listener
Name the remove method for multicast events by adding a lowercase "remove" to the event-listener interface name. (If your event-registration methods are not declared according to the standard design patterns, you can use the optional BeanInfo interface to explain them to the Component Inspector.)
<event>Listener
The remove method takes, as its argument, the listener object to be removed from the list. <event> is the name of the event.


Sending an event to listeners

To send an event to listening components, the source component must create an event object and then pass that event object to the listeners. You must define a method that

For example, the following code is added to the MyTimer class. When the program that uses this component calls any of the process methods, a TimerEvent object is created and sent to each listening component:

import java.awt.*;

public class MyTimer
{
  private Vector listenerList = new Vector();

  public synchronized void addTimerListener(TimerListener timerListener)
  {
    listenerList.addElement(timerListener);
  }

  public synchronized void removeTimerListener(TimerListener timerListener)
  {
    listenerList.removeElement(timerListener);
  }
  
  protected void processTimerTick()
  {
    TimerEvent ev = new TimerEvent(this);                        // TimerEvent object instantiated
    for (int i = 0;  i < listenerList.size(); i++)               // for each component in the list
      ((TimerListener)listenerList.elementAt(i)).timerTick(ev);  // call its timerTick() method, passing it the event object
  }

  protected void processStartTimer()
  {
    TimerEvent ev = new TimerEvent(this);                        // TimerEvent object instantiated
    for (int i = 0;  i < listenerList.size(); i++)               // for each component in the list
      ((TimerListener)listenerList.elementAt(i)).startTimer(ev); // call its startTimer() method, passing it the event object
  }

  protected void processStopTimer()
  {
    TimerEvent ev = new TimerEvent(this);                        // TimerEvent object instantiated
    for (int i = 0;  i < listenerList.size(); i++)               // for each component in the list
      ((TimerListener)listenerList.elementAt(i)).stopTimer(ev);  // call its stopTimer() method, passing it the event object
  }
The resulting MyTimer component can now generate the three timer events. When the MyTimer component is added to an application at design time, the event-listener interface methods (in this example the three methods of TimerListener) will appear on the Events page of the Component Inspector.

See also:
Understanding event adapter classes


Understanding event adapter classes

Using event adapter classes is optional, but they can be extremely useful in complex situations where you want a component to listen to only part of an event set. Existing adapters in the java.awt package implement the event listener interface for a given set, but have empty bodies for all of the event handling methods.

Event adapters provide another advantage. If you have a component that listens to several other components that fire the same event type, but you want to treat the events differently, you can use one adapter for each source component. This way you have separate code being called for each event and you don't have to test the source components to know which one fired.

To use an event adapter in your application, instantiate it in your component and override the methods for the events you want to handle, calling the method in your component that contains the required behavior.

JBuilder uses event adapter classes when the component user links an event with a method. To see an event adapter class in action, follow these steps within JBuilder:

  1. Start a new project that includes a frame node named Frame1 that extends DecoratedFrame.

  2. Select the frame node and choose the Design tab to display the UI Designer.

    JBuilder takes you directly to the event handling method in the source code. The new empty event handler is ready for you to add your code.

  3. Drop a TextAreaControl and a ButtonControl on the frame.

  4. With the button selected, switch to the Events page of the Component Inspector.

  5. Select the mouseClicked event and double-click its value column in the Component Inspector.

  6. Enter the following code in the buttonControl1_mouseClicked event handler:
    textAreaControl1.setText("Hello out there!");
    

In this example, the frame component listens for the button to generate a mouseClicked event. When the user clicks the button and the button generates a mouseClicked event, the text in the TextAreaControl is set.

When you double-clicked the mouseClicked event in the Component Inspector, JBuilder added a number of statements to your code. Examine the source code to find these statements.

In the jbinit() method for the frame, JBuilder added the addMouseListener() method call, which makes the frame a listener component for the a mouseClick event in the ButtonControl. This is the code JBuilder generated:

buttonControl1.addMouseListener(new Frame1_buttonControl1_MouseAdapter(this));
Note that instead of Frame1 being passed to the addMouseListener() method, a Frame1_buttonControl_1MouseAdapter class is created and this new adapter class is passed to the addMouseListener() method, registering it as the listener. The this argument is passed to the constructor of the adapter class. this refers to the Frame1 component.

Near the bottom of the jbinit() method for the frame, JBuilder added a method in which you wrote your code:

void buttonControl1_mouseClicked(java.awt.event.MouseEvent e)
{
   textAreaControl1.setText("Hello out there!");    // this is the code you added
}
The statement you wrote set the text property of the TextAreaControl. The connection between the two statements is in the adapter class itself. Finally, below the jbinit() method, is the definition of the adapter class:
class Frame1_ButtonControl1_MouseAdapter extends MouseAdapter
{
  Frame1 adaptee;
  Frame1_buttonControl1_MouseAdapter(Frame1 adaptee)
 {
    this.adaptee = adaptee;
  }

  // override the method for the desired event, calling the method in Frame1
  public void mouseClicked(java.awt.event.MouseEvent e)
  {
    adaptee.buttonControl1_mouseClicked(e);
  }
}
JBuilder creates the adapter class and overrides a single method. The method that handles the selected event calls the buttonControl1_mouseClicked() method in Frame1 to do the actual responding to the mouse click. The result is that the frame listens and responds to mouseClick events only and not mousePressed and mouseReleased events.

Note
JBuilder can use another style of adapters called anonymous adapters. You may find this style easier to work with. For information about anonymous adapters, see Using anonymous adapters.


Listening for an event occurrence

It is rare that you will need to make your component an event listener, although you can use event source/listener connections internally between subcomponents as you build up a composite component.

Your components are seldom listeners because JBuilder usually creates event listeners automatically in the container class where your components are used. If, however, you decide you want your component to be able to listen for particular events, you must implement the appropriate Listener interfaces for the event sets you choose.

For example, to make your component a key event listener, implement the KeyListener interface, an interface that extends the java.util.EventListener interface:

  1. Add the KeyListener interface to your component with the implements statement. For example,

    public class myComponent extends java.awt.Component implements KeyListener
    
  2. Write the implementation of the interface methods:

    public void keyTyped(KeyEvent event)
    {
      // your implementation goes here
    }
    
    public void keyPressed(KeyEvent event)
    {
      // your implementation goes here
    }
    
    public void keyReleased(KeyEvent event)
    {
      // your implementation goes here
    }
    

Within each of these methods, write the code to perform any special processing you want to occur. You can leave the body of the method empty, if you just want your component to be able to respond without any special processing. The user of your component can write the specific code that determines what happens when the event occurs.

Now your component is capable of listening for an event. The only question that remains is which source component is your component listening for key events in. To specify which source component your component listens to, some piece of code must call the add event-listener registration method of the source component passing your listening component as a parameter. The name of the registration method is the name of the event interface prefaced with the word "add." For example, to register a component for notification of key events, call the addKeyListener() method. The addKeyListener() method adds your component to the list of components the source component notifies when it generates a key event.

The add-listener method could be called in several ways. It could be called by a source component when it creates various listening components, attaching them to itself. It could be called by a listening component that automatically attaches itself to some event source. But usually your component is only an event source JBuilder attaches action adapters as listeners to its events when the user uses the Events page of the Component Inspector. JBuilder generates action adapters and delegates the actual event handling to a method in the container class being designed.

To understand the need for this, consider that when you are writing a component that a user will use to build applications, you don't know how the component will be used. Therefore, you really can't register your component at the time you create it. That is a task for the component user. When the component user selects an event for a control and double-clicks it on the Events page of the Component Inspector, JBuilder automatically adds the event listening (adapter class) code to the source file of the container class being designed. It also adds to the container class a call to the add event-listener method of your component. This registers the adapter as the listener, and the adapter acts as a connector, transfering the event from your component to some container method.


Events in the JavaBeans Component Library

To facilitate component development, a number of custom event sets have been defined in JBCL. They reside in the dataset, model, and view packages.

These event sets are defined in the dataset package:

These event sets are defined in the model package, and depend upon the specific model:

These event sets are defined in the view package:

See also: Java AWT events