创建基于GUI的计时器(或秒表)

时间:2014-01-09 04:59:48

标签: java swing timer stopwatch chronometer

我花了一些时间研究的是一个程序,显示用户点击开始按钮的时间或剩余时间,就像秒表或计时器一样,它会测量您停止和重置之前的时间。测量时间的其他示例是赛车游戏中的单圈时间和其他游戏中的时间限制(以毫秒为单位)。

但是,我遇到了一些麻烦,因为我自己的秒表没有以与实际时间相同的速度运行。我的计时器向下或向上运行一秒需要超过一秒的时间。

代码就在这里:( GUI工作得很好;我更关心的是如何控制值以便以每秒传递的方式显示时间,JLabel上显示的时间不到一秒。我无法修改传递给Thread.sleep的参数,因为它会使计时器更糟糕。)

import javax.swing.*;

import java.awt.event.*;
import java.awt.*;
import java.text.DecimalFormat;
import java.util.concurrent.*;

public class StopwatchGUI3 extends JFrame 
{

    private static final long serialVersionUID = 3545053785228009472L;

    // GUI Components
    private JPanel panel;
    private JLabel timeLabel;

    private JPanel buttonPanel;
    private JButton startButton;
    private JButton resetButton;
    private JButton stopButton;

    // Properties of Program.
    private byte centiseconds = 0;
    private byte seconds = 30;
    private short minutes = 0;

    private Runnable timeTask;
    private Runnable incrementTimeTask;
    private Runnable setTimeTask;
    private DecimalFormat timeFormatter;
    private boolean timerIsRunning = true;

    private ExecutorService executor = Executors.newCachedThreadPool();

    public StopwatchGUI3()
    {
        panel = new JPanel();
        panel.setLayout(new BorderLayout());

        timeLabel = new JLabel();
        timeLabel.setFont(new Font("Consolas", Font.PLAIN, 13));
        timeLabel.setHorizontalAlignment(JLabel.CENTER);
        panel.add(timeLabel);


        buttonPanel = new JPanel();
        buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER));

        startButton = new JButton("Start");
        startButton.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e)
            {
                if (!timerIsRunning)
                    timerIsRunning = true;

                executor.execute(timeTask);
            }
        });
        buttonPanel.add(startButton);

        resetButton = new JButton("Reset");
        resetButton.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e)
            {
                timerIsRunning = false;

                centiseconds = 0;
                seconds = 30;
                minutes = 0;

                timeLabel.setText(timeFormatter.format(minutes) + ":" 
                        + timeFormatter.format(seconds) + "." 
                        + timeFormatter.format(centiseconds));
            }
        });

        buttonPanel.add(resetButton);

        stopButton = new JButton("Stop");
        stopButton.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e)
            {
                timerIsRunning = false;
            }
        });

        buttonPanel.add(stopButton);


        panel.add(buttonPanel, BorderLayout.SOUTH);


        timeFormatter = new DecimalFormat("00");

        timeTask = new Runnable(){
            public void run()
            {
                while(timerIsRunning)
                {
                    executor.execute(incrementTimeTask);

                    try
                    {
                        Thread.sleep(10);
                    }
                    catch (InterruptedException ex)
                    {
                        ex.printStackTrace();
                    }
                 }
            }
        };

        incrementTimeTask = new Runnable(){
            public void run()
            {
                if (centiseconds > 0)
                    centiseconds--;
                else
                {
                    if (seconds == 0 && minutes == 0)
                        timerIsRunning = false;
                    else if (seconds > 0)
                    {
                        seconds--;
                        centiseconds = 99;
                    }
                    else if (minutes > 0)
                    {
                        minutes--;
                        seconds = 59;
                        centiseconds = 99;
                    }
                }

                executor.execute(setTimeTask);
            }
        };

        setTimeTask = new Runnable(){
            public void run()
            {
                timeLabel.setText(timeFormatter.format(minutes) + ":" 
                        + timeFormatter.format(seconds) + "." 
                        + timeFormatter.format(centiseconds));
            }
        };

        timeLabel.setText(timeFormatter.format(minutes) + ":" 
                + timeFormatter.format(seconds) + "." 
                + timeFormatter.format(centiseconds));

        add(panel);

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setTitle("StopwatchGUI.java");

        pack();
        setVisible(true);
    }

    public static void main(String[] args) 
    {
        new StopwatchGUI3();
    }
}

还有另一种让计时器与实时同步的方法,就像一个真正的秒表,而不是必须依赖三个独立的线程,我认为这对于这么大的编程项目来说太多了,但是没关系入门级现在。 (哦,顺便说一下,DecimalFormat类就像真正的秒表一样正确地格式化数字,虽然没有十进制值可以舍入。直到现在,在我发布这个时,才存在名为SimpleDateFormat的文本类。)

换句话说,我希望这个程序只是一个真正的秒表。如果不是这种情况,那么你如何在Java游戏中创建或使用秒表呢?

2 个答案:

答案 0 :(得分:3)

您将面临的最大问题是能够让各种Runnable以一致的速度运行。基本上,没有真正的方法可以知道Executor何时会实际执行你提供的任务,因为它有自己的头脑。

