JimInCO
JimInCO

Reputation: 189

How do I wait for a java timer to finish

I have a java swing GUI in which I'm trying to implement a countdown timer. I have a series of tasks that each need to run sequentially inside a for-loop. I've been able to successfully use a swing timer to do the countdown timer, but once I try and put it in a loop I can't get it to update without freezing the GUI.

Here's an SSCCE of what I currently have working. The basic problem is this example run three 30 second timers in parallel. What I want to do is run three 30 second timers sequentially or in other words it should wait for one timer to finish before beginning the next. Everything I've done to try and wait for the timer to finish makes the GUI freeze.

import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.security.auth.callback.Callback;
import javax.swing.*;

public class SwingTester extends JFrame {
  public SwingTester() {
    JPanel panel = new JPanel();
    getContentPane().add(panel);
    panel.setLayout(new GridLayout(1,3));
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    // Add the components
    jLblTimer = new JLabel("00:30");
    jLblSet = new JLabel("Set #1");
    jBtnStart = new JButton("Start!");
    jBtnStart.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent evt) {jBtnStartActionPerformed(evt);}
    });
    // Add the labels
    panel.add(jLblTimer); panel.add(jLblSet); panel.add(jBtnStart);
    pack();
  }
  private void jBtnStartActionPerformed(ActionEvent evt) {
    for (int ss=0; ss<3; ss++) {
        // Change the set name
        jLblSet.setText("Set #" + Integer.toString(ss+1));
        // Setup the counter to begin the countdown
        counter = 30;
        timer = new Timer(1000, startCycle());
        timer.start();
        // I want to wait here until the timer is done, nothing I've done
        // works without freezing the GUI.
    }
  }
  public static void main(String args[]) {
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        new SwingTester().setVisible(true);
      }
    });
  }
  private Action startCycle() {
    return new AbstractAction() {
        @Override
        public void actionPerformed(ActionEvent e) {
            new ClockTask(null).execute();
        }
    };
  }
  class ClockTask extends SwingWorker<Void, String> {
  private Callback callback;
  public ClockTask(Callback callback) {
    this.callback = callback;
  }
  @Override
  protected Void doInBackground() throws Exception {
    if (counter >= 0) {
      int millis = counter*1000;
      String time = String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(millis),
        TimeUnit.MILLISECONDS.toSeconds(millis) -
        TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))
        );
      publish(time); // call publish which will call process() method in EDT
      counter--;
    } else {
      // this.callback.call();
      this.callback.notify();
      timer.stop();
    }
    return(null);
  }
  @Override
  protected void process(List<String> times) {
    String lastTime = times.get(times.size()-1);
    jLblTimer.setText(lastTime); // just update ui with lastTimer, ohters are ignorable 
  }
  @Override
  protected void done() {
    super.done();
  }

  // private interface Callback {
  //   void call();
  // }
  }
  private javax.swing.JLabel jLblTimer;
  private javax.swing.JLabel jLblSet;
  private javax.swing.JButton jBtnStart;
  private javax.swing.Timer timer;
  private int counter;

}

I've also tried using the sleep function to just wait until the countdown finishes, but this stops the GUI from updating. Any suggestions are most welcome. As you can probably tell from my code I'm not a programmer.

Upvotes: 2

Views: 2853

Answers (2)

Kowser
Kowser

Reputation: 8261

Below is how you need to change your SwingWorker

class ClockTask extends SwingWorker<Void, String> {

    private Callback callback;

    public ClockTask(Callback callback) {
        this.callback = callback;
    }

    @Override
    protected Void doInBackground() throws Exception {
        if (counter >= 0) {
            int millis = counter*1000;
            String time = String.format("%02d:%02d", 
                TimeUnit.MILLISECONDS.toMinutes(millis),
                TimeUnit.MILLISECONDS.toSeconds(millis) - 
                TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))
            );

            //jLblTimer.setText(time); // dont update UI in backgroudn thread
            publish(time); // call publish which will call process() method in EDT

            counter--;
        } else {
            this.callback.call();
        }
        return(null);
    }

    /*
     * this  method will be called in EDT and will not freeze your UI ;)
     */
    @Override
    protected void process(List<String> times) {
        String lastTime = times.get(times.size()-1);
        jLblTimer.setText(lastTime); // just update ui with lastTimer, ohters are ignorable 
    }

    @Override
    protected void done() {
        super.done();
    }

    private interface Callback {
        void call();
    }

}

You should spend few more time with the JavaDoc of SwingWorker

Upvotes: 3

Jeffrey Zhao
Jeffrey Zhao

Reputation: 5023

There're several ways to do so. Normally you can create an object, pass it to the background work, after the background work finishs, it use the object to tell you the result and let you do the rest of work.

private class ClockTask extends SwingWorker<Void, Void> {

    private Callback _callback;

    public ClockTask(Callback callback) {
        this._callback = callback;
    }

    @Override
    protected Void doInBackground() throws Exception {
        if (counter >= 0) {
            int millis = counter*1000;
            String time = String.format("%02d:%02d", 
                TimeUnit.MILLISECONDS.toMinutes(millis),
                TimeUnit.MILLISECONDS.toSeconds(millis) - 
                TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))
            );
            jLblTimer.setText(time);
            counter--;
        } else {
            this._callback.notify();
        }
        return(null);
    }
}

Upvotes: -2

Related Questions