【问题标题】:Swing: How do I run a job from AWT thread, but after a window was layed out?Swing:如何从 AWT 线程运行作业,但在布局窗口之后?
【发布时间】:2010-12-27 09:10:33
【问题描述】:

我的完整 GUI 在 AWT 线程中运行,因为我使用 SwingUtilities.invokeAndWait(...) 启动主窗口。

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

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

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

对话框在其 ctor 中做的最后一件事是 setVisible(true)
诸如revalidate()repaint()、...之类的东西也无济于事。

即使我为受监控的作业启动一个线程,并使用someThread.join() 等待它也无济于事,因为我猜,当前线程(即 AWT 线程)被join 阻塞了。

JDialog 替换为JFrame 也无济于事。

那么,这个概念总体上是错误的吗?或者我可以在确保JDialog(或JFrame)完全布局之后完成某些工作吗?

我想要实现的简化算法:

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

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

EDIT 现在解决了来自答案的问题: 此用例确实显示标签,但无法关闭 在“模拟过程”之后,因为对话的形式。

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();
            }
        });
    }
}

【问题讨论】:

  • 请添加您启动挥杆组件的代码段。
  • 我真的不明白问题出在哪里。如果您的应用程序在线程内运行,那么您只需从中显示 JDialog,然后使用 JDialog 作为作业的参数启动您的作业(也从线程)。像这样的东西: JDialog myDialog = ....;启动我的工作(我的对话);该过程完成后,从作业中关闭 JDialog。对于您的标签问题,正如 Amjad 所说,您应该展示如何实例化您的 JDialog 及其组件。

标签: java multithreading events swing awt


【解决方案1】:

试试这个:

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();


        }
    });
}
}

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

您的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");
            }
        });

编辑:这个问题涉及到很多与 Swing 相关的问题。无法一下子全部解释清楚……但这里还有一个 sn-p,它可以满足您的需求:

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);

        }
    });
}

总结一下:

  • 所有 Swing 相关代码都必须在 EDT 上运行
  • 所有长时间运行的代码都不得在 EDT 上运行
  • 可以使用SwingUtilities.在 EDT 上运行代码...
    • invokeAndWait() - 顾名思义,调用是同步的,
    • invokeLater() - 调用代码“有时”,但立即返回
  • 如果您在 EDT 上并希望在另一个线程上调用代码,则可以:
    • 创建一个新的Thread(将一个Runnable传递给新线程或覆盖它的start()方法)并启动它,
    • 创建一个新的SwingWorker 线程,其中包含一些附加功能。
    • 可能使用任何其他线程机制(例如 Executor 线程)。

典型的 GUI 场景包括:

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

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

【讨论】:

  • 谢谢,但问题是现在:我的对话框是模态的,所以它在进程结束后永远不会关闭。请在我的问题中查看更新的测试用例。
  • 是的,这是一个相当复杂的问题。我一次只发布一部分答案。
  • 谢谢你这么详细的解释!
【解决方案2】:

如前面的答案所述,您必须在与 EDT 不同的线程中运行冗长的作业。

恕我直言,最简单的方法是使用 SwingWorker 并添加一个侦听器以在完成后处理对话框。这是一个示例:

SwingWorkerCompletionWaiter.java

public class SwingWorkerCompletionWaiter implements PropertyChangeListener {
  private JDialog dialog;

  public SwingWorkerCompletionWaiter(JDialog dialog) {
      this.dialog = dialog;
  }

  @Override
  public void propertyChange(PropertyChangeEvent event) {
      if ("state".equals(event.getPropertyName())
              && SwingWorker.StateValue.DONE == event.getNewValue()) {
          dialog.setVisible(false);
          dialog.dispose();
      }
  }
}

下面的代码会运行一些冗长的任务:

SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

  @Override
  protected Void doInBackground() throws Exception {
    // do something long
  }
};
JDialog dialog = new JDialog();
    // initialize the dialog here
