繁体   English   中英

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

[英]Swing: How do I run a job from AWT thread, but after a window was layed out?

我完整的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。 因此,永远不要在EDT上执行您的代码Thread.wait(...) 这是我的修改。 我将调用包装在另一个线程中。 因此,这是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传递给新线程或覆盖它的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<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并在需要的地方使用扩展的类可能会更容易。

首先,很抱歉对工作有所猜测,在发布代码之前,我并没有真正理解您要执行的操作。

尽管如此,我对创建对话框,窗口和常规对话框的方式并不陌生,因此建议您为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");

它可能看起来像狗屎,但可能有效(我尚未测试)。

希望这可以帮助。

您需要在后台线程或主应用程序线程中运行作业。 应该使用SwingUtilities.invokeLater();在事件调度线程(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);
        }
    });
}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM