为什么 InvokeLater 导致我的 JFrame 无法正确显示?
好吧,我已经在网上进行了搜索,但我还没有找到解决我的问题的方法,也许我错过了一些简单的东西,因此我在这里......
我有一个相当大的项目,处理维修业务的工单。所有这些都与数据库相连,有许多页代码和类。但我只是在前端添加了一小段代码,主要是检查笔记区域中的新消息。
不管怎样,我显示了一个带有两个 JLabel 的简单 JFrame,同时有一个单独的线程查询数据库。这一切都发生在程序开始时。问题是我的小“请稍等”JFrame 出现了它的框架,但在等待期间没有内容,没有背景,也没有 JLabel(这是其余的程序加载,而不是数据库线程),它显示后记,但到那时它就失去了意义。
我编写了以下示例程序。它显示一个简单的JFrame(CheckingMessagesGUI:一个带有两个JLabel的JFrame,仅此而已)休眠5秒,然后显示示例(主程序) 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() 会阻止一切,但我的 CheckingMessagesGUI 不应该在我调用 sleep 之前就已经完全绘制了吗?这就是问题所在。
Ok I've read an searched all over the web, and I've not found the solution to my problem yet, perhaps I'm missing something simple, hence here I am...
I've got a rather large project, that handles work orders for a repair business. It's all database connected, many many pages of code, and classes. But i just added a short bit of code to the front end that essentially checks for new messages in our notes area.
Anyway, I display a simple JFrame with two JLabels while a separate thread queries the database. This all happens at the start of the program. The problem is my little "please wait" JFrame comes up with its frame but no guts, no background, and no JLabels, during the wait (which is the rest of the program loading, not the database thread), afterwords it displays, but by then its missing its point.
I wrote the following example program. It displays a simple JFrame (CheckingMessagesGUI: a JFrame with two JLabels, nothing more) sleeps for 5 sec then displays the Example (main program) JFrame, then instantly closes (System.exit(0)
) in this example, of course my real program goes on to do a lot more. What I found is that invokeLater
seems to be causing the problem. Once the sleep timer runs out the window will display, but the code to display it was given before the Thread.sleep
command, and should have been done in that order correct?
My question is why does invokeLater
cause my JFrame not to display correctly?
Its my understanding that the purpose of invokeLater
is so that the items run on the correct AWT event thread, which would make me think that this window would get painted correctly. Anyway I'm sure I'm missing something obvious. I commented out the invokeLater
part in the code below, and it runs correctly, if you put it back it doesn't...
Many thanks in advance.
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);
/* }
});
*/
}
}
UPDATE:
To clarify, I realize Thread.sleep()
will block everything, but shouldn't my CheckingMessagesGUI already have been fully drawn before I call sleep? That is the issue.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
invokeLater 在事件调度线程中运行 Runnable,该线程也用于更新 GUI。
您的睡眠阻塞了该线程,因此 GUI 也无法获得服务,在您从 invokeLater 代码返回之前无法进行任何更新。
这就是为什么您不应该在此线程中进行任何长时间(耗时)计算的原因。它们应该在不同的(新的)线程中完成。
事件调度队列 状态
您的代码可以更改为(未测试):
编辑:
让我们“深入一点”(这是我对 Swing/AWT 工作的看法)。
我认为“请稍候”(请参阅注释)应该显示在 CheckingMessagesGUI 类中,但事实并非如此。
这与 GUI 的工作方式有关。如果您调用相应的 (Swing) 方法(draw、setText、setLocation...),它不会直接更改显示屏上的任何内容;它只是将事件放入事件队列中。事件调度线程是(应该是)读取此队列并处理事件的唯一线程。只要它被阻止(在本例中被睡眠阻止),就不会显示 GUI 的任何更改。 GUI 被冻结。
编辑2:
invokeLater
Runnable 被附加到队列的末尾,在处理完所有挂起的事件后由 EDT 执行,invokeLater 调用后的下一个命令将被执行.invokeAndWait
与上面相同,但实际的 Thread 会阻塞,直到 EDT 执行 Runnable(在待处理事件之后),也就是说,invokeAndWait 后面的命令只有在执行提交的 Runnable 后才会启动。invokeLater runs the Runnable in the Event Dispatch Thread which also is used for updating the GUI.
Your sleep is blocking this Thread so the GUI also does not get serviced, no updates can be done till you return from the invokeLater code.
That's why you should not do any long (time consuming) computations in this Thread. They should be done in an different (new) Thread.
The Event Dispatch Queue states
Your code could be changed to (not tested):
EDIT:
let's go a bit "deeper" (and it's my view of the working of Swing/AWT).
I suppose the "please wait" (see comments) should be displayed in the CheckingMessagesGUI class, but isn't.
That's related to the way the GUI works. It does not directly change anything on the display if you call the corresponding (Swing) methods (draw, setText, setLocation, ...); it just queues an Event in the Event Queue. The Event Dispatch Thread is (should be) the only Thread that reads this queue and process the events. As long as it is being blocked - by the sleep in this case - no changes to the GUI will be displayed. The GUI is frozen.
EDIT2:
invokeLater
the Runnable is appended to the end of the queue to be latter executed by the EDT after all pending events have been processed, the next command after the invokeLater call will be executed.invokeAndWait
same as above but the actual Thread blocks until the Runnable was executed (after pending events) by the EDT, that is, the command following the invokeAndWait will only get started after the submitted Runnable was executed.这是正确的。
然而,这也意味着 Thread.sleep() 正在 EDT 上执行,这意味着 GUI 无法重新绘制自身,因为您刚刚告诉 EDT 休眠。您需要为长时间运行的任务使用单独的线程。
有关详细信息,请阅读 Swing 教程中有关并发的部分关于美国东部时间。
这是我对这个过程的简单理解。创建并显示框架是因为它是操作系统本机组件。然而,contentPane 和子组件是轻量级组件,这意味着 Swing 重绘管理器会安排它们何时应该重绘。所以在安排重画之前,EDT会进入睡眠状态,直到睡眠完成后才能进行重画。
您可以在 在 AWT 和 Swing 中绘画。
That is correct.
However, that also means that Thread.sleep() is executing on the EDT, which means the GUI can't repaint itself, since you just told the EDT to sleep. You need to use a separate Thread for your long running task.
Read the section from the Swing tutorial on Concurrency for more information about the EDT.
Here is my simplified understanding of the process. The frame is created and displayed because it is an OS native component. However, the contentPane and child component are lightweight components which means the Swing Repaint Manager schedules when they should be repainted. So before the repainting is scheduled, the EDT is put to sleep, and the repainting can't be done until the sleeping is finished.
You can find more infomation about the Repaint Manager in the article on Paintng in AWT and Swing.
对于像我这样在 Swing 教程中找不到所需内容的新手来说,这是一个通用解决方案。
Here is a general solution for newbies like myself who have problems finding what they need in the Swing tutorial.
我的答案是,当 GUI 构建时,它不会自动绘制,而是对绘制的调用被放置在 EDT 队列中。如果在同一方法中构造一个 GUI 对象,并 setVisible(true) ,那么在接下来的几行中执行一些密集操作,它会阻止将来对 Paint 的调用发生,因为直到该方法(与密集的东西)完成。另外,正如前面提到的,框架或边框位于等式的平台侧(因此被绘制),其余部分(Jlabel、容器、背景等)位于 java 侧,并且在实际运行绘制之前不会发生(即 EDT 队列到达它)。我的示例代码在没有 InvokeLater 调用的情况下工作,因为它在 init 线程中运行了密集的内容,并允许 EDT 线程仍然进行绘制。
My answer was that when the GUI is constructed its not automatically painted at that time, instead a call to paint is placed in the EDT queue. If in the same method you construct a GUI object, and setVisible(true) then in the next few lines do something intensive, it blocks the future call to paint from occurring, because it will not get placed into the EDT queue until that method (with the intensive stuff) finishes. Also as was mentioned the frame or border is on the platform side of the equation (hence gets drawn), and the rest (Jlabel, container, background, etc) is on the java side and doesn't happen until paint is actually run (i.e. the EDT queue gets to it). My example code worked without the InvokeLater call, because that ran the intensive stuff in the init thread, and allowed the EDT thread to still paint.
不可见的组件不会被绘制。
Invisible components are not painted.