2009年2月8日星期日

WM_PAINT no end

今天在写程序的时候,发现一个自定义控件的WM_PAINT被不断的调用,以至于CPU占用率上升到40%以上。

我觉得很奇怪,我并没有移动窗口或者做什么引起重绘的操作,WM_PAINT为什么会被不断调用了。之后我发现一开始该控件没有不断重绘,只有第一次重绘之后才开始不断调用。
我的重绘代码是这样写的:

HDC hdc = GetDC(m_hWnd);
....do some drawing
Release(m_hWnd, hdc);
我很老实的遵守了调用DC的对称性规则,有创建也有释放。看来问题不在这里。

再一次仔细看了WM_PAINT的说明其中有几句引起了我的注意:
a WM_PAINT message may have been caused by both a non-NULL update region
仔细体会一下,就得出了这个问题的背后黑手--update region。

我曾在Windows绘图概述一文中写道:
事实上WM_PAINT产生的条件就是有部分窗口失效了。如果不出现这种情况,就不会产生WM_PAINT
当窗口的Client Area发生改变的时候,系统给应用程序发送WM_PAINT消息。系统仅在消息队列中没有其他消息的时候发送该消息给应用程序。一般的用法是在WM_PAINT中调用BeginPaint获得DC,然后进行任何GDI绘图,最后通过EndPaint释放DC
BeginPaint会把需要更新的区域设为NULL,以防连续导致发送WM_PAINT消息。
这里的文字已经很清楚的告诉了我原因,很可惜,这些文字是翻译自MSDN,没有经过自己亲身经历,因此没有深刻的体会。而这次,我正是犯了这个错误。

我接收到的WM_PAINT的消息是由InvalidateRect(NULL)导致的。该函数把整个Client区域都变成了update region.而我在WM_PAINT中又恰恰用了GetDC而不是BeginPaint函数来获取DC,因此导致处理完WM_PAINT后,失效区域没有更新,让Window以为我一直没有重绘,因此DefWindowProc中不断的重复发送WM_PAINT到message queue中,导致了无限循环。

解决的方法有两个,一个是在WM_PAINT中用BeginPaint和EndPaint来处理。另外一种是在WM_PAINT中使用ValidateRect函数。

Bjarne Stroustrup说过,一知半解的程序员是危险的,这个问题没有花去我很多时间,但是它告诉我,不要小看任何一个小小的函数。它或许牵涉到很大的问题。

没有评论: