3

有这篇文章:


有人插队!有时似乎某些挥杆事件在事件队列中以不正确的顺序处理(没有什么让我热血沸腾,就像有人切入队列时一样)导致奇怪的行为。最好用一个小代码片段来说明这一点。阅读下面的片段并仔细思考您想象的事件将按什么顺序发生。

button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent arg0) {
         repaint();
         doSomething();
    }
});

大多数开发人员会认为 repaint() 方法将导致在 doSomething() 方法调用之前进行绘画操作。然而实际上并非如此,对 repaint() 的调用将创建一个新的绘制事件,该事件将被添加到事件队列的末尾。这个新的绘画事件只会在当前的动作事件完成后被处理(调度)。这意味着 doSomething() 方法将在队列上的新 Paint Event 被调度之前执行。

这里的关键点是调用 repaint() 将创建一个新的绘制事件,该事件将被添加到结束事件队列中并且不会立即处理。这意味着没有事件插队(我的血液可以保持在正确的温度)。

(资源)


我的问题是,我怎样才能强制 Swing 做repaint();BEFORE doSomething();

此外,如果有对repaint()WITHIN 方法的调用,doSomething();它们将仅在doSomething();完成后执行。有没有办法可以暂停doSomething();中间执行,然后扔进去reapaint();,完成它,然后恢复doSomething();

到目前为止,我发现的唯一解决方案是this(link),但这并不实用......

4

2 回答 2

6

好吧,你和引用文章的作者在这里没有抓住重点。“重绘”方法调用只是通知重绘管理器:

  1. 有一个要重绘的组件(您称之为“重绘”)
  2. 它应该用 (x,y,w,h) 剪辑重新绘制(如果你调用“repaint”而不指定 rect -它将是整个组件边界,(0,0,w,h))

因此,何时发生重绘并不重要,因为如果您为同一个组件一一调用 A LOT of repaints,您甚至可能不会注意到它。这也是为什么此类后续调用可能会被合并的原因。检查这个例子:

private static int repaintCount = 0;

public static void main ( String[] args )
{
    final JComponent component = new JComponent ()
    {

        protected void paintComponent ( Graphics g )
        {
            try
            {
                // Simulate heavy painting method (10 milliseconds is more than enough)
                Thread.sleep ( 10 );
            }
            catch ( InterruptedException e )
            {
                e.printStackTrace ();
            }

            g.setColor ( Color.BLACK );
            g.drawLine ( 0, 0, getWidth (), getHeight () );

            repaintCount++;
            System.out.println ( repaintCount );
        }
    };
    component.setPreferredSize ( new Dimension ( 200, 200 ) );

    JFrame frame = new JFrame ();
    frame.add ( component );
    frame.pack ();
    frame.setLocationRelativeTo ( null );
    frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
    frame.setVisible ( true );

    new Thread ( new Runnable ()
    {
        public void run ()
        {
            try
            {
                Thread.sleep ( 1000 );
            }
            catch ( InterruptedException e )
            {
                e.printStackTrace ();
            }
            System.out.println ( "Starting repaint calls" );
            for ( int i = 0; i < 100000; i++ )
            {
                component.repaint ();
            }
            System.out.println ( "Finishing repaint calls" );
        }
    } ).start ();
}

这是您将看到的近似输出(可能因计算机速度、Java 版本和许多其他条件而异):

1
Starting repaint calls
2
3
4
5
6
Finishing repaint calls
7
8

"1" - 显示帧时的初始重绘。
“2,3,4...” - 由于来自单独的非 EDT 线程的调用,发生了其他七次重绘。

“但我已经调用了 100000 次重绘,而不是 7 次!” - 你会说。是的,重绘管理器合并了那些相似且同时在重绘队列中的。这样做是为了优化重绘和整体加速 UI。

顺便说一句,您不需要从 EDT 调用 repaint,因为它不执行任何真正的绘画,只是将您的组件更新排队以备将来使用。它已经是线程安全的方法。

总而言之 - 在执行某些其他操作之前确实需要重新绘制组件(这也可能导致它再次重新绘制)时,不应该有任何情况。当您需要重绘组件时(如果可能,使用指定的矩形)只需调用重绘 - 重绘管理器将完成剩下的工作。除非您在绘制方法中进行一些计算,否则这种方法效果很好,这是完全错误的并且可能会导致很多问题。

于 2013-08-05T15:56:29.317 回答
2

repaint() 将新的绘制请求添加到事件调度线程 (EDT) 的队列末尾。因此,对 inside 进行多次调用只会在 doSomething() 完成后重新绘制repaint()doSomething()(我假设doSomething()总是从 EDT 调用。您的代码示例从内部调用 doSomething()actionPerformed总是从 EDT 调用。)

paint() 请求排队的原因是为了减少绘制组件的次数。排队 repaint() 请求允许多种方法将不同的组件标记为脏,以便它们可以在一个昂贵的 paint() 操作中同时重新绘制。

如果您真的想立即强制重绘,有类似的方法paintImmediately(Rectangle r)paintImmediately(int x, int y,int w,int h)但您必须知道要重绘的尺寸。

paint(Graphics g)如果您有对 Swing 组件正在使用的当前 Graphics 的引用,您也可以随时调用自己。(如果您想截取屏幕截图并将其写入图片文件,您也可以使用此技术从 Image 对象创建自己的图形对象)。

于 2013-08-05T15:55:46.010 回答