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

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

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

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

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

对话框在其ctor中执行的最后一项操作是setVisible(true)
诸如revalidate()repaint() ,……之类的东西也无济于事。

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

JFrame替换JDialog也无济于事。

那么,这个概念一般是错的吗? 或者,在确保JDialog (或JFrame )完全布局 ,我可以管理它以完成某项工作吗?

我想要实现的简化算法:

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

尝试这个:

 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()方法从’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相关问题。 无法一次解释所有这些……但这里还有一个片段,可以满足您的需求:

 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上运行SwingUtilities.
    • invokeAndWait() – 顾名思义,调用是同步的,
    • invokeLater() – 在某个时候调用代码,但立即返回
  • 如果你在EDT并想要在另一个线程上调用代码,那么你可以:
    • 创建一个新的Thread (将Runnable传递给新的Thread或覆盖它的start()方法)并启动它,
    • 创建一个新的SwingWorker线程,它有一些额外的东西。
    • 可能使用任何其他线程机制(例如Executor线程)。

典型的GUI场景包括:

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

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

如前面的答案中所述,你必须在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 worker = new SwingWorker() { @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并在你需要的任何地方使用扩展类。

首先,对于推测工作的事情感到抱歉,在发布代码之前,我并没有真正理解你要做的事情。

尽管如此,我并不熟悉这种创建对话框和窗口的方法,所以我建议你为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"); 

它可能看起来像狗屎,但它可能工作(我没有测试)。

希望这可以帮助。

您应该使用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(); } } } 

您需要在后台线程或主应用程序线程中运行该作业。 应使用SwingUtilities.invokeLater();在Event Dispatch Thread(EDT)上创建JDialog和JFrame SwingUtilities.invokeLater(); 。 然后等到作业完成并关闭(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); } }); }