theProgrammer101 theProgrammer101 - 1 year ago 38
Java Question

Why is my looping GUI timer not showing up?

I'm trying to make a GUI timer without using

javax.swing.Timer
, 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 dock 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
JFrame
applications? 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:

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.

Answer Source

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);
        }
    }
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download