worker.addPropertyChangeListener(new SwingWorkerCompletionWaiter(dialog));
worker.execute();
dialog.setVisible(true);

如果您想创建某种“请稍候”对话框,那么扩展 JDialog 并在需要的任何地方使用扩展类可能会更容易。

【讨论】:

  • 谢谢,倾听是个好主意!
【解决方案3】:

首先,很抱歉猜测工作的事情,在您发布代码之前,我并没有真正理解您要做什么。

尽管如此,我对这种创建对话框和窗口的方式以及一般情况并不十分熟悉,因此我建议您为 JDialog 创建一个单独的类。只是一个简单的类,像这样:

public class MyDialog extends JDialog
{
    public MyDialog(JFrame owner, String title)
    {
        super(owner, title);

        d.setSize(300, 200);           
        d.setLocationRelativeTo(null);           
        d.setVisible(true);
        d.setDefaultCloseOperation(DISPOSE_ON_CLOSE );

        Container c = getContentPane();
        c.setLayout(new FlowLayout());

        c.add(new JLabel("Please wait..."));  
    }
}

并以这种方式启动对话框:

new MyDialog(this, "Your title");

它可能看起来很糟糕,但它可能有效(我没有测试过)。

希望这会有所帮助。

【讨论】:

  • 好吧,我的问题显然与 AWT 线程的线程问题有关,因为无需等待该后台作业,一切都会正确显示,正如我在问题中所述。
【解决方案4】:

您应该使用invokeLater 而不是InvokeAndWait。

public class DialogTest {
    public static void main(String[] args) throws Exception {
        final JLabel comp =  new JLabel("the job has started");;
        final JFrame myFrame = new JFrame("Main frame");
        final JDialog d = new JDialog(myFrame, "I'm waiting");
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {

                myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                myFrame.setSize(750, 500);
                myFrame.setLocationRelativeTo(null);
                myFrame.setVisible(true);

                 d.setModalityType(JDialog.ModalityType.APPLICATION_MODAL); 
                d.add(comp);
                d.setSize(300, 200);
                d.setLocationRelativeTo(null);
                d.setVisible(true);


            }
        });
        try {
            Thread.sleep(3000);
            // d.setVisible(false);     
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    comp.setText("the job has finished");
                     d.setVisible(false);         
                     d.dispose();         
                     myFrame.setVisible(false);         
                     myFrame.dispose(); 
                }
            });

        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

【讨论】:

  • 谢谢!可悲的是,在我正在与之抗争的真实案例中,由于某些原因,我无法做到这样的解决方案。
【解决方案5】:

您需要在后台线程或主应用程序线程中运行作业。 JDialog 和 JFrame 应使用SwingUtilities.invokeLater(); 在事件调度线程 (EDT) 上创建。然后等到作业完成并关闭 (EDT) 中的对话框。由于布局和作业在两个单独的线程上,所以应该没有问题。事实上,您的示例的以下修改有效:

public static void main(String[] args) throws Exception {
    final JFrame myFrame = new JFrame("Main frame");
    final JDialog d = new JDialog(myFrame, "I'm waiting");

    final Thread backgroundJob = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000); // simulate process
                }
                catch (InterruptedException ex) {
                    Logger.getLogger(NewClass.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
    });
    backgroundJob.start();

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

            d.add(new JLabel("Please wait..."));
            d.setSize(300, 200);
            d.setLocationRelativeTo(null);
            d.setVisible(true);
        }
    });

    backgroundJob.join();

    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            d.setVisible(false);
        }
    });
}

【讨论】:

  • 谢谢!可悲的是,我的后台工作是做与 GUI 相关的工作,所以它不能在 AWT 线程之外。
猜你喜欢
  • 2014-09-27
  • 2017-05-30
  • 1970-01-01
  • 2019-04-26
  • 1970-01-01
  • 1970-01-01
  • 2015-01-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多