为什么我的循环 GUI 计时器没有显示?

Why is my looping GUI timer not showing up?

我试图在不使用 javax.swing.Timer的情况下制作一个 GUI 计时器 (有点奇怪的任务),但我无法让它工作。它应该让线程休眠 1 秒,将 1 添加到 seconds,然后(无限地)重复。当我 运行 我的程序时,图标出现了,但是 window 没有出现。我猜我的错误是在 Thread.sleep(1000); 行或那个区域,但我不确定为什么它不起作用。 Thread.sleep(millis)与 swing 应用程序不兼容吗?我必须多线程吗?这是我的程序:

import java.awt.*;
import javax.swing.*;

public class GUITimer extends JFrame {
    private static final long serialVersionUID = 1L;
    private int seconds = 0;

    public GUITimer() {
        initGUI();
        pack();
        setVisible(true);
        setResizable(false);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void initGUI(){
        JLabel title = new JLabel("Timer");
        Font titleFont = new Font(Font.SERIF, Font.BOLD, 32);
        title.setFont(titleFont);
        title.setHorizontalAlignment(JLabel.CENTER);
        title.setBackground(Color.BLACK);
        title.setForeground(Color.WHITE);
        title.setOpaque(true);
        add(title, BorderLayout.NORTH);
        JLabel timeDisplay = new JLabel(Integer.toString(seconds));//this label shows seconds
        add(timeDisplay, BorderLayout.CENTER);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        seconds++;
        initGUI();
    }

    public static void main(String[] args) {
        try {
            String className = UIManager.getCrossPlatformLookAndFeelClassName();
            UIManager.setLookAndFeel(className);
        }
        catch (Exception e) {}

        EventQueue.invokeLater(new Runnable() {
            public void run() {
                new GUITimer();
            }
        });
    }
}

编辑:
我注意到当我在我的方法 initGUI() 中打印 seconds 到控制台时,它会正确地以一秒为增量打印它们。所以当它看起来像:

private void initGUI() {
    System.out.println(seconds);
    //...

它每秒打印 seconds 的值(JLabel 应该如何)。这表明我的循环工作正常,我的 Thread.sleep(1000) 也正常。我现在唯一的问题是框架没有显示。

您的主要 window 没有出现,因为您在构造函数中调用了无限递归。将不会创建 GUITimer 并锁定主线程。

为此您需要使用多线程。绘制时间的主线程,第二个线程增量并将值放入标签

例如:

import javax.swing.*;
import java.awt.*;

public class GUITimer extends JFrame
{
    private static final long serialVersionUID = 1L;
    private int seconds = 0;
    private Thread timerThread;
    private JLabel timeDisplay;

    public GUITimer()
    {
        initGUI();
        pack();
        setVisible(true);
        setResizable(false);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void initGUI()
    {
        JLabel title = new JLabel("Timer");
        Font titleFont = new Font(Font.SERIF, Font.BOLD, 32);
        title.setFont(titleFont);
        title.setHorizontalAlignment(JLabel.CENTER);
        title.setBackground(Color.BLACK);
        title.setForeground(Color.WHITE);
        title.setOpaque(true);
        add(title, BorderLayout.NORTH);
        timeDisplay = new JLabel(Integer.toString(seconds));//this label shows seconds
        add(timeDisplay, BorderLayout.CENTER);
    }

    public void start()
    {
        seconds = 0;
        timerThread = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                while(true)
                {
                    timeDisplay.setText(Integer.toString(seconds++));
                    try
                    {
                        Thread.sleep(1000L);
                    }
                    catch(InterruptedException e) {}
                }
            }
        });
        timerThread.start();
    }

    public void stop()
    {
        timerThread.interrupt();
    }

    public static void main(String[] args)
    {
        try
        {
            GUITimer timer = new GUITimer();
            timer.start();
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}

核心问题是,您通过连续调用 initGUI 来阻止 UI,这最终会失败并显示 WhosebugException,因为方法调用永远不会结束 [=18] =]

首选是使用 Swing Timer,但既然您已经声明不想这样做,更好的解决方案是使用 SwingWorker,原因为此 - Swing 不是线程安全的,SwingWorker 提供了一种方便的机制允许我们安全地更新 UI。

因为SwingTimerThead.sleep都只保证了最小延迟,它们不是衡量时间流逝的可靠手段,最好利用Java 8 的 Date/Time API 而不是

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class Test {

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

    public Test() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JLabel label = new JLabel("00:00:00");
        private TimeWorker timeWorker;

        public TestPane() {
            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            add(label, gbc);

            JButton button = new JButton("Start");
            add(button, gbc);

            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (timeWorker == null) {
                        timeWorker = new TimeWorker(label);
                        timeWorker.execute();
                        button.setText("Stop");
                    } else {
                        timeWorker.cancel(true);
                        timeWorker = null;
                        button.setText("Start");
                    }
                }
            });
        }
    }

    public class TimeWorker extends SwingWorker<Void, Duration> {

        private JLabel label;

        public TimeWorker(JLabel label) {
            this.label = label;
        }

        @Override
        protected Void doInBackground() throws Exception {
            LocalDateTime startTime = LocalDateTime.now();
            Duration totalDuration = Duration.ZERO;
            while (!isCancelled()) {
                LocalDateTime now = LocalDateTime.now();
                Duration tickDuration = Duration.between(startTime, now);
                publish(tickDuration);
                Thread.sleep(500);
            }

            return null;
        }

        @Override
        protected void process(List<Duration> chunks) {
            Duration duration = chunks.get(chunks.size() - 1);
            String text = format(duration);
            label.setText(text);
        }

        public String format(Duration duration) {
            long hours = duration.toHours();
            duration = duration.minusHours(hours);
            long minutes = duration.toMinutes();
            duration = duration.minusMinutes(minutes);
            long millis = duration.toMillis();
            long seconds = (long)(millis / 1000.0);

            return String.format("%02d:%02d:%02d", hours, minutes, seconds);
        }
    }
}