0

0

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

DDD

DDD

发布时间:2025-11-28 18:01:51

|

150人浏览过

|

来源于php中文网

原创

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的代码都将安全地执行。

吐槽大师
吐槽大师

吐槽大师(Roast Master) - 终极 AI 吐槽生成器,适用于 Instagram,Facebook,Twitter,Threads 和 Linkedin

下载

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管理上的问题:

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

注意事项与总结

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

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

相关文章

Windows激活工具
Windows激活工具

Windows激活工具是正版认证的激活工具,永久激活,一键解决windows许可证即将过期。可激活win7系统、win8.1系统、win10系统、win11系统。下载后先看完视频激活教程,再进行操作,100%激活成功。

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

254

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1089

2024.03.01

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

107

2023.09.25

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

765

2023.08.10

Java 并发编程高级实践
Java 并发编程高级实践

本专题深入讲解 Java 在高并发开发中的核心技术,涵盖线程模型、Thread 与 Runnable、Lock 与 synchronized、原子类、并发容器、线程池(Executor 框架)、阻塞队列、并发工具类(CountDownLatch、Semaphore)、以及高并发系统设计中的关键策略。通过实战案例帮助学习者全面掌握构建高性能并发应用的工程能力。

99

2025.12.01

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

25

2026.03.13

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

44

2026.03.12

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

174

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

50

2026.03.10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.4万人学习

C# 教程
C# 教程

共94课时 | 11.3万人学习

Java 教程
Java 教程

共578课时 | 81.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号