Java ScreenShot
Screenshot      

Screenshot Core Java 2: Volume I - Fundamentals

Table of Contents
 8.  Event Handling


Event Queue

When the operating environment generates an event in response to a user action such as a mouse click, the part of the AWT that communicates with the operating environment receives a notification and turns it into an AWT event. The AWT then deposits the event into an event queue. The part of the AWT that dispatches events to listeners:

  • Fetches events from the event queue;
  • Locates the listener object for that event;
  • Invokes the appropriate listener procedure for that event.

An event queue is important for performance reasons. Events that occur frequently (such as mouse moves) or that are slow to carry out (such as painting) can be combined in the queue. If the program has not managed to extract mouse move or paint events and a new event is inserted, then the AWT can combine it with the existing event to make a single, new event. For example, we can have the new mouse position update the old one, or a new paint event can contain a request to repaint the combined areas of the old paint events. Occasionally, it is useful to manipulate the event queue directly. For example, you can remove events from the queue, thereby bypassing how events would normally be delivered. Or, you can add new events into the queue, allowing a richer event handling than is possible in the basic Java event model. You obtain an object representing the event queue by using the method call

EventQueue queue
 = Toolkit.getDefaultToolkit().getSystemEventQueue();


You insert a new event into the event queue with the postEvent method:

queue.postEvent(new ActionEvent(this,
 ActionEvent.ACTION_PERFORMED, "Blue"));


You remove an event with the getNextEvent method. The peekEvent method returns the next event in the queue, but it does not remove it.

Java graphics notes_icon.gif

Inserting or removing events is an advanced technique. If performed improperly or maliciously, it can wreak havoc with an app. For that reason, applets—the Java apps that are downloaded from foreign computers and run inside your browser—are not allowed access to the system event queue.

java.awt.EventQueue 1.1

Java graphics api_icon.gif
  • AWTEvent peekEvent()

    returns a reference to the AWTEvent object that describes the next event.

  • AWTEvent getNextEvent()

    returns a reference to the AWTEvent object that describes the next event and removes it from the queue.

  • void postEvent(AWTEvent anEvent)

    places the event on the event queue.

    Parameters:

    anEvent

    The event you want to post

Adding Custom Events

In the last section of this chapter, we will do some fairly sophisticated programming. We want to show you how to build a custom event type that you can insert into the AWT event queue and then have it dispatched to a listener, just like regular AWT events. For the example in this section, we will implement our own timer. The timer sends an event to its listener whenever a certain time interval has elapsed. For this event, we make a new event type that we call TimerEvent. The associated listener will have one method, called timeElapsed. Using our timer is simple. Construct a timer object and specify the interval (in milliseconds) in the constructor. Then, add a listener. The listener will be notified whenever the time interval has elapsed. Here is how you can put the timer to work:

Timer t = new Timer(100);
 // deliver timer clicks every 100 milliseconds TimerListener listener = . . .;
t.addTimerListener(listener);
 // notify the timeElapsed method of this class


You need to define a class that implements the TimerListener interface:

class TimerAction implements TimerListener
{
 public void timeElapsed(TimerEvent event)
 {
 // this code is executed every 100 milliseconds
 }
}


Java graphics notes_icon.gif

As you know from , the Swing package has its own Timer class, which is slightly different from ours. The Swing timer does not introduce a new event type but instead sends action events to the listener. More importantly, the Swing timer does not smuggle its events inside the AWT queue but keeps its own separate queue for timer events. We are implementing our own class to show you how to add new event types to the AWT, not to build a better timer. If you need a timer in your own code, you should simply use the Swing timer, not ours.

Now, let us see how to implement a custom event. Whenever you define a custom event, you need three ingredients:
  • an event type. We will define a TimerEvent class.
  • an event listener interface. We will use:
    public interface TimerListener extends EventListener
    {
     void timeElapsed(TimerEvent event);
    }
    


  • an event source (that is, our Timer). Listeners are attached to the event source.

The TimerEvent class is pretty simple:

  • It extends the AWTEvent superclass since all events in the AWT event queue must have type AWTEvent.
  • The constructor for the timer event receives the object that is the source of the event (that is, the timer object).

We also need to give an event ID number to the superclass. It does not matter what positive integer we choose, as long as we stay outside the range that the AWT uses for its own events. How to find an unused ID? To quote the SDK documentation: "Programs should choose event ID values which are greater than the integer constant java.awt.AWTEvent.RESERVED_ID_MAX."

class TimerEvent extends AWTEvent
{
 public TimerEvent(Timer t) { super(t, TIMER_EVENT); }
 public static final int TIMER_EVENT =
 AWTEvent.RESERVED_ID_MAX + 5555;
}


Finally, we need to implement the Timer class itself. The AWT event mechanism requires that event sources extend the class Component. Normally, components are user interface elements that are placed inside a window. We will simply take the attitude that a timer is an invisible component and have it extend the JComponent class. To write the code that constructs the interval that the timer "ticks," we need to use threads. (Threads are discussed in the second volume of this tutorial, so you will need to take the thread handling code on faith for now.) Whenever the specified time interval has elapsed, we make a new timer event and insert it into the event queue. Here's the code for this, with the pieces that are needed to post the event in bold:

