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” 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()
.