2

I am trying to develop a game in an Applet and I am having this problem. I would like the display to show a countdown to the user before the game continues. However, the countdown will not display and is actually making the GUI freeze up instead. How can this be avoided? Here is some code demonstrating this problem.

EDIT: Code below 'almost' works, timer is going but screen will only update to new timer value whenever Start button is pressed. How can I make the text refresh automatically?

public class TestApplet extends JApplet implements ActionListener{


        final JTextField _displayField = new JTextField("Countdown", 6);
        CountDownTimer clock = new CountDownTimer();
        JButton jbtnStart = new JButton("Start");

    public void addComponentToPane(Container pane) {

        JPanel mainPanel = new JPanel();    
        mainPanel.add(jbtnStart);
        mainPanel.add(_displayField);
        pane.add(mainPanel);
        jbtnStart.addActionListener(this);
    }


  public void init() {

        TestApplet testApplet = new TestApplet();
        testApplet.setVisible(true);    
        testApplet.addComponentToPane(this.getContentPane());   
        this.setSize(200, 100);

}

    public void actionPerformed(ActionEvent e) {

      if ( e.getSource() == jbtnStart   ){
              clock.start(_displayField);
          }
   }     
}

// ********************************************************************************
//********************************************************************************
//********************************************************************************

class CountDownTimer  {

    private static final int N = 60;
    private final ClockListener cl = new ClockListener();
    private final Timer t = new Timer(1000, cl);
    static int count =0;

    public int getCount(){
         System.out.println(count);
        return count;
    }
    public void setCount(int n){
        count = n;
    }

    public CountDownTimer() {
        t.setInitialDelay(0);
    }

    public void start(JTextComponent c) {
        t.start();
       Boolean bool  = false;
          while ( bool ==false){     
              c.setText( "Starting new game in... "+ this.getCount() );
              bool = ( this.getCount()<10 );
          }
    }

    private class ClockListener implements ActionListener {

        public void actionPerformed(ActionEvent e) {
            count %= N;
            count++;
           setCount(count);
        }
    }
}
phcoding
  • 608
  • 4
  • 16

3 Answers3

4

You have a while loop in the ActionListener that is blocking the EDT. The code to update the display field should not be in the ActionListener.

Instead this code should be in the Timer class. Then whenever the Timer fires you simply subtract one and update the display field. When the count reaches zero you stop the Timer.

Also, your CountDownTimer should not extend a JFrame. It is just a class and has nothing to do with a frame.

Edit:

Here is a simple usage of a Swing Timer:

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

public class TimerTime extends JFrame implements ActionListener
{
    JLabel timeLabel;

    public TimerTime()
    {
        timeLabel = new JLabel( new Date().toString() );
        getContentPane().add(timeLabel, BorderLayout.NORTH);
    }

    public void actionPerformed(ActionEvent e)
    {
        timeLabel.setText( new Date().toString() );
    }

    public static void main(String[] args)
    {
        TimerTime frame = new TimerTime();
        frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
        frame.pack();
        frame.setVisible(true);

        int time = 1000;
        javax.swing.Timer timer = new javax.swing.Timer(time, frame);
        timer.setInitialDelay(1);
        timer.start();
    }
}
camickr
  • 321,443
  • 19
  • 166
  • 288
  • Thanks for your response I will try to incorporate this fix. However can I ask why is the while loop blocking the EDT? – phcoding Feb 12 '13 at 16:44
  • 1
    All Listeners run on the EDT. Since your while loop is executed in the listener it is executed on the EDT. Your while loop is basically an infinite loop. It does not just execute 5 times, it executes millions of times. Add a print statement to the loop to see this. That is why the code should be in the Timer. Then it will only execute 5 times and update the text field properly on the EDT. – camickr Feb 12 '13 at 16:49
  • **Solution: I found a fix to this problem by putting a JLablel field in the Countdowntimer class - This is possible as JLabel is threadsafe.** – phcoding Feb 12 '13 at 18:06
  • You still don't understand the concept of using a Timer. There is no need for the while loop. The Timer is used to avoid the loop. The Timer will fire every second and then you will update your components every time the Timer fires. All of this code will be in the ActionListener you add to the Timer. Your code will be more complicated because you will need to track the Timer count and then stop the Timer after 5 seconds. – camickr Feb 12 '13 at 19:21
  • Thanks for your input camickr I know what you mean I have fixed the code now. The working solution looks like this: – phcoding Feb 12 '13 at 20:25
1