class Timer extends JComponent implements Runnable
{
 public Timer(int i)
 {
 interval = i;
 Thread t = new Thread(this);
 t.start();
 }
 public void run()
 {
 while (true)
 {
 try { Thread.sleep(interval); }
 catch(InterruptedException e) {}
 EventQueue queue
 = Toolkit.getDefaultToolkit().getSystemEventQueue();
 TimerEvent event = new TimerEvent(this);
 queue.postEvent(event);
 }
 }
 . . .
 private int interval;


After this code is processed, our custom timer events are inserted into the queue. Event delivery is not automatic however, so our custom timer event will not be sent to anyone without additional code. How do we make sure our custom event is sent to interested parties? The answer is that is the responsibility of the event source to:

  • Manage the listeners for the events that it generates;
  • Dispatch the events to the listeners that are registered for them.

Event management is a common task, and the Swing designers provide a convenience class EventListenerList to make it easy to implement the methods for adding and removing listeners and for firing events. The class takes care of the tricky details that can arise when multiple threads attempt to add, remove, or dispatch events at the same time. Because some event sources accept listeners of multiple types, each listener in the event listener list is associated with a particular class. The add and remove methods are intended for the implementation of addXxxListener methods. For example,

public void addTimerListener(TimerListener listener)
{
 listenerList.add(TimerListener.class, listener);
}
public void removeTimerListener(TimerListener listener)
{
 listenerList.remove(TimerListener.class, listener);
}


Java graphics notes_icon.gif

You may wonder why the EventListenerList doesn't simply check which interface the listener object implements. But it is possible for an object to implement multiple interfaces. For example, it is possible that listener happens to implement both the TimerListener and the ActionListener interface, but a programmer may choose only to add it as a TimerListener by calling the addTimerListener. The EventListenerList must respect that choice.

Whenever the AWT removes an event from the queue, it calls the processEvent method. For timers, we define that method to call the timeElapsed method on the single listener. We use the getListeners method of the EventListenerList class to obtain all timer listeners. Then we call the timeElapsed method for each of them:
public void processEvent(AWTEvent event)
{
 if (event instanceof TimerEvent)
 {
 EventListener[] listeners = listenerList.getListeners(
 TimerListener.class);
 for (int i = 0; i < listeners.length; i++)
 ((TimerListener)listeners[i]).timeElapsed(
 (TimerEvent)event);
 }
 else super.processEvent(event);
}


Java graphics notes_icon.gif

The getListeners method is a J2SE 1.3 feature. Use getListenerList if you use J2SE 1.2.

Java graphics caution_icon.gif

Our timer extends the JComponent class. If you instead use the Component class as the superclass, you will run into a problem. The AWT code that removes events from the queue and dispatches them to the event source will deliver them only if it is convinced that the component supports the new event model. One way to convince it is to call the enableEvents method in the Component class. This method takes a parameter that gives a mask for the AWT events that you want to enable for this component. If you don't care about AWT events at all—as is the case for a source of custom events—you can pass a mask of 0. Therefore, if your event source extends the Component class, you should place a call enableEvents(0) into the constructor.

As you can see, it is possible to add custom events to the AWT mechanism using relatively little code. Example 8-7 shows the complete source code of a sample program that uses the timer. For fun, we generate a new random circle and shift the screen display down a pixel with every timer tick. The effect is an animation that simulates rainfall. (See Screenshot-11.)
Screenshot-11. Using custom timer events to simulate rainfall

Java graphics 08fig11.gif


This ends our discussion of event handling. In the next chapter, you will learn more about user interface components. Of course, to program user interfaces, you will put your knowledge of event handling to work by capturing the events that the user interface components generate.

Example 8-7 CustomEventTest.java
 1. import java.awt.*;
 2. import java.awt.geom.*;
 3. import java.util.*;
 4. import java.awt.event.*;
 5. import javax.swing.*;
 6. import javax.swing.event.*;
 7.
 8. public class CustomEventTest
 9. {
 10. public static void main(String[] args)
 11. {
 12. CustomEventFrame frame = new CustomEventFrame();
 13. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 14. frame.show();
 15. }
 16. }
 17.
 18. /**
 19. A frame with a panel that displays falling raindrops
 20. */
 21. class CustomEventFrame extends JFrame
 22. {
 23. public CustomEventFrame()
 24. {
 25. setTitle("CustomEventTest");
 26. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
 27.
 28. // add frame to panel
 29.
 30. CustomEventPanel panel = new CustomEventPanel();
 31. Container contentPane = getContentPane();
 32. contentPane.add(panel);
 33. }
 34.
 35. public static final int DEFAULT_WIDTH = 300;
 36. public static final int DEFAULT_HEIGHT = 200;
 37. }
 38.
 39. /**
 40. A panel that displays falling rain drops
 41. */
 42. class CustomEventPanel extends JPanel
 43. {
 44. public CustomEventPanel()
 45. {
 46. y = 0;
 47. circles = new ArrayList();
 48.
 49. Timer t = new Timer(100);
 50. TimerAction listener = new TimerAction();
 51. t.addTimerListener(listener);
 52. }
 53.
 54. public void paintComponent(Graphics g)
 55. {
 56. super.paintComponent(g);
 57. Graphics2D g2 = (Graphics2D)g;
 58.
 59. // translate the origin to create illusion of falling drops
 60. g2.translate(0, y);
 61.
 62. // draw all circles
 63. for (int i = 0; i < circles.size(); i++)
 64. g2.draw((Ellipse2D)circles.get(i));
 65. }
 66.
 67. private ArrayList circles;
 68. private int y;
 69.
 70. private class TimerAction implements TimerListener
 71. {
 72. public void timeElapsed(TimerEvent event)
 73. {
 74. if (getWidth() == 0) return; // panel not yet shown
 75.
 76. // add another circle
 77. int x = generator.nextInt(getWidth());
 78. Ellipse2D circle = new Ellipse2D.Double(x, -y,
 79. SIZE, SIZE);
 80. circles.add(circle);
 81.
 82. // shift up the origin
 83. y++;
 84.
 85. repaint();
 86. }
 87.
 88. private Random generator = new Random();
 89. private static final int SIZE = 9;
 90. }
 91. }
 92.
 93. /**
 94. A custom event class.
 95. */
 96. class TimerEvent extends AWTEvent
 97. {
 98. public TimerEvent(Timer t) { super(t, TIMER_EVENT); }
 99. public static final int TIMER_EVENT
100. = AWTEvent.RESERVED_ID_MAX + 5555;
101. }
102.
103. /**
104. A custom event listener interface.
105. */
106. interface TimerListener extends EventListener
107. {
108. public void timeElapsed(TimerEvent event);
109. }
110.
111. /**
112. A custom timer class that is the source of timer events.
113. */
114. class Timer extends JComponent implements Runnable
115. {
116. public Timer(int i)
117. {
118. listenerList = new EventListenerList();
119. interval = i;
120. Thread t = new Thread(this);
121. t.start();
122. }
123.
124. /**
125. Adds a timer listener
126. @param listener the listener to add
127. */
128. public void addTimerListener(TimerListener listener)
129. {
130. listenerList.add(TimerListener.class, listener);
131. }
132.
133. /**
134. Removes a timer listener
135. @param listener the listener to remove
136. */
137. public void removeTimerListener(TimerListener listener)
138. {
139. listenerList.remove(TimerListener.class, listener);
140. }
141.
142.
143. /**
144. Posts a new timer event every <code>interval</code>
145. milliseconds.
146. */
147. public void run()
148. {
149. while (true)
150. {
151. try { Thread.sleep(interval); }
152. catch(InterruptedException e) {}
153.
154. TimerEvent event = new TimerEvent(this);
155.
156. EventQueue queue
157. = Toolkit.getDefaultToolkit().getSystemEventQueue();
158. queue.postEvent(event);
159. }
160. }
161.
162. public void processEvent(AWTEvent event)
163. {
164. if (event instanceof TimerEvent)
165. {
166. EventListener[] listeners = listenerList.getListeners(
167. TimerListener.class);
168. for (int i = 0; i < listeners.length; i++)
169. ((TimerListener)listeners[i]).timeElapsed(
170. (TimerEvent)event);
171. }
172. else
173. super.processEvent(event);
174. }
175.
176. private int interval;
177. private EventListenerList listeners;
178. }


java.swing.event.EventListenerList 1.2

Java graphics api_icon.gif
  • void add(Class t, EventListener l)

