(Java)repaint()随机执行。

问题描述 投票:0回答:1

当我在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

以此类推,通常只是打印 12 而很少印刷 painted 在随机位置。我想要的输出是。

1
painted
2

1
painted
2

重复。似乎是这样的 update() 方法在每次迭代开始时被调用,不管我实际写在哪里。在方法执行之前似乎也有一些延迟。我可以做些什么来实现我想要的输出?

java swing user-interface repaint
1个回答
1
投票

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的一些调用实际上是因为你调用了重画,还是因为操作系统决定是时候询问你的应用程序要显示哪些像素。

© www.soinside.com 2019 - 2024. All rights reserved.