11.3 The javax.swing.Timer Class
As we've discussed, Swing objects cannot be accessed from arbitrary
threads梬hich includes the threads from the
Timer class (and the threads in the thread pool of
the
ScheduledThreadPoolExecutor class that we discuss later in this
chapter). We know that we can use the invokeLater() and invokeAndWait() methods of the
SwingUtilities class to overcome this, but Java
also provides a Timer class just for Swing
objects. The javax.swing.Timer class provides the
ability to execute actions at a particular time, and those actions
are invoked on the event-dispatching thread.
Here is the interface to
the javax.swing.Timer
class:
public class Timer {
public Timer(int delay, ActionListener listener);
public void addActionListener(ActionListener listener);
public void removeActionListener(ActionListener listener);
public ActionListener[] getActionListeners( );
public EventListener[] getListeners(Class listenerType);
public static void setLogTimers(boolean flag);
public static boolean getLogTimers( );
public void setDelay(int delay);
public int getDelay( )
public void setInitialDelay(int initialDelay);
public int getInitialDelay( );
public void setRepeats(boolean flag);
public boolean isRepeats( );
public void setCoalesce(boolean flag);
public boolean isCoalesce( );
public void start( );
public boolean isRunning( );
public void stop( );
public void restart( );
}
This class is not really a generic scheduler. In fact, even though
multiple callbacks (event listeners) can be attached to the timer, it
has only one schedule: all the listeners use the schedule defined by
the Timer class itself (rather than the schedule
defined by particular tasks). Tasks that require a different schedule
need a different instance of the Swing timer. Most of the methods
provided by this class are used to configure the schedule and control
the timer.
Unlike the java.util.Timer class, this
Timer class uses the
ActionListener
interface. This provides an interface that Swing developers are
accustomed to: all Swing objects use event listeners to execute
callbacks. When a scheduled time is reached, it is treated as any
other event (such as a button press): the registered action listeners
are called.
The constructor to the class takes two parameters. The first is the
delay in milliseconds. This value is used by the timer as both the
initial time to wait to fire the first action listener and the time
to wait between repeated firings of the action listeners. The second
parameter is an action listener to fire. Both of these parameters can
be modified at a later time.
The addActionListener()
and
removeActionListener()
methods are used to add listeners to and remove listeners from the
timer. The getActionListeners() method is used to
retrieve the listeners that have been registered to the timer.
The
getListeners() method
provides the added qualification of the event listener type. This
allows the developer to get specific types of listeners that are
registered to the timer. In most cases, this is probably not very
useful, as the limitation of the timer as a generic scheduler also
limits the number of action listeners registered to each timer.
The getDelay()
and setDelay()
methods are used to retrieve and modify the
time between repeated events (which by default is set in the
constructor). This allows it to be different from the initial delay
time. That delay time is handled by the getInitialDelay()
and setInitialDelay()
methods.
The isRepeats()
and setRepeats()
methods are used to control whether events
are repeated. By default, the timer repeats events, as this
Timer class was originally designed for tasks such
as a blinking cursor. The isCoalesce(
)
and setCoalesce()
methods are used to handle repeated methods
that are backlogged. For example, if a method is to be called once
every second, and three seconds have elapsed, then the listener may
have to be called three times. If the coalesce
flag is set, the listener is called only once. This is important for
tasks such as blinking the cursor. If the timer has already missed
two blinks, blinking three times very fast does not fix the problem;
it is better to just skip the missed blinks.
The getLogTimers()
and setLogTimers()
methods are used to control debugging of
the timer. If debugging is activated, messages are sent to standard
output to report the actions of the timer.
Finally, the timer must be activated upon completion of the
registration of the listeners (and, possibly, adjusting the initial
delay and repeat times). This is accomplished by the start()
method. The timer can later be terminated
by calling the stop()
method. The restart()
method resets the timer: the timer then
waits until its initial delay time period has elapsed, at which point
it starts calling its listeners. The isRunning()
method is used to determine whether the
timer has been started.
11.3.1 Using the javax.swing.Timer Class
We can use the javax.swing.Timer class in our
typing program. Previously, our animated canvas set up a thread to
handle the animation; this thread periodically told the animation
canvas to repaint itself. Now, we'll use a timer.
package javathreads.examples.ch11.example2;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import javathreads.examples.ch11.*;
public class AnimatedCharacterDisplayCanvas extends CharacterDisplayCanvas
implements ActionListener, CharacterListener {
private int curX;
private Timer timer;
public AnimatedCharacterDisplayCanvas(CharacterSource cs) {
super(cs);
timer = new Timer(100, this);
}
public synchronized void newCharacter(CharacterEvent ce) {
curX = 0;
tmpChar[0] = (char) ce.character;
repaint( );
}
public synchronized void paintComponent(Graphics gc) {
if (tmpChar[0] == 0)
return;
Dimension d = getSize( );
int charWidth = fm.charWidth(tmpChar[0]);
gc.clearRect(0, 0, d.width, d.height);
gc.drawChars(tmpChar, 0, 1, curX++, fontHeight);
if (curX > d.width - charWidth)
curX = 0;
}
public void actionPerformed(ActionEvent ae) {
repaint( );
}
public void setDone(boolean b) {
if (!b)
timer.start( );
else timer.stop( );
}
}
Note that this implementation is much simpler than our previous
implementations. Previously, we set up a thread in the
setDone() method; now, we simply call the timer
start() method.
Using the timer has also allowed us to simplify the locking around
the calls to the repaint() method. Knowing when
the animation should run used to require a wait-and-notify mechanism
(or condition variable). Now we just defer that to the timer. The
Timer class itself has the waiting logic within
it: operationally, we haven't saved anything. But in
terms of development, using a timer has saved us some effort. This is
a clear example of why using higher-level thread constructs makes
things simpler for the developer.
|