首页 文章

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

提问于
浏览
0

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

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

代码就在这里:( 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 回答

  • 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());
    
  • 3

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

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

    而不是使用 Thread ,而是使用 javax.swing.Timer ,primary,因为它很简单并且在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();
                }
            });
        }
    }
    

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

    相反,我会 grab 秒表开始时的当前时间和 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

相关问题