站内搜索: 请输入搜索关键词
当前页面: 图书首页 > Java Threads, Third Edition

11.3 The javax.swing.Timer Class - Java Threads, Third Edition

Previous Section  < Day Day Up >  Next Section

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.

    Previous Section  < Day Day Up >  Next Section