有这篇文章:
有人跳了队! 偶尔看来,一些挥杆事件在事件队列中以不正确的顺序处理(并且当有人切入队列时没有任何东西让我的血液沸腾)导致奇怪的行为。最好用小代码片段来说明这一点。阅读下面的代码段,仔细考虑您想象的事件发生的顺序。
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
repaint();
doSomething();
}
});
大多数开发人员会认为repaint()方法将导致在doSomething()方法调用之前进行绘制操作。但事实并非如此,对repaint()的调用将创建一个新的绘制事件,该事件将添加到事件队列的末尾。只有当前的Action Event完成后才会处理(调度)此新的paint事件。这意味着将在调度队列上的新Paint事件之前执行doSomething()方法。
这里的关键点是调用repaint()将创建一个新的绘制事件,该事件将被添加到结束事件队列中而不会立即处理。这意味着没有事件跳过队列(我的血液可以保持在正确的温度)。
我的问题是,如何强制Swing在repaint();
之前doSomething();
进行操作?
此外,如果在repaint()
内调用doSomething();
方法,则只有在doSomething();
完成后才会执行这些方法。有没有办法我可以暂停doSomething();
mid-executin,然后投入reapaint();
,完成它,然后恢复doSomething();
?
到目前为止我找到的解决方案只有this(link),但它并不实用......
答案 0 :(得分:6)
嗯,你和引文的作者在这里忽略了这一点。 “重绘”方法调用只是通知重绘管理器:
因此,当>> 重复出现时,重要的事情并不重要,因为如果您为同一个组件逐个调用大量重新绘制,您可能甚至都没有注意到它。这也是为什么这样的后续调用可能合并的原因。检查此示例:
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调用重绘,因为它不会执行任何真正的绘画,只是为了将来排队你的组件更新。它已经是线程安全的方法。
要总结一下 - 在执行其他操作之前,您确实需要重新绘制组件(这也可能导致其再次重新绘制)。只需在重新绘制组件时调用重绘(尽可能使用指定的矩形) - 重绘管理器将完成剩下的工作。除非你在paint方法中加入一些完全错误并且可能会导致很多问题的计算,否则这种方法很有效。
答案 1 :(得分:2)
repaint()将新的绘制请求添加到事件调度线程(EDT)的队列末尾。因此,在repaint()
内对doSomething()
进行多次调用只会在doSomething()完成后重新绘制。 (我假设始终从EDT调用doSomething()
。您的代码示例从actionPerformed
内部调用doSomething(),它始终从EDT调用。)
paint()请求排队的原因是减少组件绘制的次数。对repaint()请求进行排队允许使用多种方法将不同的组件标记为脏,以便在一次昂贵的paint()操作中同时重新绘制它们。
如果您真的想立即强制重绘,有paintImmediately(Rectangle r)
和paintImmediately(int x, int y,int w,int h)
等方法,但您必须知道要重新绘制的尺寸。
如果您对Swing组件正在使用的当前Graphics有一个引用,您也可以自己调用paint(Graphics g)
。 (如果要截取屏幕截图并将其写入图片文件,也可以使用此技术从Image对象创建自己的图形对象。