
本文旨在解决java swing应用中点击按钮打开新jframe时出现空白、无法关闭或组件不显示的问题。核心原因通常在于不当的ui线程操作(如使用`while(true)`阻塞事件调度线程)和jframe生命周期管理错误。我们将详细讲解如何利用`javax.swing.timer`进行安全的ui更新,并正确处理jframe的实例化、显示与关闭,确保应用程序的响应性和组件的正确渲染。
在Java Swing应用程序中,所有UI相关的操作都必须在事件调度线程(Event Dispatch Thread, EDT)上执行。EDT负责处理用户交互事件、绘制组件以及更新UI。如果EDT被长时间阻塞,应用程序就会变得无响应,表现为界面卡死、组件不显示或无法关闭窗口等。
原始代码中存在以下几个主要问题:
阻塞EDT的无限循环 (while(true)): 在DisplayTimeDate和testTime_take_2类的setTime()方法中,都使用了while(true)循环配合Thread.sleep(1000)来定时更新时间。这种做法是错误的,因为它直接在EDT上运行一个无限循环,导致EDT被阻塞。一旦EDT被阻塞,它就无法处理任何其他事件,包括绘制组件、响应用户点击或处理窗口关闭请求,从而导致:
JFrame的重复实例化与不当管理: 在testTime_take_2类中,该类本身已经通过extends JFrame继承了JFrame,但在其构造器内部又声明并实例化了一个private static JFrame frame = new JFrame();。这种做法是冗余且错误的,它创建了一个额外的、未被正确管理的JFrame实例,而非使用this(即当前testTime_take_2对象)作为主窗口。这可能导致组件被添加到错误的JFrame实例上,从而使期望的窗口看起来是空白的。
不当的默认关闭操作 (JFrame.EXIT_ON_CLOSE): 在DisplayTimeDate类中,setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)意味着当此窗口关闭时,整个应用程序都会退出。这在多窗口应用中可能不是期望的行为,特别是当您希望关闭一个子窗口后返回主窗口时。更合适的做法通常是JFrame.DISPOSE_ON_CLOSE,它只会释放当前窗口的资源,而不会终止整个JVM。
为了解决上述问题,我们需要遵循Swing的线程模型,并采用正确的JFrame管理策略。
javax.swing.Timer是Swing专门为定时任务设计的,它会在EDT上触发事件,因此是进行周期性UI更新的正确方式。
立即学习“Java免费学习笔记(深入)”;
示例代码(setTime 方法的修改):
import javax.swing.Timer; // 注意导入的是javax.swing.Timer
// ... 其他代码 ...
public void setTime() {
// 创建一个Swing Timer,每1000毫秒(1秒)触发一次
clockTimer = new Timer(1000, new java.awt.event.ActionListener() {
@Override
public void actionPerformed(java.awt.event.ActionEvent ae) {
// 在这里执行UI更新逻辑,这些代码会在EDT上运行
time = timeFormat.format(Calendar.getInstance().getTime());
timeLabel.setText(time);
day = dayFormat.format(Calendar.getInstance().getTime());
dayLabel.setText(day);
date = dateFormat.format(Calendar.getInstance().getTime());
dateLabel.setText(date);
}
});
// 启动Timer
clockTimer.start();
}示例代码(testTime_take_2 构造器及关闭事件处理的修改):
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
// ... 其他导入 ...
public class TestTimeTake2 extends JFrame {
// ... 成员变量 ...
// 移除 private static JFrame frame; 及其在构造器中的实例化
public TestTimeTake2() {
initializeForm();
}
private void initializeForm() {
// 添加窗口监听器,处理窗口关闭事件
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
clockTimer2.stop(); // 停止当前窗口的Timer
// 在当前窗口关闭后,重新显示 DisplayTimeDate 窗口
java.awt.EventQueue.invokeLater(() -> {
new DisplayTimeDate().setVisible(true);
});
}
});
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // 关闭当前窗口时只释放资源
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
// ... 组件初始化和布局 ...
setLocationRelativeTo(null); // 窗口居中显示
setTime(); // 启动Timer
}
// ... setTime() 方法使用 Swing Timer ...
}当点击按钮打开新窗口时,通常会关闭当前窗口(或使其不可见),然后打开新窗口。确保这些操作在EDT上安全执行。
示例代码(DisplayTimeDate 按钮事件处理的修改):
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
// ... 其他导入 ...
public class DisplayTimeDate extends JFrame {
// ... 成员变量 ...
public DisplayTimeDate() {
initializeForm();
}
private void initializeForm() {
// 为当前窗口添加关闭监听,确保在窗口关闭时停止Timer
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
clockTimer.stop();
}
});
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // 更改为 DISPOSE_ON_CLOSE
// ... 其他初始化 ...
btnNewButton = new JButton("");
btnNewButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dispose(); // 关闭当前窗口(DisplayTimeDate)
// 在EDT上创建并显示新的窗口 (TestTimeTake2)
java.awt.EventQueue.invokeLater(() -> {
new TestTimeTake2().setVisible(true);
});
}
});
// ... 添加组件 ...
setLocationRelativeTo(null); // 窗口居中显示
setTime();
}
// ... setTime() 方法使用 Swing Timer ...
}以下是根据上述原则重构后的DisplayTimeDate.java和TestTimeTake2.java代码。
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer; // 导入 Swing Timer
public class DisplayTimeDate extends JFrame {
private static final long serialVersionUID = 425524L;
private SimpleDateFormat timeFormat;
private SimpleDateFormat dayFormat;
private SimpleDateFormat dateFormat;
private JLabel timeLabel;
private JLabel dayLabel;
private JLabel dateLabel;
private String time;
private String day;
private String date;
private JButton btnNewButton;
private Timer clockTimer; // 使用 javax.swing.Timer
public DisplayTimeDate() {
initializeForm();
}
private void initializeForm() {
// 监听窗口关闭事件,停止Timer
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if (clockTimer != null && clockTimer.isRunning()) {
clockTimer.stop();
}
}
});
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // 仅关闭当前窗口
setTitle("My Clock Program");
setAlwaysOnTop(true); // 窗口总在最上层
setSize(350, 200);
setResizable(false);
timeFormat = new SimpleDateFormat("hh:mm:ss a");
dayFormat = new SimpleDateFormat("EEEE");
dateFormat = new SimpleDateFormat("MMMMM dd, yyyy");
timeLabel = new JLabel();
timeLabel.setFont(new Font("Verdana", Font.PLAIN, 50));
timeLabel.setForeground(new Color(0x00FF00));
timeLabel.setBackground(Color.black);
timeLabel.setOpaque(true);
dayLabel = new JLabel();
dayLabel.setFont(new Font("Ink Free", Font.PLAIN, 35));
dateLabel = new JLabel();
dateLabel.setFont(new Font("Ink Free", Font.PLAIN, 25));
getContentPane().setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
btnNewButton = new JButton("Open New Frame"); // 按钮文本更明确
btnNewButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dispose(); // 关闭当前窗口
// 在EDT上创建并显示 TestTimeTake2 窗口
java.awt.EventQueue.invokeLater(() -> {
new TestTimeTake2().setVisible(true);
});
}
});
getContentPane().add(btnNewButton);
getContentPane().add(timeLabel);
getContentPane().add(dayLabel);
getContentPane().add(dateLabel);
setLocationRelativeTo(null); // 窗口居中
setTime(); // 启动定时器
}
public void setTime() {
// 创建并启动Swing Timer
clockTimer = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
time = timeFormat.format(Calendar.getInstance().getTime());
timeLabel.setText(time);
day = dayFormat.format(Calendar.getInstance().getTime());
dayLabel.setText(day);
date = dateFormat.format(Calendar.getInstance().getTime());
dateLabel.setText(date);
}
});
clockTimer.start();
}
public static void main(String[] args) {
// 确保在EDT上创建并显示JFrame
java.awt.EventQueue.invokeLater(() -> {
new DisplayTimeDate().setVisible(true);
});
}
}import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.Timer; // 导入 Swing Timer
import javax.swing.border.EmptyBorder;
public class TestTimeTake2 extends JFrame {
private static final long serialVersionUID = 342241L;
private JPanel contentPane;
private SimpleDateFormat timeFormat;
private SimpleDateFormat dayFormat;
private SimpleDateFormat dateFormat;
private JLabel timeLabel;
private JLabel dayLabel;
private String day;
private String time;
private String date;
private JLabel dateLabel;
private Timer clockTimer2; // 使用 javax.swing.Timer
public TestTimeTake2() {
initializeForm();
}
private void initializeForm() {
// 监听窗口关闭事件
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if (clockTimer2 != null && clockTimer2.isRunning()) {
clockTimer2.stop(); // 停止当前窗口的Timer
}
// 在此可以决定关闭后是否重新打开 DisplayTimeDate
java.awt.EventQueue.invokeLater(() -> {
new DisplayTimeDate().setVisible(true); // 重新打开 DisplayTimeDate
});
}
});
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // 仅关闭当前窗口
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
timeFormat = new SimpleDateFormat("hh:mm:ss a");
dayFormat = new SimpleDateFormat("EEEE");
dateFormat = new SimpleDateFormat("dd-MMMMM-yyyy");
contentPane.setLayout(null); // 使用绝对布局,需手动设置组件位置和大小
timeLabel = new JLabel();
timeLabel.setHorizontalAlignment(SwingConstants.CENTER);
timeLabel.setBounds(151, 45, 112, 14);
// timeLabel.setText(time); // 初始值可为空,或在setTime()中立即更新
dayLabel = new JLabel();
dayLabel.setHorizontalAlignment(SwingConstants.CENTER);
dayLabel.setBounds(151, 100, 112, 14);
// 注意:getContentPane() 和 contentPane 都可以添加组件,但通常选择其中一个
// 如果setLayout(null)设置在contentPane上,则应将组件添加到contentPane
contentPane.add(timeLabel); // 添加到 contentPane
contentPane.add(dayLabel); // 添加到 contentPane
dateLabel = new JLabel();
dateLabel.setHorizontalAlignment(SwingConstants.CENTER);
dateLabel.setBounds(151, 151, 112, 14);
contentPane.add(dateLabel);
setLocationRelativeTo(null); // 窗口居中
setTime(); // 启动定时器
}
public void setTime() {
// 创建并启动Swing Timer
clockTimer2 = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
time = timeFormat.format(Calendar.getInstance().getTime());
timeLabel.setText(time);
day = dayFormat.format(Calendar.getInstance().getTime());
dayLabel.setText(day);
date = dateFormat.format(Calendar.getInstance().getTime());
dateLabel.setText(date);
}
});
clockTimer2.start();
}
// 移除 main 方法,因为 TestTimeTake2 不应独立启动,而是由 DisplayTimeDate 启动
}通过遵循这些最佳实践,您的Swing应用程序将更加健壮、响应迅速,并能正确显示所有UI组件。
以上就是Java Swing应用中JFrame空白、卡死与组件不显示问题的解决方案的详细内容,更多请关注php中文网其它相关文章!
Windows激活工具是正版认证的激活工具,永久激活,一键解决windows许可证即将过期。可激活win7系统、win8.1系统、win10系统、win11系统。下载后先看完视频激活教程,再进行操作,100%激活成功。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号