在这种特殊情况下,我建议将活动Thread的数量减少为1,这样可以减少创建和执行其他Thread所涉及的任何额外开销,并为您提供最佳控制让事情尽可能接近你想要的时间。

而不是使用Thread,而是使用javax.swing.Timer,主要因为它很简单并且在EDT的上下文中执行,这使得从内部更新UI更安全,示例

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class StopwatchGUI3 extends JFrame {

    private static final long serialVersionUID = 3545053785228009472L;

    // GUI Components
    private JPanel panel;
    private JLabel timeLabel;

    private JPanel buttonPanel;
    private JButton startButton;
    private JButton resetButton;
    private JButton stopButton;

    // Properties of Program.
    private byte centiseconds = 0;
    private byte seconds = 30;
    private short minutes = 0;

    private DecimalFormat timeFormatter;

    private Timer timer;

    public StopwatchGUI3() {
        panel = new JPanel();
        panel.setLayout(new BorderLayout());

        timeLabel = new JLabel();
        timeLabel.setFont(new Font("Consolas", Font.PLAIN, 13));
        timeLabel.setHorizontalAlignment(JLabel.CENTER);
        panel.add(timeLabel);

        buttonPanel = new JPanel();
        buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER));

        startButton = new JButton("Start");
        startButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {

                timer.start();

            }
        });
        buttonPanel.add(startButton);

        resetButton = new JButton("Reset");
        resetButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {

                timer.stop();

                centiseconds = 0;
                seconds = 30;
                minutes = 0;

                timeLabel.setText(timeFormatter.format(minutes) + ":"
                        + timeFormatter.format(seconds) + "."
                        + timeFormatter.format(centiseconds));
            }
        });

        buttonPanel.add(resetButton);

        stopButton = new JButton("Stop");
        stopButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                timer.stop();
            }
        });

        buttonPanel.add(stopButton);

        panel.add(buttonPanel, BorderLayout.SOUTH);

        timeFormatter = new DecimalFormat("00");

        timer = new Timer(10, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (centiseconds > 0) {
                    centiseconds--;
                } else {
                    if (seconds == 0 && minutes == 0) {
                        timer.stop();
                    } else if (seconds > 0) {
                        seconds--;
                        centiseconds = 99;
                    } else if (minutes > 0) {
                        minutes--;
                        seconds = 59;
                        centiseconds = 99;
                    }
                }
                timeLabel.setText(timeFormatter.format(minutes) + ":"
                        + timeFormatter.format(seconds) + "."
                        + timeFormatter.format(centiseconds));
            }
        });

        timeLabel.setText(timeFormatter.format(minutes) + ":"
                + timeFormatter.format(seconds) + "."
                + timeFormatter.format(centiseconds));

        add(panel);

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setTitle("StopwatchGUI.java");

        pack();
        setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                new StopwatchGUI3();
            }
        });
    }
}

我当时也会停止“猜测”。根本无法保证“更新”之间经过的时间是准确的。

相反,我会抓住秒表启动时的当前时间,并在Timer的每个刻度上,从当前时间减去它,给出你已经过去的时间。然后,您可以使用它来确定秒表的当前值应该是什么......

例如......

添加以下实例字段...

private long startTime;
private long runTime = 30000; // 30 seconds...

更新startButton以包括捕获开始时间......

startButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {

        startTime = System.currentTimeMillis();
        timer.start();

    }
});

然后更新Timer,如下所示......

timer = new Timer(10, new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {

        long now = System.currentTimeMillis();
        long dif = now - startTime;
        if (dif >= runTime) {

            timer.stop();
            dif = runTime;

        }

        dif = runTime - dif;

        long minutes = dif / (60 * 1000);
        dif = Math.round(dif % (60 * 1000));
        long seconds = dif / 1000;
        dif = Math.round(dif % 1000);
        long centiseconds = dif / 10;

        timeLabel.setText(timeFormatter.format(minutes) + ":"
                + timeFormatter.format(seconds) + "."
                + timeFormatter.format(centiseconds));
    }
});

请查看Concurrency in Swing了解详情

答案 1 :(得分:2)

  

“还有另一种让计时器与实时同步的方法,就像一个真正的秒表,而不是必须依赖三个独立的线程,我认为这对于这么大的编程项目来说太多了,“

您可以使用javax.swing.Timer。它相当容易使用。基本构造函数看起来像这样

Timer(int duration, ActionListener listener)

所以你可以做的是将Timer对象声明为类成员。然后在构造函数中初始化它。像这样的东西

public Constructor() {
    timer = new Timer(1000, new ActionLisentener(){
        public void actionPerformed(ActionEvent e) {
            // do something
        }
    });
}

您可以在计时器中增加计数。此外,计时器还包含您可以使用的方法stop() start()restart()

如果你想要一个看起来更整洁的构造函数,你总是可以创建一个内部监听器类,如

private class TimerListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        // do something
    }
}

然后就像这样初始化你的计时器

timer = new Timer(1000, new TimerListener());