(Java) repaint() 随机执行
(Java) repaint() executes at random times
在 Java 中使用 GUI 时,我注意到 Component
class 的 repaint()
方法表现出异常行为。下面的代码在打印 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),系统通过将事件注入队列来工作;线程不断地从队列中取出最顶层的事件并处理它。
用户按下按钮?调用该按钮上定义的任何 actionlistener 的工作被放入队列中。这意味着事件侦听器中的任何代码在 EDT 中都是 运行。
那个报复电话?它根本不直接调用 paintComponent 。它在队列中注入一个事件来进行重绘,然后在 EDT 中完成重绘,这将调用 paintComponent。
(swing 和其他框架的规则是您不能从除 EDT 之外的任何线程编辑任何小部件,请参阅 swing 的文档)。
换句话说,您的主要方法是一个线程,您的 EDT 是另一个线程,并且 repaint()
调用正在从一个线程到另一个线程进行通信:请 运行 此事件,然后将继续.
EDT 的另一条规则是它绝不能 'block'(在磁盘、网络或其他线程上等待,或出于任何原因暂停执行)。如果您确实在 EDT 上进行了阻止,该应用程序看起来完全没有响应,很快 OS 将弹出一个通知,表明该应用程序似乎已崩溃。这是因为各种 GUI 交互,例如如果鼠标光标漂浮在文本框上则将鼠标光标更新为不同的形状,也由 EDT 处理,因此如果 EDT 被冻结,none有效。
因此,我们得出结论:无法完成您想要的 - 获得可靠 'this occurs before that' 的唯一方法,其中 'this' 是一个线程中的工作,'that'是另一份工作,带有锁定,这会导致冻结,这在 EDT* 中是不可接受的事情。
但是,没有人发布 'invokes the paint method before continuing on after repaint is invoked' 的应用程序。显然你有一些你想做的工作,你认为让 repaint() 和 paint() 同步是到达那里的方式,现在你正在问关于第二件事的问题。但这是一个错误的策略 - 有一个答案,但这个 'sync up paint and repaint calls' 是死胡同。
其次,只要需要绘制组件,就会调用 paintComponent 方法。做一些简单的事情,比如将另一个应用程序的 window 拖过您应用程序的 window 将导致调用 paintComponent 来完成工作的事件。您无法控制此类行为。因此,即使您挥动魔杖并完成不可能的事情(同步 repaint() 和 paintComponent() 调用),您仍然会发现一堆根本不是由 repaint() 引起的 paintComponent 调用。
*) 你可以让调用 repaint() 的主线程在 EDT 上等待至少完成一次绘制,我猜,有一个闩锁,但这听起来不是一个好计划,你有无法保证重绘实际发生的时间,并且您不知道对 paintComponent 的某些调用实际上是因为您调用了重绘,还是因为 OS 决定是时候询问您的应用它想要显示哪些像素了。
在 Java 中使用 GUI 时,我注意到 Component
class 的 repaint()
方法表现出异常行为。下面的代码在打印 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),系统通过将事件注入队列来工作;线程不断地从队列中取出最顶层的事件并处理它。
用户按下按钮?调用该按钮上定义的任何 actionlistener 的工作被放入队列中。这意味着事件侦听器中的任何代码在 EDT 中都是 运行。
那个报复电话?它根本不直接调用 paintComponent 。它在队列中注入一个事件来进行重绘,然后在 EDT 中完成重绘,这将调用 paintComponent。
(swing 和其他框架的规则是您不能从除 EDT 之外的任何线程编辑任何小部件,请参阅 swing 的文档)。
换句话说,您的主要方法是一个线程,您的 EDT 是另一个线程,并且 repaint()
调用正在从一个线程到另一个线程进行通信:请 运行 此事件,然后将继续.
EDT 的另一条规则是它绝不能 'block'(在磁盘、网络或其他线程上等待,或出于任何原因暂停执行)。如果您确实在 EDT 上进行了阻止,该应用程序看起来完全没有响应,很快 OS 将弹出一个通知,表明该应用程序似乎已崩溃。这是因为各种 GUI 交互,例如如果鼠标光标漂浮在文本框上则将鼠标光标更新为不同的形状,也由 EDT 处理,因此如果 EDT 被冻结,none有效。
因此,我们得出结论:无法完成您想要的 - 获得可靠 'this occurs before that' 的唯一方法,其中 'this' 是一个线程中的工作,'that'是另一份工作,带有锁定,这会导致冻结,这在 EDT* 中是不可接受的事情。
但是,没有人发布 'invokes the paint method before continuing on after repaint is invoked' 的应用程序。显然你有一些你想做的工作,你认为让 repaint() 和 paint() 同步是到达那里的方式,现在你正在问关于第二件事的问题。但这是一个错误的策略 - 有一个答案,但这个 'sync up paint and repaint calls' 是死胡同。
其次,只要需要绘制组件,就会调用 paintComponent 方法。做一些简单的事情,比如将另一个应用程序的 window 拖过您应用程序的 window 将导致调用 paintComponent 来完成工作的事件。您无法控制此类行为。因此,即使您挥动魔杖并完成不可能的事情(同步 repaint() 和 paintComponent() 调用),您仍然会发现一堆根本不是由 repaint() 引起的 paintComponent 调用。
*) 你可以让调用 repaint() 的主线程在 EDT 上等待至少完成一次绘制,我猜,有一个闩锁,但这听起来不是一个好计划,你有无法保证重绘实际发生的时间,并且您不知道对 paintComponent 的某些调用实际上是因为您调用了重绘,还是因为 OS 决定是时候询问您的应用它想要显示哪些像素了。