Java Swing应用中多JFrame切换及时间更新的正确实践

java swing应用中多jframe切换及时间更新的正确实践

本文旨在解决Java Swing应用中,点击按钮打开新JFrame时出现空白窗口或无法关闭的问题。核心原因在于使用while(true)循环阻塞了Swing的事件调度线程(EDT),以及JFrame实例化不当。教程将详细讲解如何通过javax.swing.Timer实现UI元素的周期性更新,并提供多JFrame之间切换的正确管理方法,确保UI响应流畅且组件正常显示。

在开发Java Swing桌面应用程序时,经常需要实现从一个窗口(JFrame)打开另一个窗口的功能。然而,开发者可能会遇到新打开的JFrame显示为空白、组件不显示,甚至无法关闭的情况。这通常是由于不当的线程管理和JFrame实例化方式导致的。本教程将深入探讨这些问题,并提供一套健壮的解决方案。

理解Swing的事件调度线程(EDT)

Swing应用程序是单线程的,所有与UI相关的操作都必须在事件调度线程(Event Dispatch Thread, EDT)上执行。如果任何耗时操作(如无限循环、长时间计算或Thread.sleep())在EDT上运行,就会阻塞UI,导致界面冻结、组件不响应或不显示。

原始代码中,setTime()方法包含一个while(true)循环和Thread.sleep(1000)。这个循环被直接调用,意味着它在EDT上无限运行,从而完全阻塞了UI更新,使得新JFrame的组件无法被绘制,也无法响应关闭事件。

立即学习“Java免费学习笔记(深入)”;

核心解决方案:使用 javax.swing.Timer 进行周期性更新

为了在Swing应用中实现周期性任务(如时钟更新),而又不阻塞EDT,正确的做法是使用javax.swing.Timer。Swing Timer会在EDT上触发事件,因此所有更新UI的代码都将安全地执行。

Riffusion Riffusion

AI生成不同风格的音乐

Riffusion 87 查看详情 Riffusion

javax.swing.Timer 的基本用法如下:

import javax.swing.Timer;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;// ... 其他导入public class MySwingApp extends JFrame {    private JLabel timeLabel;    private Timer clockTimer;    public MySwingApp() {        // ... JFrame初始化代码        timeLabel = new JLabel("00:00:00");        getContentPane().add(timeLabel);        // 创建一个每1000毫秒(1秒)触发一次的Timer        clockTimer = new Timer(1000, new ActionListener() {            @Override            public void actionPerformed(ActionEvent e) {                // 在这里更新UI,例如更新时间标签                updateTimeLabel();            }        });        clockTimer.start(); // 启动Timer    }    private void updateTimeLabel() {        // 获取当前时间并更新timeLabel        // ...    }    // 当窗口关闭时,停止Timer以释放资源    private void stopTimer() {        if (clockTimer != null && clockTimer.isRunning()) {            clockTimer.stop();        }    }    // 示例:在窗口关闭时调用stopTimer    // this.addWindowListener(new WindowAdapter() {    //     @Override    //     public void windowClosing(WindowEvent e) {    //         stopTimer();    //     }    // });}

JFrame管理与最佳实践

除了线程问题,原始代码中还存在一些JFrame管理上的问题:

重复实例化JFrame: 在testTime_take_2类中,它已经继承了JFrame,但在构造函数内部又声明并实例化了一个private static JFrame frame;。这是不必要的,并且可能导致混淆。应该直接使用this来指代当前类的JFrame实例。setDefaultCloseOperation 的选择:JFrame.EXIT_ON_CLOSE: 当此窗口关闭时,应用程序将退出。这通常只用于主应用程序窗口。JFrame.DISPOSE_ON_CLOSE: 当此窗口关闭时,它将被销毁并释放其资源,但应用程序不会退出。这适用于辅助窗口或子窗口,允许返回到之前的窗口。Swing UI创建和更新的线程安全: 始终使用SwingUtilities.invokeLater()或java.awt.EventQueue.invokeLater()来创建和显示Swing UI组件。这确保所有UI操作都在EDT上执行。类命名规范: Java类名应遵循PascalCase(驼峰命名法),例如TestTimeTake2而不是testTime_take_2。

示例代码重构

以下是根据上述原则重构后的代码,解决了空白窗口、无法关闭以及时间更新不当的问题。

