本文介绍了创建基于 GUI 的计时器(或秒表)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我花了一些时间来开发一个程序,显示从用户单击开始按钮开始的已用时间或剩余时间,很像秒表或天文钟,可以测量时间直到您停止和重置.测量经过时间的其他示例是赛车游戏中的单圈时间和其他游戏中的时间限制(以毫秒为单位).

What I took some time to work on is a program showing time elapsed, or time remaining, from where the user clicks the start button, much like a stopwatch or a chronometer which measures time until you stop and reset. Other examples of measuring time elapsed are those lap times in racing games and time limits, with milliseconds, in other games.

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

I'm running into some trouble, though, because my own stopwatch is not running at the same rate as actual time. It takes longer than one second for my timer to run one second down or up.

代码就在这里:(GUI 完美运行;我更关心如何控制值以显示经过的时间,即每过去一秒,JLabel 少了一秒.我无法修改传递给 Thread.sleep 的参数,因为它会使计时器变得更糟.)

The code is right here: (the GUI works perfectly; I'm more concerned about how to control the values to show the time elapsed in a way that for every second passed, the time displayed on the JLabel is one second less. I cannot modify the argument that's passed into Thread.sleep because it will make the timer much worse.)

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 的文本类.)

There's gotta be another way to make the timer in sync with real time, just like a real stopwatch, instead of having to rely on three separate threads, which I think are too many for such a large programming project, but okay at entry level for now. (oh, by the way, the DecimalFormat class is to format the numbers properly like a real stopwatch, though there are no decimal values to round. It's only until now, at the time I posted this, that there exists a text class called SimpleDateFormat.)

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

In other words, I want this program to be just a real stopwatch. If this is not the case, then how do you create, or use, a stopwatch, in Java games, for example?

推荐答案

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

The biggest problem you would face is being able to get the various Runnables to run at a consistent rate. Basically, there's no real way to know when the Executor will actual execute the task you provide it, as it has it's own over heads.

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

In this particular case, I would recommend reducing the number of active Threads to one, this reduces any additional overheads involved in the creation and execution of other Threads and provides you with the best control over getting things to work as close to the time you want as possible.

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

Instead of using a Thread, I would instead, use a javax.swing.Timer, primary because it's simple and is executed within the context of the EDT which makes it safer to update the UI from within, for example

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

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

I would also stop "guessing" at the time. There simply is no guarantee that the amount of time passed between "updates" is accurate.

相反,我会在秒表启动时获取当前时间,并在 Timer 的每个滴答声中,从当前时间中减去它,从而得出经过的时间.然后您可以使用它来确定秒表的当前值应该是什么...

Instead, I would grab the current time when the stop watch is started and on each tick of the Timer, subtract it from the current time, giving you the amount of time that has passed. You can then use that to determine what the current value of the stop watch should be...

例如...

添加以下实例字段...

Adding the following instance fields...

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

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

Updating the startButton to include capturing the start time...

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));
    }
});

查看Swing 中的并发了解更多详情

这篇关于创建基于 GUI 的计时器(或秒表)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-22 23:24
查看更多