Why is my looping GUI timer not showing up?

北城余情 提交于 2019-12-25 05:07:07

问题


I'm trying to make a GUI timer without using javax.swing.Timer(kind of a strange task), but I am having trouble making it work. It's supposed to sleep the thread for 1 second, add 1 to seconds, and repeat(infinitely). When I run my program, the icon shows up, but the window does not appear. I'm guessing my error is in the Thread.sleep(1000); line or in that area, but I'm not sure why it doesn't work. Is Thread.sleep(millis)not compatible with swing applications? Do I have to multithread? Here's my program:

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

EDIT:
I noticed when I print seconds in my method initGUI() to console, it prints them incrementally by one second correctly. So when it looks like:

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

it prints the value of seconds after every second(How the JLabel should). This shows that my loop is working fine, and my Thread.sleep(1000) is OK also. My only problem now, is that the frame is not showing up.


回答1:


Your main window does not appear, because you called infinite recursion inside constructor. GUITimer will not be created and this lock main thread.

You need use multithreading for this aim. Main thread for drawing time, second thread increment and put value to label

For example:

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



回答2:


The core issue is, you're blocking the UI by continuously calling initGUI, which will eventually fail with a StackOverFlowException, as the method calls never end

The preference would be to use a Swing Timer, but since you've stated you don't want to do that, a better solution would be to use a SwingWorker, the reason for this - Swing is NOT thread safe and SwingWorker provides a convenient mechanism for allowing us to update the UI safely.

Because both Swing Timer and Thead.sleep only guarantee a minimum delay, they are not a reliable means for measuring the passage of time, it would be better to make use of Java 8's Date/Time API instead

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


来源:https://stackoverflow.com/questions/44103925/why-is-my-looping-gui-timer-not-showing-up

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!