12.10 在主线程或子线程中更新 UI

发布于:2025-06-20 ⋅ 阅读:(14) ⋅ 点赞:(0)

Swing 是单线程工具包,所有对 UI 的操作都应发生在**事件调度线程(Event Dispatch Thread, EDT)**中。


 invokeLater

 正确更新控件内容的方法

1. 直接更新(适用于已在 EDT 中)

如果你确定当前代码是在 EDT 中运行的(例如在按钮点击事件或 paint() 方法中),可以直接更新控件:

label.setText("新的文本");
textField.setText("新输入内容");
button.setEnabled(false);

2. 如果不在 EDT 中(如在主线程或子线程中更新 UI)

必须使用 SwingUtilities.invokeLater()SwingUtilities.invokeAndWait() 来确保 UI 更新在 EDT 中执行:

SwingUtilities.invokeLater(() -> {
    label.setText("更新后的文本");
    textField.setText("更新输入框内容");
});
  • invokeLater():异步执行,不会阻塞当前线程。
  • invokeAndWait():同步执行,会阻塞当前线程直到 UI 操作完成(常用于初始化阶段)。

 示例

import javax.swing.*;
import java.awt.*;
import java.util.Timer;
import java.util.TimerTask;

public class UpdateLabelExample {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Swing 更新示例");
        JLabel label = new JLabel("初始文本", SwingConstants.CENTER);
        frame.add(label, BorderLayout.CENTER);
        frame.setSize(300, 200);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);

        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            int count = 0;
            @Override
            public void run() {
                String newText = "更新次数: " + (++count);
                SwingUtilities.invokeLater(() -> {
                    label.setText(newText);
                });
            }
        }, 0, 1000); // 每秒更新一次
    }
}

 动态刷新其他组件示例

控件类型 设置内容方法
JLabel setText("新文本")
JTextField setText("新内容")
JButton setText("按钮文字")
JComboBox setSelectedItem("选项")
JTextArea setText("新段落")
JList 需要更新 ListModel

注意事项

  • 不要在非 EDT 线程中直接修改 UI 组件!否则可能导致界面显示异常甚至崩溃。
  • 使用多线程时,务必配合 SwingWorker 或 invokeLater()
  • 对于复杂界面更新,考虑使用 SwingWorker 来后台处理数据并在完成后更新 UI。

 SwingWorker 

SwingWorker 是 Java Swing 中用于处理后台任务的一个类,旨在解决在执行长时间运行的任务时保持用户界面响应的问题。它使得开发者能够将耗时的操作从事件调度线程(Event Dispatch Thread, EDT)中移出,并提供了一种机制来更新UI而不会导致界面冻结。

基本概念

  • EDT(Event Dispatch Thread):所有Swing组件的绘制和事件处理都必须在EDT中进行。如果你尝试在非EDT线程中修改UI组件,可能会导致不一致的行为或异常。
  • SwingWorker:允许你在一个单独的工作线程中执行后台任务,同时提供了安全地更新GUI的方法。

主要方法

  1. doInBackground():这是你需要覆盖的方法,在这个方法里执行你的后台任务。此方法自动在工作线程中运行,不会阻塞EDT。
  2. process():当需要更新UI时可以调用publish()方法,这将触发process()方法在EDT上执行。process()接收一个参数列表,这些是通过publish()发送的数据片段。
  3. done():当doInBackground()完成时,会自动调用此方法,它在EDT上执行,因此可以安全地在此方法中更新UI以反映后台任务的结果。
  4. get():返回doInBackground()方法的执行结果,如果必要的话,它会等待直到doInBackground()执行完毕。通常在done()方法中使用,以获取后台任务的结果。

使用步骤

  1. 创建一个继承自SwingWorker的类。
  2. 覆盖doInBackground()方法,用于执行后台操作。
  3. 如果需要在后台任务执行期间更新UI,可以在适当的时候调用publish()方法,并实现process()方法来处理更新逻辑。
  4. 在后台任务完成后,可选择覆盖done()方法来进行最后的UI更新。

示例代码

以下是一个简单的例子,展示了如何使用SwingWorker来执行一个耗时任务并更新进度条:

import javax.swing.*;
import java.util.List;

public class MySwingWorkerExample extends SwingWorker<Void, Integer> {

    private JProgressBar progressBar;

    public MySwingWorkerExample(JProgressBar progressBar) {
        this.progressBar = progressBar;
    }

    @Override
    protected Void doInBackground() throws Exception {
        int progress = 0;
        while (progress < 100) {
            Thread.sleep(100); // 模拟耗时操作
            progress += 5;
            setProgress(progress);
            publish(progress);
        }
        return null;
    }

    @Override
    protected void process(List<Integer> chunks) {
        for (int progress : chunks) {
            progressBar.setValue(progress);
        }
    }

    @Override
    protected void done() {
        JOptionPane.showMessageDialog(null, "任务完成!");
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("SwingWorker 示例");
        JProgressBar progressBar = new JProgressBar(0, 100);
        frame.add(progressBar);
        frame.setSize(300, 100);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);

        MySwingWorkerExample worker = new MySwingWorkerExample(progressBar);
        worker.execute();
    }
}

在这个例子中,我们创建了一个继承自SwingWorker的类,并重写了doInBackground()process()done()方法。doInBackground()模拟了一个耗时过程,每100毫秒更新一次进度,然后通过publish()process()方法更新进度条的值。当任务完成后,done()方法会显示一个消息框通知用户任务已完成。


网站公告

今日签到

点亮在社区的每一天
去签到