如何用setText方法实现计时器?JLabel更新失效问题排查求助
问题分析与解决方案
嘿,这个问题我之前踩过坑!核心原因是你阻塞了Swing的事件调度线程(EDT),而且没有遵循Swing的UI更新规则,才导致了UI卡顿和只显示最后一个数值的问题。咱们一步步拆解:
为什么会出现这个问题?
- EDT被阻塞:Swing的所有UI事件(比如按钮点击)和UI更新都依赖事件调度线程(EDT)执行。你的
setTimer()方法是在actionPerformed里调用的,而actionPerformed本身就在EDT上运行。你用while(true)循环加Thread.sleep(1000)直接卡死了EDT——它根本没时间去更新JLabel的显示,直到整个循环结束才会一次性刷新最后一次的setText结果,所以你只能看到最后一个数值,按钮也会全程卡顿。 - 控制台输出不受影响:
System.out.println(i)能正常输出,是因为它运行在控制台的独立线程,和EDT互不干扰。
正确的解决方案:使用Swing Timer
Swing专门提供了javax.swing.Timer来处理定时UI更新任务,它不会阻塞EDT,而且自动在EDT上执行UI更新操作,完美适配你的需求。下面是修改后的完整代码:
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.border.EtchedBorder; public class Game extends JFrame implements ActionListener { JLabel jTimer = new JLabel();//Showing time JLabel jScore = new JLabel("");//Showing score // 新增Swing Timer和计数变量 private Timer swingTimer; private int timerCount = 0; Game() { JFrame Gframe = new JFrame("Game play"); Gframe.setBounds(400, 400, 800, 800); Gframe.setLayout(null); JPanel pScore = new JPanel(); EtchedBorder border = new EtchedBorder(); JLabel score = new JLabel(" Score "); Font font1 = new Font("굴림", Font.PLAIN, 20); jScore.setPreferredSize(new Dimension(5, 5)); score.setFont(font1); score.setBounds(330, 30, 100, 100); score.setBorder(border); Font font2 = new Font("고딕체", Font.PLAIN, 20); JLabel ttime = new JLabel(" Time "); JButton start = new JButton("START"); start.setFont(font2); start.setBounds(150, 40, 100, 100); start.setBorder(border); start.addActionListener(this); jTimer.setLayout(null); jTimer.setBounds(330, 30, 300, 300); ttime.setFont(font2); ttime.setBounds(200, 15, 100, 100); ttime.setBorder(border); pScore.setLayout(new GridLayout(2, 2)); pScore.setBounds(330, 30, 200, 100); pScore.add(score); pScore.add(ttime); pScore.add(jScore); pScore.add(jTimer); add(start); Gframe.add(pScore);//Including Score&Time Gframe.add(start);//Adding Start Button Gframe.setVisible(true); } // 重写计时方法,用Swing Timer实现 public void setTimer() { timerCount = 0; // 初始化Timer:每1000ms触发一次,触发时执行UI更新 swingTimer = new Timer(1000, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (timerCount < 60) { jTimer.setText(timerCount + "Sec"); System.out.println(timerCount); timerCount++; } else { // 计时结束,停止Timer swingTimer.stop(); } } }); swingTimer.start(); } public void actionPerformed(ActionEvent e) { String str = e.getActionCommand(); if (str.equals("START")) { System.out.println("Counting"); setTimer(); } } // 标准Swing程序启动方式:在EDT初始化UI public static void main(String[] args) { SwingUtilities.invokeLater(() -> new Game()); } }
关键修改点说明
- 新增
swingTimer和timerCount变量:分别管理定时任务和计时数值 - 替换while循环为Swing Timer:它会在后台调度定时任务,每次触发时自动在EDT上执行UI更新,完全不会阻塞UI
- 计时到60秒时停止Timer:避免无限触发
- 添加
main方法并使用SwingUtilities.invokeLater:这是Swing程序的标准启动方式,确保UI组件在EDT上初始化,避免潜在的线程安全问题
额外注意事项
- 永远不要在EDT上执行长时间运行的任务(比如循环、sleep、IO操作),否则必现UI卡顿
- Swing的所有UI更新操作必须在EDT上执行,Swing Timer帮你自动做到了这一点
- 你的代码里用了
setLayout(null)手动布局组件,虽然能快速定位,但推荐使用Swing内置的布局管理器(比如BorderLayout、GridLayout),这样窗口大小变化时组件能自动调整,代码更易维护
内容的提问来源于stack exchange,提问作者Akses
相关产品推荐
相关产品推荐

