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

时间:2010-12-27 09:10:34

标签: java multithreading events swing awt

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

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

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

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

对话框的最后一件事是setVisible(true) 诸如revalidate()repaint()之类的内容也无济于事。

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

JDialog替换JFrame也无济于事。

那么,概念一般是错误的吗?或者我可以管理它以完成某项工作吗 确保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();
            }
        });
    }
}

5 个答案:

答案 0 :(得分:4)

试试这个:

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

典型的GUI场景包括:

  1. 创建GUI组件,
  2. 联系财产变更听众,
  3. 执行与用户操作相关的代码(即运行属性更改侦听器),
  4. 运行可能耗时的任务,
  5. 更新GUI状态,
  6. 1.,2.,3。和4.在 EDT 上运行。 4.不应该。有很多方法可以编写正确的线程代码。最麻烦的是使用早期版本的Java附带的Thread类。如果天真地做到这一点,就会浪费资源(一次运行的线程太多)。更新GUI也很麻烦。使用SwingWorker可以缓解这个问题。它保证在启动,运​​行和更新GUI时表现正常(每个都有一个专用的方法,你可以覆盖并确保它在适当的线程上运行)。

答案 1 :(得分:3)

如前面的答案中所述,你必须在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并在所需的任何地方使用扩展类。

答案 2 :(得分:0)

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

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

它可能看起来像狗屎,但它可能有效(我还没有测试过)。

希望这有帮助。

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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