当我在Java中使用GUI时,我注意到了 repaint()
方式 Component
类表现出不寻常的行为。下面的代码在打印了 1
而之前 2
,或者说是我的本意。
public class ErrorTest {
public static void main(String[] args) throws InterruptedException {
JFrame F = new JFrame();
F.setSize(100,100);
Panel P = new Panel();
F.setContentPane(P);
F.setVisible(true);
while(true) {
System.out.println("\n1");
P.repaint();
System.out.println("2");
Thread.sleep(100);
}
}
}
class Panel extends JPanel {
@Override
protected void paintComponent(Graphics G) {
System.out.println("painted");
G.drawOval(10, 10, 20, 30);
}
}
如果 Thread.sleep(100);
包括,该代码的输出是。
1
2
painted
1
2
painted
以此类推 如果不包含,则输出为。
1
2
1
2
painted
以此类推,通常只是打印 1
和 2
而很少印刷 painted
在随机位置。我想要的输出是。
1
painted
2
1
painted
2
重复。似乎是这样的 update()
方法在每次迭代开始时被调用,不管我实际写在哪里。在方法执行之前似乎也有一些延迟。我可以做些什么来实现我想要的输出?
TL;DR:你想要的是不可能的。
有一种叫做 "EDT "的东西--事件派遣线程。swing(以及大多数UI库)的工作原理是,有一个单线程(EDT),系统的工作原理是将事件注入队列;线程不断地从队列中取出最上面的事件并处理它。
用户按下一个按钮?调用该按钮上定义的任何动作监听器的工作都会被放入队列中。这意味着事件监听器中的任何代码都会在EDT中运行。
那个repaiont调用?它根本没有直接调用paintComponent。它在队列中注入一个事件来进行重绘,然后在EDT中进行重绘,that将调用paintComponent。
(swing和其他框架的规则是,除了EDT之外,你不能在任何线程中编辑任何widget,参见swing的文档)。
换句话说,你的主方法是一个线程,你的EDT是另一个线程,而那个 repaint()
调用正在从一个线程通信到另一个线程。请运行这个事件,然后将继续。
EDT的另一个规则是,它决不能 "阻塞"(在磁盘、网络或其他线程上等待,或因任何原因暂停执行)。如果你真的在EDT上阻塞了,应用程序看起来完全没有反应,很快操作系统就会弹出一个通知,应用程序似乎已经崩溃了。这是因为各种GUI交互,比如如果浮在文本框上,将鼠标光标更新为不同的形状,也是由EDT来处理的,所以如果EDT被冻结,这些都不行。
因此,我们得出结论。你想要的是不可能实现的 -- 唯一能让 "这个发生在那个之前 "可靠的方法,即 "这个 "是一个线程的工作,而 "那个 "是另一个线程的工作,就是锁定,这会导致冻结,这在EDT中是不可接受的事情*。
但是,没有人会发布一个'调用重绘后再继续画法'的应用。显然,你有一些你想做的工作,你认为让repaint()和paint()同步起来是达到目的的方法,现在你又对这第二件事提出了疑问。但这是一个错误的策略--答案是有的,但这个'把paint和repaint调用同步起来'是一个死胡同。
其次,paintComponent方法是在任何时候都会被调用来为你的组件上色。做一些简单的事情,比如把另一个应用程序的窗口拖过你的应用程序的窗口,会引起事件,这些事件会调用paintComponent来完成这项工作。你无法控制这样的操作。因此,即使你挥动你的魔法棒,做了不可能的事情(同步重绘()和paintComponent()调用),你仍然会发现一堆paintComponent调用根本不是由重绘()引起的。
*) 你可以让你调用repaint()的主线程在EDT上等待至少做了一次画,我想,用一个锁存器,但这听起来并不是一个好的计划,你不能保证什么时候真正发生重画,你也不知道对paintComponent的一些调用实际上是因为你调用了重画,还是因为操作系统决定是时候询问你的应用程序要显示哪些像素。