DisplayTimeDate.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 Timerpublic 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 JButton btnNewButton;    private Timer clockTimer; // 使用Swing Timer    public DisplayTimeDate() {        initializeForm();        setTime(); // 启动时间更新    }    private void initializeForm() {        // 当窗口关闭时停止Timer        addWindowListener(new WindowAdapter() {            @Override            public void windowClosing(WindowEvent e) {                if (clockTimer != null && clockTimer.isRunning()) {                    clockTimer.stop();                }                // 如果是主窗口,可以选择退出应用                // System.exit(0);             }        });        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // 使用DISPOSE_ON_CLOSE        setTitle("我的时钟程序");        setAlwaysOnTop(true);        setSize(350, 200);        setResizable(false);        setLocationRelativeTo(null); // 窗口居中        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("打开新窗口"); // 按钮文本更明确        btnNewButton.addActionListener(new ActionListener() {            @Override            public void actionPerformed(ActionEvent e) {                if (clockTimer != null && clockTimer.isRunning()) {                    clockTimer.stop(); // 停止当前窗口的Timer                }                dispose(); // 销毁当前窗口                // 在EDT上创建并显示新窗口                java.awt.EventQueue.invokeLater(() -> {                    new TestTimeTake2().setVisible(true);                });            }        });        getContentPane().add(btnNewButton);        getContentPane().add(timeLabel);        getContentPane().add(dayLabel);        getContentPane().add(dateLabel);    }    public void setTime() {        // 创建一个每1000毫秒(1秒)触发一次的Swing Timer        clockTimer = new Timer(1000, new ActionListener() {            @Override            public void actionPerformed(ActionEvent ae) {                // 在Timer的actionPerformed方法中更新UI,确保在EDT上执行                String time = timeFormat.format(Calendar.getInstance().getTime());                timeLabel.setText(time);                String day = dayFormat.format(Calendar.getInstance().getTime());                dayLabel.setText(day);                String date = dateFormat.format(Calendar.getInstance().getTime());                dateLabel.setText(date);            }        });        clockTimer.start(); // 启动Timer    }    public static void main(String[] args) {        // 确保在EDT上创建和显示JFrame        java.awt.EventQueue.invokeLater(() -> {            new DisplayTimeDate().setVisible(true);        });    }}

TestTimeTake2.java (新打开的窗口)

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 Timerimport 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 JLabel dateLabel;    private Timer clockTimer2; // 使用Swing Timer    public TestTimeTake2() {        initializeForm();        setTime(); // 启动时间更新    }    private void initializeForm() {        // 当窗口关闭时,停止Timer并重新打开DisplayTimeDate        addWindowListener(new WindowAdapter() {            @Override            public void windowClosing(WindowEvent e) {                if (clockTimer2 != null && clockTimer2.isRunning()) {                    clockTimer2.stop(); // 停止当前窗口的Timer                }                // 在EDT上重新打开DisplayTimeDate窗口                java.awt.EventQueue.invokeLater(() -> {                    new DisplayTimeDate().setVisible(true);                });            }        });        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);        setBounds(100, 100, 450, 300);        setLocationRelativeTo(null); // 窗口居中        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); // 使用null布局,需要手动设置组件位置和大小        timeLabel = new JLabel();        timeLabel.setHorizontalAlignment(SwingConstants.CENTER);        timeLabel.setBounds(151, 45, 112, 14);        contentPane.add(timeLabel); // 添加到contentPane        dayLabel = new JLabel();        dayLabel.setHorizontalAlignment(SwingConstants.CENTER);        dayLabel.setBounds(151, 100, 112, 14);        contentPane.add(dayLabel); // 添加到contentPane        dateLabel = new JLabel();        dateLabel.setHorizontalAlignment(SwingConstants.CENTER);        dateLabel.setBounds(151, 151, 112, 14);        contentPane.add(dateLabel); // 添加到contentPane    }    public void setTime() {        // 创建一个每1000毫秒(1秒)触发一次的Swing Timer        clockTimer2 = new Timer(1000, new ActionListener() {            @Override            public void actionPerformed(ActionEvent ae) {                // 在Timer的actionPerformed方法中更新UI                String time = timeFormat.format(Calendar.getInstance().getTime());                timeLabel.setText(time);                String day = dayFormat.format(Calendar.getInstance().getTime());                dayLabel.setText(day);                String date = dateFormat.format(Calendar.getInstance().getTime());                dateLabel.setText(date);            }        });        clockTimer2.start(); // 启动Timer    }}

注意事项与总结

EDT是关键: 任何对Swing组件的创建、修改或查询操作都必须在EDT上执行。使用javax.swing.Timer是实现周期性UI更新的黄金法则。setDefaultCloseOperation: 根据窗口的功能选择合适的关闭操作。对于非主窗口,JFrame.DISPOSE_ON_CLOSE是更安全的选项,它只会销毁当前窗口,而不会终止整个应用程序。资源管理: 当窗口关闭或不再需要时,及时停止相关的Timer,以避免资源泄漏和不必要的后台操作。WindowAdapter是一个很好的监听器来处理窗口关闭事件。布局管理器: 示例代码中TestTimeTake2使用了null布局(contentPane.setLayout(null);),这意味着所有组件的位置和大小都需要手动通过setBounds()方法设置。对于复杂的布局,建议使用Swing提供的各种布局管理器(如FlowLayout, BorderLayout, GridLayout, GridBagLayout等),它们能更好地适应不同屏幕尺寸和内容变化。避免重复实例化: 继承JFrame的类本身就是一个JFrame,无需在内部再创建一个JFrame实例。

