Quar
Quar

Reputation: 369

Timer/Stopwatch GUI

I am trying to make a simple java application that counts time, with the ability to stop and start the timer. However, the label won't update, and when I press start, it freezes.

Could you help me figure out what the problem is?

package random;

import javax.swing.JFrame;

public class Timer {
boolean shouldCount=false;
int int_sec=0;
int int_min=0;
int int_mil=0;
public static void main(String[] args) {
    TimeFrame t = new TimeFrame();
    JFrame f = new JFrame("Timer");
    f.setSize(300,200);
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setLocationRelativeTo(null);
    f.getContentPane().add(t);
    f.setVisible(true);
}

public void count(){
    TimeFrame t = new TimeFrame();
    if(shouldCount){
        long now = System.currentTimeMillis();
        while(true){
            if(System.currentTimeMillis()-now>=100){
                now=System.currentTimeMillis();
                String sec = Integer.toString(int_sec);
                String min = Integer.toString(int_min);
                String mil = Integer.toString(int_mil);
                t.update(sec,int_sec,min,mil,int_mil);
                int_mil++;
                if(int_mil>9){
                    int_mil=0;
                    int_sec++;
                    if(int_sec>=60){
                        int_sec=1;
                        int_min++;
                    }
                }
            }
        }
    }
}
}

And here is TimeFrame.java

    package random;

    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.JButton;
    import javax.swing.JLabel;
    import javax.swing.JPanel;

    public class TimeFrame extends JPanel{
    JLabel time = new JLabel("Time goes here", JLabel.CENTER);
    Timer t = new Timer();
    JButton pause = new JButton ("Pause");
     JButton start = new JButton ("Start");
    public TimeFrame(){

         start.addActionListener(new starts());
         pause.addActionListener(new starts());
         add(time);
         add(start);
         add(pause);
    }
    public void update(String sec,int s, String min,String mil,int m){
        if (s<=10){
            sec="0"+sec;
        }
        System.out.println(min+":"+sec+","+mil);
        time.setText(min+":"+sec+","+mil);

    }
    public class starts implements ActionListener{
        public void actionPerformed(ActionEvent event){
            if(event.getSource() == start){
                t.shouldCount=true;
            }else{
                t.shouldCount=false;
            }
            t.count();
        }
    }
}

Upvotes: 3

Views: 17944

Answers (2)

Marc
Marc

Reputation: 2639

The problem is that you have only one thread in your application. You should have at least two : one for the UI that will update the text and one for the computation of the time.

If you have only one thread it hangs in the while(true) loop and Swing never get to update the view.

I refactored your code using two threads :

  1. One counting until the end of time and updating the fields to keep time in memory

  2. Another one that uses a java.util.Timer#scheduleAtFixedRate() method wich is invoqued every 100 millisec to update the view.

Timer.java (avoid naming classes like ones in the Java API)

    public class Timer {

        boolean shouldCount=false;
        int int_sec=0;
        int int_min=0;
        int int_mil=0;

        public Timer() {
        }

        public static void main(String[] args) {
            TimeFrame t = new TimeFrame();
            JFrame f = new JFrame("Timer");
            f.setSize(300,200);
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setLocationRelativeTo(null);
            f.getContentPane().add(new TimeFrame());
            f.setVisible(true);
        }

        public void count(){
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        long now = System.currentTimeMillis();
                        while(true){
                        if(shouldCount){
                            if(System.currentTimeMillis()-now>=100){
                                now=System.currentTimeMillis();
                                int_mil++;
                                if(int_mil>9){
                                     int_mil=0;
                                     int_sec++;
                                     if(int_sec>=60){
                                          int_sec=1;
                                          int_min++;
                                     }
                                }
                            }       
                        }
                    }               
                }
            });
            thread.start();
        }
    }
}

TimeFrame (I would rather call it TimePanel since it extends JPanel)

