Swing:如何在布局窗口之后从 AWT 线程运行作业?

jav*_*top 2 java events swing multithreading awt

我的完整 GUI 在 AWT 线程内运行,因为我使用SwingUtilities.invokeAndWait(...).

现在我有一个 JDialog,它只显示一个JLabel,表示某个作业正在进行中,并在作业完成后关闭该对话框。

问题是:标签没有显示。JDialog这项工作似乎在完全布置好之前就开始了。

当我只是打开对话框而不等待作业并关闭时,就会显示标签。

对话框在其构造函数中执行的最后一件事是setVisible(true)
诸如revalidate()repaint()、 ... 之类的东西也没有帮助。

即使我为受监视的作业启动一个线程,并使用它等待someThread.join()它也没有帮助,因为join我猜当前线程(即 AWT 线程)被 阻塞了。

替换JDialogJFrame也没有帮助。

那么,这个概念总体上是错误的吗?或者在确保 a (或) 完全布局我可以管理它来完成某些工作吗?JDialogJFrame

我想要实现的简化算法:

  • 创建一个子类JDialog
  • 确保其及其内容已完全布局
  • 启动一个进程并等待它完成(线程与否,并不重要)
  • 关闭对话框

我设法编写了一个可重现的测试用例:

编辑答案中的问题现已解决:此用例确实显示标签,但由于对话框的模态,它在“模拟过程”后无法关闭。

import java.awt.*;
import javax.swing.*;

public class _DialogTest2 {
    public static void main(String[] args) throws Exception {
        SwingUtilities.invokeAndWait(new Runnable() {
            final JLabel jLabel = new JLabel("Please wait...");
            @Override
            public void run() {
                JFrame myFrame = new JFrame("Main frame");
                myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                myFrame.setSize(750, 500);
                myFrame.setLocationRelativeTo(null);
                myFrame.setVisible(true);

                JDialog d = new JDialog(myFrame, "I'm waiting");
                d.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);

                d.add(jLabel);
                d.setSize(300, 200);
                d.setLocationRelativeTo(null);
                d.setVisible(true);

                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(3000); // simulate process
                            jLabel.setText("Done");
                        } catch (InterruptedException ex) {
                        }
                    }
                });

                d.setVisible(false);
                d.dispose();

                myFrame.setVisible(false);
                myFrame.dispose();
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

Rek*_*kin 5

尝试这个:

package javaapplication3;

import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class Main {

public static void main(String[] args)
        throws Exception {
    SwingUtilities.invokeAndWait(new Runnable() {

        final JLabel jLabel = new JLabel("Please wait...");

        @Override
        public void run() {
            JFrame myFrame = new JFrame("Main frame");
            myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            myFrame.setSize(750, 500);
            myFrame.setLocationRelativeTo(null);
            myFrame.setVisible(true);

            JDialog d = new JDialog(myFrame, "I'm waiting");

            d.add(jLabel);
            d.setSize(300, 200);
            d.setLocationRelativeTo(null);
            d.setVisible(true);

            new Thread(new Runnable() {

                @Override
                public void run() {

                public void run() {
                    try {
                        Thread.sleep(3000); // simulate process
                        jLabel.setText("Done");   // HERE: should be done on EDT!
                    } catch (InterruptedException ex) {
                    }
                }
            }).start();


        }
    });
}
}
Run Code Online (Sandbox Code Playgroud)

这个可行,但不正确。我会解释发生了什么事。

您的main()方法从“主”线程开始。所有与 Swing 相关的代码都应该在 EDT 线程上完成。这就是您使用(正确)的原因SwingUtilities.invokeAndWait(...)。到目前为止,一切都很好。

但 EDT 上不应该有长时间运行的任务。由于 Swing 是单线程的,任何长时间运行的进程都会阻塞 EDT。所以你的代码Thread.wait(...)永远不应该在 EDT 上执行。这是我的修改。我将调用包装在另一个线程中。所以这是 Swing 惯用的长时间运行任务处理。为了简洁起见,我使用 Thread 类,但我真的建议使用SwingWorker线程。

非常重要的是:我在前面的示例中犯了一个错误。看到带有“HERE”注释的行了吗?这是另一个 Swing 单线程规则违规。线程内的代码在 EDT之外运行,因此它永远不应该接触 Swing。所以这段代码不符合 Swing 单线程规则。冻结 GUI 并不安全。

如何纠正这个问题?简单的。您应该将调用包装在另一个线程中并将其放入 EDT 队列中。所以正确的代码应该是这样的:

    SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                jLabel.setText("Done");
            }
        });
Run Code Online (Sandbox Code Playgroud)

编辑:这个问题涉及很多与 Swing 相关的问题。无法一次解释所有这些......但这里还有一个片段,它可以满足您的需求:

public static void main(String[] args)
        throws Exception {
    SwingUtilities.invokeAndWait(new Runnable() {

        final JFrame myFrame = new JFrame("Main frame");
        final JLabel jLabel = new JLabel("Please wait...");
        final JDialog d = new JDialog(myFrame, "I'm waiting");

        @Override
        public void run() {
            myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            myFrame.setSize(750, 500);
            myFrame.setLocationRelativeTo(null);
            myFrame.setVisible(true);

            d.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);

            d.add(jLabel);
            d.setSize(300, 200);
            d.setLocationRelativeTo(null);
            new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        Thread.sleep(3000); // simulate process
                        System.out.println("After");
                        SwingUtilities.invokeLater(new Runnable() {

                            public void run() {


                                d.setVisible(false);
                                d.dispose();

                                myFrame.setVisible(false);
                                myFrame.dispose();
                            }
                        });
                    } catch (InterruptedException ex) {
                    }
                }
            }).start();
            d.setVisible(true);

        }
    });
}
Run Code Online (Sandbox Code Playgroud)

总结:

  • 所有与 Swing 相关的代码必须在 EDT 上运行
  • 所有长时间运行的代码不得在 EDT 上运行
  • 代码可以在 EDT 上运行,使用SwingUtilities....
    • invokeAndWait()- 顾名思义,调用是同步的,
    • invokeLater()-“有时”调用代码,但立即返回
  • 如果您在 EDT 上并且想要在另一个线程上调用代码,那么您可以:
    • 创建一个新的Thread(将 Runnable 传递给新线程或覆盖它的start()方法)并启动它,
    • SwingWorker创建一个有一些额外内容的新线程。
    • 可能使用任何其他线程机制(例如执行器线程)。

典型的 GUI 场景包括:

  1. 创建 GUI 组件,
  2. 连接属性更改侦听器,
  3. 执行与用户操作相关的代码(即运行属性更改侦听器),
  4. 运行可能耗时的任务,
  5. 更新 GUI 状态,

1.、2.、3. 和 4. 在EDT上运行。4.不应该。有很多方法可以编写正确的线程代码。最麻烦的是使用Thread早期版本的 Java 附带的类。如果天真地这样做,可能会浪费资源(同时运行太多线程)。而且更新 GUI 也很麻烦。使用 SwingWorker 可以稍微缓解这个问题。它保证在启动、运行和更新 GUI 时行为正常(每个方法都有一个专用方法,您可以覆盖该方法并确保它在正确的线程上运行)。