问题
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