为什么InvokeLater导致我的JFrame无法正确显示?

好的,我已经阅读了网上的搜索结果,但我还没有找到问题的解决方案,也许我错过了一些简单的东西,因此我在这里……

我有一个相当大的项目,处理维修业务的工单。 这是所有数据库连接,许多页面的代码和类。 但我只是在前端添加了一小段代码,实质上是在我们的笔记区域中检查新消息。

无论如何,我显示一个带有两个JLabel的简单JFrame ,而一个单独的线程查询数据库。 这一切都发生在程序的开始。 问题是我的小“请等待” JFrame提出了它的框架但没有胆量,没有背景,没有JLabel ,在等待期间(这是程序加载的其余部分,而不是数据库线程),显示后,但到那时它错过了它的观点。

我写了以下示例程序。 它显示一个简单的JFrame (CheckingMessagesGUI:一个带有两个JLabelJFrame ,仅此而已)hibernate5秒然后显示Example(主程序) JFrame ,然后在这个例子中立即关闭( System.exit(0) ),当然我的真正的计划继续做更多。 我发现invokeLater似乎导致了这个问题。 一旦睡眠定时器用完,窗口就会显示,但显示它的代码是在Thread.sleep命令之前给出的,应该按照那个顺序完成了吗?

我的问题是为什么invokeLater会导致我的JFrame无法正确显示?

我的理解是invokeLater的目的是让项目在正确的AWT事件线程上运行,这会让我觉得这个窗口会被正确绘制。 无论如何,我确定我错过了一些明显的东西。 我在下面的代码中注释掉了invokeLater部分,它运行正常,如果你把它放回去它不…

提前谢谢了。

 package javaapplication6; public class Example extends javax.swing.JFrame { public Example() { System.out.println("Example started"); setBounds(100,100,200,200); System.out.println("cmGUI instantiated"); CheckingMessagesGUI cmGUI = new CheckingMessagesGUI(); System.out.println("Set cmGUI visible"); cmGUI.setVisible(true); cmGUI.validate(); try { System.out.println("timer started"); Thread.sleep(5000); System.out.println("timer done"); } catch(InterruptedException e){ } System.exit(0); } public static void main(String[] args) { /*java.awt.EventQueue.invokeLater(new Runnable() { @Override public void run() { */ System.out.println("Started"); System.out.println("example Instantiated"); Example example = new Example(); System.out.println("example visible"); example.setVisible(true); /* } }); */ } } 

更新:为了澄清,我意识到Thread.sleep()会阻塞所有内容,但是在调用sleep之前我的CheckingMessagesGUI是否已经完全绘制了? 这就是问题所在。

invokeLater在Event Dispatch Thread中运行Runnable,它也用于更新GUI。
你的睡眠阻止了这个线程,所以GUI也没有得到服务 ,在你从invokeLater代码返回之前不能做任何更新。
这就是为什么你不应该在这个线程中做任何长(耗时)的计算。 它们应该在不同的(新)线程中完成。

事件调度队列声明

事件派发线程上的任务必须快速完成; 如果他们不这样做,未处理的事件将备份,用户界面将无法响应。

您的代码可以更改为(未测试):

 public Example(){ System.out.println("Example started"); setBounds(100,100,200,200); System.out.println("cmGUI instantiated"); CheckingMessagesGUI cmGUI = new CheckingMessagesGUI(); System.out.println("Set cmGUI visible"); cmGUI.setVisible(true); cmGUI.validate(); Thread thread = new Thread(new Runnable() { try { System.out.println("timer started"); Thread.sleep(5000); System.out.println("timer done"); } catch(InterruptedException e) { } System.exit(0); }); thread.start(); } 

编辑:让我们更深入一点(这是我对Swing / AWT工作的看法)。
我想应该在CheckingMessagesGUI类中显示“please wait”(参见注释),但不是。
这与GUI的工作方式有关。 如果调用相应的(Swing)方法(draw,setText,setLocation,…),它不会直接更改显示内容。 它只是在事件队列中排队一个事件。 Event Dispatch Thread(应该是)唯一读取此队列并处理事件的Thread。 只要它被阻止 – 在这种情况下通过睡眠 – 将不会显示对GUI的任何更改。 GUI被冻结。

EDIT2:
invokeLater将Runnable 附加到队列的末尾,以便在处理完所有挂起事件后由EDT执行后,将执行invokeLater调用之后的下一个命令。
invokeAndWait与上面相同,但实际的Thread阻塞直到EDT执行Runnable(在挂起事件之后),也就是说,invokeAndWait之后的命令只会在执行提交的Runnable之后启动。

我的理解是,InvokeLater的目的是使项目在正确的AWT事件线程上运行

那是对的。

但是,这也意味着Thread.sleep()正在EDT上执行,这意味着GUI无法重绘自己,因为你只是告诉EDT要睡觉。 您需要为长时间运行的任务使用单独的线程。

有关EDT的更多信息,请阅读有关并发的Swing教程中的部分。

但我的观点是,在我打电话给睡眠之前,我的“请等待”(CheckingMessagesGUI)应该已经完全绘制完毕。 这不应该是真的吗?

以下是我对该过程的简化理解。 创建并显示框架,因为它是OS本机组件。 但是,contentPane和子组件是轻量级组件,这意味着Swing Repaint Manager会在重新绘制它们时进行计划。 因此,在重新计划之前,EDT将进入睡眠状态,并且在睡眠结束之前无法进行重新绘制。

您可以在AWT和Swing中关于Paintng的文章中找到有关Repaint Manager的更多信息。

我的回答是,当构建GUI时,它不会在那时自动绘制,而是在EDT队列中放置对绘制的调用。 如果你在同一个方法中构造一个GUI对象,并且setVisible(true)那么在接下来的几行中做一些密集的事情,它会阻止将来调用paint,因为在该方法之前它不会被放入EDT队列(用密集的东西)完成。 同样如前所述,框架或边框位于等式的平台一侧(因此被绘制),其余的(Jlabel,容器,背景等)在java端,并且直到paint实际运行才会发生(即EDT队列到达它)。 我的示例代码在没有InvokeLater调用的情况下工作,因为它在init线程中运行了密集的东西,并允许EDT线程仍然绘制。

不可见的组件没有涂漆。

对于像我这样的新手来说,这是一个通用的解决方案,他们在Swing教程中找到了他们需要的东西。

 public void method(){ final PleaseWaitWindow window = new PleaseWaitWindow(); Thread thread = new Thread(new Runnable() { @Override public void run() { //stuff that you want to do that is preventing window to display window.dispose(); } } thread.start(); }