public class TimeFrame extends JPanel{
    JLabel time;
    Timer t ;
    JButton pause ;
    JButton start ;
    public TimeFrame(){

        t= new Timer(this);

        time = new JLabel("Time goes here", JLabel.CENTER);
        pause = new JButton ("Pause");
        start = new JButton ("Start");

        start.addActionListener(new starts());
        pause.addActionListener(new starts());
        add(time);
        add(start);
        add(pause);

        java.util.Timer updateTimer= new java.util.Timer();
        updateTimer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            t.update(int_sec,int_min,int_mil);
        }
        }, 0, 100);
    }    

    public void update(int s, int minute,int m){
        String sec = Integer.toString(s);
        String min = Integer.toString(minute);
        String mil = Integer.toString(m);
    if (s<=10){
            sec="0"+sec;
        }

        System.out.println(min+":"+sec+","+mil);
        time.setText(min+":"+sec+","+mil);
    }


   public class starts implements ActionListener{
    boolean firstTime=true;
    public void actionPerformed(ActionEvent event){
        if (firstTime){
            t.count();
            firstTime = false;
        }
        if(event.getSource() == start){
            t.shouldCount=true;
        }else{
            t.shouldCount=false;
        }
    }
}

}

Upvotes: 3

ug_
ug_

Reputation: 11440

The problem is your while loop is tied to your interface thread. When you click the start button it calls Timer.count() and then goes into an infinite loop causing the interface to be stuck and never update.

The assumptions that using the java.util.Timer class is better may be over estimating that classes functionality for this specific problem. It does not contain a pause method and you have to recreate the timer when you want to pause it, causing some possible difficult challenges for adding up time.

What I would do is make your timer implement the runnable interface and use a thread to keep tabs on your current time. Heres the changes I would make

Notice I made your fields private. It is proper practice to make your fields private (if they should be) and use getters and setters to grant access to them. eg: getCurrentTime()

Timer.java:

package random;

import javax.swing.JFrame;

public class Timer implements Runnable {

    private Thread runThread;
    private boolean running = false;
    private boolean paused = false;
    private TimeFrame timeFrame;
    private long summedTime = 0;

    public Timer(TimeFrame timeFrame) {
        this.timeFrame = timeFrame;
    }

    public static void main(String[] args) {
        TimeFrame t = new TimeFrame();
        JFrame f = new JFrame("Timer");
        f.setSize(300,200);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLocationRelativeTo(null);
        f.getContentPane().add(t);
        f.setVisible(true);
    }

    public void startTimer() {
        running = true;
        paused = false;
        // start the thread up
        runThread = new Thread(this);
        runThread.start();
    }

    public void pauseTimer() {
        // just pause it
        paused = true;
    }

    public void stopTimer() {
        // completely stop the timer
        running = false;
        paused = false;
    }

    @Override
    public void run() {
        long startTime = System.currentTimeMillis();
        // keep showing the difference in time until we are either paused or not running anymore
        while(running && !paused) {
            timeFrame.update(summedTime + (System.currentTimeMillis() - startTime));
        }
        // if we just want to pause the timer dont throw away the change in time, instead store it
        if(paused)
            summedTime += System.currentTimeMillis() - startTime;
        else 
            summedTime = 0;
    }
}

TimeFrame.java:

package random;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class TimeFrame extends JPanel{
    private JLabel time = new JLabel("Time goes here", JLabel.CENTER);
    private Timer timer;
    private JButton pause = new JButton ("Pause");
    private JButton start = new JButton ("Start");

    public TimeFrame(){
        timer = new Timer(this);
         start.addActionListener(new starts());
         pause.addActionListener(new starts());
         add(time);
         add(start);
         add(pause);
    }
    public void update(long dT){
        // convert milliseconds into other forms
        time.setText(String.valueOf((dT/6000)%1000)+":"+String.valueOf((dT/1000)%1000)+","+String.valueOf((dT)%1000));
    }
    public class starts implements ActionListener{
        public void actionPerformed(ActionEvent event){
            if(event.getSource() == start){
                timer.startTimer();
            }else{
                timer.pauseTimer();
            }
        }
    }
}

Upvotes: 1

Related Questions