通过遵循这些最佳实践,您可以构建出响应迅速、功能完善且易于维护的Java Swing应用程序,有效避免多窗口切换和UI更新中的常见问题

以上就是Java Swing应用中多JFrame切换及时间更新的正确实践的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1064559.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月2日 05:56:01
下一篇 2025年12月2日 05:56:22

相关推荐

  • 如何用CI流水线同步PHP环境配置 自动部署本地和生产环境

    要实现php环境配置的自动化同步和部署,核心是“配置即代码”和“环境隔离”。①使用配置模板(如.env.example或config.dist.php)替代直接提交敏感配置文件;②通过ci工具的环境变量管理敏感信息;③在ci流水线中根据环境变量动态生成配置文件;④使用sed、awk或php脚本完成配…

    2025年12月10日 好文分享
    000
  • WooCommerce 产品上线时长精准计算教程

    本教程详细介绍了如何在 WooCommerce 中准确显示产品上线时长,以年、月、日的形式呈现。针对传统基于时间戳的计算方法在处理闰年和月份天数差异时可能出现的误差,我们推荐使用 PHP 内置的 DateTime 和 DateInterval 类,它们提供了强大且精确的日期时间处理能力,确保计算结果…

    2025年12月10日
    000
  • 如何在Docker容器中调用PHP CLI命令 PHP脚本自动执行配置方法

    在docker容器中调用php cli命令并配置自动执行,可通过多种策略实现。1. 交互式或一次性执行:对运行中的容器使用docker exec -it php /path/to/script.php执行命令;对一次性任务使用docker run –rm -v /本地路径:/容器路径 p…

    2025年12月10日 好文分享
    000
  • 在WooCommerce中精确显示产品发布时长:避免闰年与月份差异问题

    本教程旨在解决WooCommerce产品发布时长显示不准确的问题,特别是因闰年和月份天数差异导致的计算误差。我们将深入探讨如何利用PHP内置的DateTime和DateInterval类,结合WooCommerce钩子,实现精确到年、月、日的产品发布时间计算与展示,确保日期逻辑的严谨性和可靠性。 理…

    2025年12月10日
    000
  • 优化WooCommerce产品发布时长显示:基于DateTime的精确计算教程

    本教程旨在解决WooCommerce中产品发布时长显示不准确的问题。通过利用PHP内置的DateTime和DateInterval类,我们可以精确计算产品自发布以来经过的年、月、日,有效避免了闰年和月份天数差异导致的计算偏差,确保显示结果的准确性和可靠性,提升用户体验。 概述:产品发布时长计算的挑战…

    2025年12月10日
    000
  • 精确计算 WooCommerce 产品上架时长:年、月、日显示教程

    本教程详细介绍了如何在 WooCommerce 中准确显示产品自发布以来经过的年、月、日时长。针对传统时间戳计算可能出现的闰年和月份天数差异导致的误差,我们采用 PHP 内置的 DateTime 和 DateInterval 对象进行精确计算。文章将提供完整的代码示例,并深入解析其工作原理,帮助您在…

    2025年12月10日
    000
  • Laravel 集合分块处理与多列布局实现

    本文将详细介绍如何在Laravel应用中利用集合的chunk方法,高效地将数据集合分块处理,并实现多列布局展示。通过具体代码示例,您将学会如何将大量数据按指定数量分割,从而优化前端渲染和用户体验,避免手动循环控制的复杂性,实现清晰、结构化的数据呈现。 在Web应用开发中,尤其是在展示列表或文章摘要等…

    2025年12月10日 好文分享
    000
  • Lumen 中分页结果集的编辑与属性添加

    本文档介绍了在 Lumen 框架下,如何对分页查询结果集进行编辑,并向结果中的每个对象动态添加新属性。主要解决了在分页查询后,如何有效地关联其他数据表信息,并将其整合到最终返回的 JSON 数据中的问题。通过示例代码,演示了如何正确地向 StdClass 对象添加属性,避免常见的 “Cr…

    2025年12月10日
    000
  • 优化 Laravel 集合循环:使用 chunk 方法实现多列布局

    Laravel 集合的 chunk 方法提供了一种高效地将大型数据集分割成小块的机制,尤其适用于在视图中实现多列布局。本文将详细介绍如何利用 chunk 方法,将集合数据按指定大小分块,并结合 Bootstrap 等前端框架,优雅地在网页上呈现多列内容,避免传统循环的局限性,提升数据展示的灵活性和可…

    2025年12月10日
    000
  • 在 Laravel 中使用 chunk() 方法优化集合数据的多列布局

    本文深入探讨了在 Laravel 应用中如何高效地将集合(Collection)数据分块并以多列形式展示。通过利用 Laravel 集合提供的 chunk() 方法,开发者可以轻松地将大型数据集按指定大小分割成若干子集合,从而实现灵活的布局控制,避免了手动计算索引或复杂逻辑的困扰,显著提升代码的可读…

    2025年12月10日 好文分享
    000
  • Laravel集合分块处理:高效实现多列数据展示

    本文详细介绍了如何在Laravel应用中,利用集合(Collection)的chunk()方法高效地将数据分块,并以多列布局的形式展示。针对传统循环在实现多列布局时遇到的问题,chunk()方法提供了一种简洁、灵活且语义化的解决方案,帮助开发者轻松构建结构清晰、易于维护的视图。 传统多列布局的挑战与…

    2025年12月10日
    000
  • Laravel 集合分块:高效实现多列数据布局

    本教程详细讲解如何在 Laravel 中利用集合(Collection)的 chunk() 方法将数据分块,从而实现多列布局的展示需求。通过实例代码,演示如何将一个集合均匀地分割成指定大小的子集合,并结合前端框架(如 Bootstrap)优雅地渲染数据,避免了传统循环判断的复杂性与局限性,提升了代码…

    2025年12月10日
    000
  • 自定义WooCommerce产品查询:在商店和分类页面实现URL参数过滤

    本文档旨在指导开发者如何通过URL参数自定义WooCommerce产品查询,实现更灵活的产品筛选功能。我们将使用pre_get_posts action hook,该hook适用于商店和分类页面,允许我们根据URL参数动态修改产品查询条件,从而实现自定义的产品过滤。通过本文,你将学会如何在WooCo…

    2025年12月10日
    000
  • WooCommerce 产品分类页面筛选失效问题排查与解决方案

    本文针对 WooCommerce 商店中自定义筛选器在产品分类页面失效的问题,提供了一种基于 pre_get_posts 钩子的解决方案。通过此方案,开发者可以在主查询中设置 meta_query,从而实现在主商店页面和产品分类页面都能正常工作的自定义筛选功能。本文将详细介绍如何使用 pre_get…

    2025年12月10日
    000
  • WooCommerce 产品分类页面筛选失效问题解决方案

    本文将解决 WooCommerce 中使用 woocommerce_product_query hook 在主商店页面工作正常,但在产品分类页面失效的问题。通过使用 pre_get_posts action hook,我们可以确保自定义筛选器在所有产品页面(包括主商店和分类页面)上都能正确应用。 w…

    2025年12月10日
    000
  • 如何在Docker中连接PHP与Redis服务 PHP环境中配置Redis通信方式

    要在docker中让php与redis“握手”,需配置网络和php的redis扩展。1. 使用docker-compose.yml定义php和redis服务,并确保它们处于同一网络以便通过容器名通信;2. 编写dockerfile安装php环境及redis扩展;3. 编写php代码测试redis连接…

    2025年12月10日 好文分享
    000
  • 从 Python 到 PHP 解码 zlib 压缩数据的正确方法

    本文旨在解决 Python 使用 zlib 压缩数据后,如何在 PHP 中正确解码的问题。重点在于避免将压缩后的二进制数据转换为字符串,而是直接发送原始字节流。通过示例代码和详细解释,帮助读者理解并实现跨语言的压缩数据传输与解码。 在 Python 和 PHP 之间传递压缩数据时,一个常见的错误是将…

    2025年12月10日
    000
  • 解码Python Zlib压缩数据到PHP的正确方法

    本文旨在帮助开发者解决Python使用zlib压缩数据后,在PHP端无法正确解压缩的问题。通过本文,你将了解如何避免常见的错误,并学习如何在Python和PHP之间正确地传输和解压缩zlib压缩的二进制数据,从而实现数据的无损传递。 在Python中使用zlib.compress()压缩数据后,直接…

    2025年12月10日
    000
  • Lumen 分页结果集编辑与数据关联

    本文档介绍了在 Lumen 框架中,如何对分页后的结果集进行编辑,并添加来自其他表的数据关联。通过示例代码,演示了如何将关联的分类信息添加到商品数据中,并解决了在动态添加属性时遇到的“Creating default object from empty value”错误。 在构建 REST API …

    2025年12月10日
    000
  • Laravel中使用required_without验证规则实现二选一校验

    本文旨在讲解如何在Laravel框架中使用required_without验证规则,实现表单中两个字段(例如Email和Telephone)二选一必填,并且在填写时校验格式的功能。通过结合nullable规则,可以避免在字段为空时触发格式验证,从而实现更灵活的验证逻辑。 在Web应用开发中,经常会遇…

    2025年12月10日
    000

发表回复

登录后才能评论
关注微信