Page 3 of 3
Meanwhile, back in your event queue, processing of the dispatched event will eventually be completed (hopefully!), at which
point the stopTimer() method will be invoked.
34: synchronized void stopTimer() {
35: if (parent == null)
36: interrupt();
37: else {
38: parent.setCursor(null);
39: parent = null;
40: }
41: }
If the parent instance variable for your timer thread is still null, then you can be certain that the timer has not yet changed
the cursor to an hourglass. In this situation, you are required to interrupt only the timer thread, which will throw an InterruptedException within the thread. Execution of the run() method will then immediately drop to the catch block near the bottom of the loop, at which point it will loop back to the
wait() statement and wait for notification of the next event.
Now, to use your queue, you just need to replace the standard event queue with an instance of the special WaitCursorEventQueue, which can be accomplished with the following few lines of code.
EventQueue waitQueue = new WaitCursorEventQueue(500);
Toolkit.getDefaultToolkit().getSystemEventQueue().push(waitQueue);
Part of the trick to this tip is choosing the duration after which the hourglass should appear. The delay should be long enough that the hourglass will not appear for most events processed within the UI thread, but it should be short enough so that the user perceives near-immediate feedback when a more intensive task begins processing. In moderate usability testing, a delay in the range of about 500 milliseconds, as shown above, appears to work quite well.
Below is the complete source code for WaitCursorEventQueue.java.
1: import java.awt.*;
2: import java.awt.event.*;
3: import javax.swing.SwingUtilities;
4:
5: public class WaitCursorEventQueue extends EventQueue {
6:
7: public WaitCursorEventQueue(int delay) {
8: this.delay = delay;
9: waitTimer = new WaitCursorTimer();
10: waitTimer.setDaemon(true);
11: waitTimer.start();
12: }
13:
14: protected void dispatchEvent(AWTEvent event) {
15: waitTimer.startTimer(event.getSource());
16: try {
17: super.dispatchEvent(event);
18: }
19: finally {
20: waitTimer.stopTimer();
21: }
22: }
23:
24: private int delay;
25: private WaitCursorTimer waitTimer;
26:
27: private class WaitCursorTimer extends Thread {
28:
29: synchronized void startTimer(Object source) {
30: this.source = source;
31: notify();
32: }
33:
34: synchronized void stopTimer() {
35: if (parent == null)
36: interrupt();
37: else {
38: parent.setCursor(null);
39: parent = null;
40: }
41: }
42:
43: public synchronized void run() {
44: while (true) {
45: try {
46: //wait for notification from startTimer()
47: wait();
48:
49: //wait for event processing to reach the threshold, or
50: //interruption from stopTimer()
51: wait(delay);
52:
53: if (source instanceof Component)
54: parent =
SwingUtilities.getRoot((Component)source);
55: else if (source instanceof MenuComponent) {
56: MenuContainer mParent =
57: ((MenuComponent)source).getParent();
58: if (mParent instanceof Component)
59: parent = SwingUtilities.getRoot(
60: (Component)mParent);
61: }
62:
63: if (parent != null && parent.isShowing())
64: parent.setCursor(
65: Cursor.getPredefinedCursor(
66: Cursor.WAIT_CURSOR));
67: }
68: catch (InterruptedException ie) { }
69: }
70: }
71:
72: private Object source;
73: private Component parent;
74: }
75: }
Toolkit.getSystemEventQueue() is a security-checked method so this tip precludes the use of unsigned applets. It should also be noted that a few third-party
tools modify the event queue and could possibly interfere with the operation of the WaitCursorEventQueue. With a few minor enhancements, the WaitCursorEventQueue could even let you disable the hourglass automation at will or for selected event types. The implementation I've provided offers a simple solution adequate for many applications and, I hope, gives an understandable explanation of Java's event queue processing. Maybe you will never have to think about that hourglass again.