首页 > Java > java教程 > 正文

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

DDD
发布: 2025-11-28 18:01:51
原创
124人浏览过

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

Noiz Agent
Noiz Agent

AI声音创作Agent平台

Noiz Agent 323
查看详情 Noiz Agent

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更新中的常见问题

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

Windows激活工具
Windows激活工具

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

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

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