2.4. Events and Listeners

When a user does something, such as clicks a button or selects an item, the application needs to know about it. Many Java-based user interface frameworks follow the Observer design pattern to communicate user input to the application logic. So does IT Mill Toolkit. The design pattern involves two kinds of elements: an object and a number of observers that listen for events regarding the object. When an event related to the object occurs, the observers receive a notification regarding the event. In most cases there is only one observer, defined in the application logic, but the pattern allows for multiple observers. As in the event-listener framework of Java SE, we call the observing objects listeners.

In the ancient times of C programming, callback functions filled largely the same need as listeners do now. In object-oriented languages, we have only classes and methods, not functions, so the application has to give a class interface instead of a callback function pointer to the framework. However, IT Mill Toolkit supports defining a method as a listener as well.

Events can serve many kinds of purposes. In IT Mill Toolkit, the usual purpose of events is handling user interaction in a user interface. Session management can require special events, such as time-out, in which case the event is actually the lack of user interaction. Time-out is a special case of timed or scheduled events, where an event occurs at a specific date and time or when a set time has passed. Database and other asynchronous communications can cause events too.

To receive events of a particular type, an application must include a class that implements the corresponding listener interface. In small applications, the application class itself could implement the needed listener interfaces. Listeners are managed by the AbstractComponent class, the base class of all user interface components. This means that events regarding any component can listened to. The listeners are registered in the components with addListener() method.

Most components that have related events define their own event class and corresponding listener classes. For example, the Button has Button.ClickEvent events, which can be listened to through the Button.ClickListener interface. This allows an application to listen to many different kinds of events and to distinguish between them at class level. This is usually not enough, as applications usually have many components of the same class and need to distinguish between the particular components too. We will look into that more closely below. The purpose of this sort of class level separation is to avoid having to make type conversions in the handlers.

Notice that many listener interfaces inherit the java.util.EventListener superinterface, but it is not generally necessary to inherit it.

Figure 2.3. Class Diagram of a Button Click Listener

Class Diagram of a Button Click Listener

Figure 2.3, “Class Diagram of a Button Click Listener” illustrates an example where an application-specific class inherits the Button.ClickListener interface to be able to listen for button click events. The application must instantiate the listener class and register it with addListener(). When an event occurs, an event object is instantiated, in this case a ClickEvent. The event object knows the related UI component, in this case the Button.

The following example follows a typical pattern where you have a Button component and a listener that handles user interaction (clicks) communicated to the application as events. Here we define a class that listens click events.

public class TheButton implements Button.ClickListener {
    Button thebutton;

    /** Creates button into given container. */
    public TheButton(AbstractComponentContainer container) {
        thebutton = new Button ("Do not push this button");
        thebutton.addListener(this);
        container.addComponent(thebutton);
    }
    
    /** Handle button click events from the button. */
    public void buttonClick (Button.ClickEvent event) {
        thebutton.setCaption ("Do not push this button again");
    }
}

As an application often receives events for several components of the same class, such as multiple buttons, it has to be able to distinguish between the individual components. There are several techniques to do this, but probably the easiest is to use the property of the received event, which is set to the object sending the event. This requires keeping at hand a reference to every object that emits events.

public class TheButtons implements Button.ClickListener {
    Button thebutton;
    Button secondbutton;

    /** Creates two buttons in given container. */
    public TheButtons(AbstractComponentContainer container) {
        thebutton = new Button ("Do not push this button");
        thebutton.addListener(this);
        container.addComponent(thebutton);
        
        secondbutton = new Button ("I am a button too");
        secondbutton.addListener(this);
        container.addComponent (secondbutton);
    }
    
    /** Handle button click events from the two buttons. */
    public void buttonClick (Button.ClickEvent event) {
        if (event.getButton() == thebutton)
            thebutton.setCaption ("Do not push this button again");
        else if (event.getButton() == secondbutton)
            secondbutton.setCaption ("I am not a number");
    }
}

Another solution to handling multiple events of the same class involves attaching an event source to a listener method instead of the class. An event can be attached to a method using another version of the addListener() method, which takes the event handler method as a parameter either as a name of the method name as a string or as a Method object. In the example below, we use the name of the method as a string.

public class TheButtons2 {
    Button thebutton;
    Button secondbutton;

    /** Creates two buttons in given container. */
    public TheButtons2(AbstractComponentContainer container) {
        thebutton = new Button ("Do not push this button");
        thebutton.addListener(Button.ClickEvent.class, this, "theButtonClick");
        container.addComponent(thebutton);
        
        secondbutton = new Button ("I am a button too");
        secondbutton.addListener(Button.ClickEvent.class, this, "secondButtonClick");
        container.addComponent (secondbutton);
    }
    
    public void theButtonClick (Button.ClickEvent event) {
        thebutton.setCaption ("Do not push this button again");
    }

    public void secondButtonClick (Button.ClickEvent event) {
        secondbutton.setCaption ("I am not a number!");
    }
}

Adding a listener method with addListener() is really just a wrapper that creates a com.itmill.toolkit.event.ListenerMethod listener object, which is an adapter from a listener class to a method. It implements the java.util.EventListener interface and can therefore work for any event source using the interface. Notice that not all listener classes necessarily inherit the EventListener interface.

The third way, which uses anonymous local class definitions, is often the easiest as it does not require cumbering the managing class with new interfaces or methods. The following example defines an anonymous class that inherits the Button.ClickListener interface and implements the buttonClick() method.

public class TheButtons3 {
    Button thebutton;
    Button secondbutton;

    /** Creates two buttons in given container. */
    public TheButtons3(AbstractComponentContainer container) {
        thebutton = new Button ("Do not push this button");
        thebutton.addListener(new Button.ClickListener() {
            /* Define the method in the anonymous class to handle the click. */
            public void buttonClick(ClickEvent event) {
                thebutton.setCaption ("Do not push this button again");
            }
        });
        container.addComponent(thebutton);
        
        secondbutton = new Button ("I am a button too");
        secondbutton.addListener(new Button.ClickListener() {
            /* Define the method in the anonymous class to handle the click. */
            public void buttonClick(ClickEvent event) {
                secondbutton.setCaption ("I am not a number!");            
            }
        });
        container.addComponent (secondbutton);
    }
}

Other techniques for separating between different sources also exist. They include using object properties, names, or captions to separate between them. Using captions or any other visible text is generally discouraged, as it may create problems for internationalization. Using other symbolic strings can also be dangerous, because the syntax of such strings is checked only runtime.

Events are usually emitted by the framework, but applications may need to emit them too in some situations, such as when updating some part of the UI is required. Events can be emitted using the fireEvent(Component.Event) method of AbstractComponent. The event is then relayed to all the listeners of the particular event class for the object. Some components have a default event type, for example, a Button has a nested Button.ClickEvent class and a corresponding Button.ClickListener interface. These events can be triggered with fireComponentEvent().