计时器/秒表图形用户界面

3
我正在尝试制作一个简单的Java应用程序,它可以计时,并具有停止和启动计时器的功能。然而,标签不会更新,当我按下开始按钮时,它会冻结。
你能帮我找出问题所在吗?
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++;
                    }
                }
            }
        }
    }
}
}

以下是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();
        }
    }
}

1
为什么不使用javax.swing.Timer而不是while循环? - BackSlash
另外,你可能正在尝试从另一个线程更新GUI线程,这会导致问题。我认为有一个runLater()线程可以让你安全地从其他线程更新GUI线程。 - SnakeDoc
2个回答

3
问题在于你的应用程序只有一个线程。你至少应该有两个:一个用于更新文本的UI线程,另一个用于计算时间。
如果只有一个线程,它将停留在while(true)循环中,Swing永远无法更新视图。
我使用了两个线程重构了你的代码:
1. 一个计数直到时间结束并更新字段以保持时间内存的线程
2. 另一个使用java.util.Timer#scheduleAtFixedRate()方法,每100毫秒调用一次来更新视图。
Timer.java(避免使用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(我更倾向于称其为TimePanel,因为它扩展了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;
        }
    }
}

}

谢谢,它运行得很好,除了暂停按钮。我读了代码,认为它会起作用,但实际上并没有,你知道为什么吗? - Quar
好的,确实有一些需要更改的地方才能使其正常工作。我会研究一下,但你也可以自己尝试。 - Marc
1
当你将 should count 设置为 true 或 false 时,它不会影响 while true 循环,因为条件从未被评估。将条件放在 while 循环内部,并从 ActionListener 中删除 t.count(),因为它会创建一个新的线程。 - Marc

1
问题在于你的while循环与界面线程绑定。当你点击开始按钮时,它调用Timer.count(),然后进入无限循环导致界面卡住并且永远不会更新。
假设使用java.util.Timer类更好可能会高估该类针对此特定问题的功能。它不包含暂停方法,而且当你想要暂停它时,必须重新创建计时器,这可能会导致一些困难的挑战来增加时间。
我会让你的计时器实现runnable接口,并使用线程来跟踪当前时间。以下是我所做的更改:
注意我将你的字段设为私有。如果需要,正确的做法是将字段设置为私有,并使用getter和setter来授予对它们的访问权限。例如: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();
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接