We've been argueing about if a solution with a background thread was possible so I've coded a solution using a secondary thread and it works fine.

  • The methods "getCount" and "setCount" are dispensable
  • "N" constant stablish the length of the countdown

    public class TestApplet extends JApplet implements ActionListener{

    JTextField _displayField;
    CountDownTimer clock;
    JButton jbtnStart;
    Thread thread;
    
    public TestApplet(){
        this.jbtnStart = new JButton("Start");
        this._displayField = new JTextField("Countdown", 30);
        this.clock = new CountDownTimer(_displayField);
        this.thread = null;
    }
    
    public void addComponentToPane(Container pane) {
    
    
    
        JPanel mainPanel = new JPanel();    
        mainPanel.add(jbtnStart);
        mainPanel.add(_displayField);
        pane.add(mainPanel);
        jbtnStart.addActionListener(this);
    }
    
    
    public void init() {
    
        TestApplet testApplet = new TestApplet();
        testApplet.setVisible(true);    
        testApplet.addComponentToPane(this.getContentPane());   
        this.setSize(200, 100);
    
    
    }
    
    public void actionPerformed(ActionEvent e) {
    
        if ( e.getSource() == jbtnStart   ){
    
            if(thread != null){
                thread.interrupt();
            }
            thread = new Thread(clock);
            thread.start();
        }
    }     
    

    }

/*************************************************/ /*************************************************/ /**************************************************/

public class CountDownTimer implements Runnable{

    private static final int N = 60;
    JTextComponent c;
    static int count =0;

    public int getCount(){
         System.out.println(count);
        return count;
    }
    public void setCount(int n){
        count = n;
    }

    public CountDownTimer(JTextComponent c) {
        this.c = c; 
    }

    @Override
    public void run() {
        try {
            for(int i=N; i>0; i--){
                setCount(i);
                c.setText( "Starting new game in... "+ this.getCount() );
                Thread.sleep(1000); 
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            setCount(0);
        }
    }
}
  • Thank you for your response Francisco, that's a really informative example. I think your solution is broader and can be applied to more complex problems than the one presented here. I think it is a valid solution to this problem also. – phcoding Feb 13 '13 at 02:15
0

What about use concurrency for your countdown?

        public void actionPerformed(ActionEvent e) {

              if ( e.getSource() == jbtnStart   ){
              //You should start your new thread here
              Thread thread = Thread(new Runnable(){
                 public void run(){
                     clock.start();
                     Boolean bool  = false;
                     while ( bool ==false){
    //You can not modify the UI from a secondary thread, so you should call to some 
    //method in the GUI thread...     
                     _displayField.setText( "Starting new game in... "+ clock.getCount() );
                     bool = ( clock.getCount()==5 );  
                 }

               }
               thread.start();

            }


     }
}  

NOTE: The code is done by heart so is susceptible to has some errors, but it express the main point.

  • 1
    So if you create a secondary thread, the code is being executed on the secondary thread, not in the GUI thread and the GUI will not be frozen. – Francisco J. Güemes Sevilla Feb 12 '13 at 16:25
  • 2
    -1 your code is attempting to update the GUI from a secondary Thread. This is wrong. All updates to GUI components should be done on the Event Dispatch Thread (EDT). Yes, the Thread will prevent the GUI from freezing, but you have broken one of the main rules of updating GUI components. – camickr Feb 12 '13 at 16:29
  • You are violating the basic rules of accessing Swing Components. – Extreme Coders Feb 12 '13 at 16:38
  • http://stackoverflow.com/questions/7229284/refreshing-gui-by-another-thread-in-java-swing – Francisco J. Güemes Sevilla Feb 12 '13 at 16:39
  • http://stackoverflow.com/questions/7131484/how-to-pass-the-message-from-working-thread-to-gui-in-java/7131574#7131574 – Francisco J. Güemes Sevilla Feb 12 '13 at 16:44
  • Thanks for your suggestions to all of those who contributed to this discussion, but I don't personally know whether starting a new thread is the correct approach or not. I will read more on this topic when I get the chance. – phcoding Feb 12 '13 at 16:46
  • You can use `SwingUtilities.invokeLater` on any thread to make sure the components are accessed on the EDT only. – Extreme Coders Feb 12 '13 at 16:48
  • 1
    So I am right, you can use a bakground thread to do the countdown, the point is to use SwingUtilities.invokeLater to update de GUI, just after the comment in my code. Comment that I wrote at first time, but I got downvoted. – Francisco J. Güemes Sevilla Feb 12 '13 at 16:53
  • 1
    Rereading your answer I see what your are trying to suggest and the answer is technically not wrong. However the answer is still confusing because of the way the poster originally wrote the code. There is no need to create an additional Thread because the poster is already using a Timer. Your solution makes a bad design worse. As I mentioned in my posting the while loop as coded will still cause the CPU to max out because it is in a continuos loop for 5 seconds until the original Timer fires 5 times. Actually your suggestion would still flood the EDT with thousands of update requests. – camickr Feb 12 '13 at 17:08