Sunday 28 September 2008

Practical Example of Swing Timer

If you have to do something repetitively, what would you use? The first quick answer would be: loops (iteration). But that would prevent other things from happening until the loop is done! Threads can solve the seizing of control problem. But threads may be complex for someone who is new to programming and require some skill. What else can we use? Swing Timer!

Swing Timer

Swing timer provides a unique wrapper which presents threads in an event driven fashion. Imagine we need to display the text "hello" in the console every one second without having to seize the control. How would we do that using the swing timer class?

Note that there are three Timer classes in the Java API. We're using javax.swing.Timer. Make sure you have the correct import.


ActionListener listener = new ActionListener(){
  public void actionPerformed(ActionEvent event){
    System.out.println("hello");
  }
};
Timer displayTimer = new Timer(1000, listener);
displayTimer.start();

The swing timer has only one constructor which takes two parameters: the time gap (sometimes referred to as delay, in milliseconds) and an action listener. The swing timer fires an action event at specific intervals, which is handled by the action listener. The interval can be adjusted and more listeners can be added (and removed) as needs be using the setDelay(int) and addActionListener(ActionListener) (and removeActionListener(ActionListener)) methods. But let's keep it simple. For more details about available functionality provided by the swing timer class please refer to the javax.swing.Timer API.

Start the timer

No action will happen until the start() method is invoked. When the start method is invoked the timer waits for some time (referred to as initial delay) before it fires the first action event. This is governed by the initial delay which is initially set equal to the time gap provided as the constructor's first parameter. The initial delay can be adjusted accordingly using the setInitialDelay(int) method. It's important to set this value before invoking the start method.


Timer displayTimer = new Timer(1000, listener);
displayTimer.setinitialDelay(0);
displayTimer.start();

The above example will fire the first action immediately, as the initial delay is set to 0 milliseconds. It will trigger subsequent events with a one second (1000 milliseconds) interval.

Stop the timer

One of the best features provided by the swing timer is the ability to start and stop and even restart the timer without having to worry about the thread lifecycle.

Those who already worked with threads know that a thread cannot be restarted. Once ready, it must die (unreferenced) and another instance has to be instantiated in order to perform the thread's work.

To stop the swing timer, simple call the stop() method. This will stop the swing timer from firing any subsequent action events.


displayTimer.stop();

The swing timer can be restarted by simply calling the start() method again.

Restarting the swing timer

The swing timer can be restarted using the stop() start() methods. Alternatively, the restart() method can be used which cancels any pending firings of events and starts all over.


displayTimer.restart();

A graphical example

Let's create a simple applet which has a bouncing ball. The ball will stop moving once the user presses any key and starts bouncing again once another key is pressed again. This example will toggle the state of the swing timer between running and stopped. The swing timer provides a method called isRunning() which returns true if the swing timer is running, false otherwise.


    addKeyListener(new KeyAdapter() {
      @Override
      public void keyPressed(KeyEvent e) {
        if (moveBallTimer.isRunning()) {
          moveBallTimer.stop();
        } else {
          moveBallTimer.start();

        }
      }
    });

The full applet code listed below


import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.geom.Ellipse2D;

import javax.swing.JApplet;
import javax.swing.Timer;

public class SwingTimerAppletDemo1 extends JApplet {

  private static final long serialVersionUID = 656209471758159755L;

  private Ellipse2D ball;
  private Timer moveBallTimer;
  private int moveX;
  private int moveY;

  @Override
  public void init() {
    ball = new Ellipse2D.Double(0, 0, 10, 10);
    moveX = 5;
    moveY = 5;

    moveBallTimer = new Timer(100, new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        moveBall();
        repaint();
      }
    });

    addKeyListener(new KeyAdapter() {
      @Override
      public void keyPressed(KeyEvent e) {
        if (moveBallTimer.isRunning()) {
          moveBallTimer.stop();
        } else {
          moveBallTimer.start();

        }
      }
    });
  }

  protected void moveBall() {
    int width = getWidth();
    int height = getHeight();

    Rectangle ballBounds = ball.getBounds();
    if (ballBounds.x + moveX < 0) {
      moveX = 5;
    } else if (ballBounds.x + ballBounds.width + moveX > width) {
      moveX = -5;
    }
    if (ballBounds.y + moveY < 0) {
      moveY = 5;
    } else if (ballBounds.y + ballBounds.height + moveY > height) {
      moveY = -5;
    }
    ballBounds.x += moveX;
    ballBounds.y += moveY;

    ball.setFrame(ballBounds);
  }

  @Override
  public void paint(Graphics g) {
    super.paint(g);

    Graphics2D g2d = (Graphics2D) g;
    g2d.setColor(Color.RED);
    g2d.fill(ball);
  }

  @Override
  public void start() {
    moveBallTimer.start();
  }

  @Override
  public void stop() {
    moveBallTimer.stop();
  }
}

The above program is mainly doing two things: listens for user input through the keyboard and moving/bouncing the ball around the applet. This was possible as the swing timer makes use of threads to fire an action event and wait until the delay is over to fire the next action event. While waiting, the application can do other things such as listens to the user input.

What will happen if an action event is fired before the previous one has finish execution? By default the swing timer will merge consecutive action events into one. This behaviour is determined by the coalesce property. If set to false using the setCoalesce(boolean) method, the swing timer will fire all action event. This will cause queuing of action events as these are triggered at a faster rate than are handled.

Conclusion

The swing timer is a good candidate for invoking repetitive tasks without seizing the control. On the other hand, the tasks cannot be long lasting ones as otherwise it may block other things from happening making the application less responsive. The swing worker should be used for long lasting jobs.

6 comments:

  1. Great, this really is a goo explination.

    Thanks Loads

    ReplyDelete
  2. I copied and pasted your applet code directly and it seems like the keyadapter is doing nothing.... the ball does not stop when I hit a key? can you explain?

    ReplyDelete
    Replies
    1. Can you debug it and see why it is not working?

      Make sure that you click on the Applet such that it has the focus. The Applet may be rendered but not handling the IO events as it is not the component with the focus.

      Delete
    2. Hi, great tutorial, be apple was focused, but still none of the keyboard keys causes it to stop and start and only the applet menu to manually click Start or Stop works actually

      Delete
  3. thanks a lot for ur msgs......

    ReplyDelete
  4. setFocusable(true);
    requestFocusInWindow();

    Add this lines in init()
    Code will work fine !

    ReplyDelete