    adds an event listener and its class to the list. The class is stored so that event firing methods can selectively call events. Typical usage is in an addXxxListener method:

    public void addXxxListener(XxxListener l)
    {
     listenerList.add(XxxListener.class, l);
    }
    


    Parameters:

    t

    The listener type

     

    l

    The listener

  • void remove(Class t, EventListener l)

    removes an event listener and its class from the list. Typical usage is in a removeXxxListener method:

    public void removeXxxListener(XxxListener l)
    {
     listenerList.remove(XxxListener.class, l);
    }
    


    Parameters:

    t

    The listener type

     

    l

    The listener

  • EventListener[] getListeners(Class t) 1.3

    returns an array of all the listeners of the given type. The array is guaranteed to be non-null.

  • Object[] getListenerList()

    returns an array whose elements with even-numbered index are listener classes, and whose elements with odd-numbered index are listener objects. The array is guaranteed to be non-null.

java.awt.Component 1.0

Java graphics api_icon.gif
  • void enableEvents(long maskForEvents) 1.1

    enables the component to insert events into the event queue even when there is no listener for a particular event type.

    Parameters:

    maskForEvents

    A mask of event types to enable, made up of constants, such as ACTION_EVENT_MASK, that are defined in the AWTEvent class.


Java ScreenShot
Screenshot      
Top
 

Comments