写在属于自己的体会,哪怕只是一点点,也是真的懂了。否则有那么多书,如果只是不过脑子的学一遍看一遍,又有谁真的掌握了这些知识呢?
这样你或许就明白了为什么不能直接用SendMessage和PostMessage发送WM_PAINT的原因:由于没有invalidate,系统认为窗口没有更新的必要,于是就对发来的WM_PAINT消息不理不睬。解决方案就是——我们自己invalidate!相关的API就是InvalidateRect()和InvalidateRgn().
还想说一下Invalidate和UpdateWindow的区别。Invalidate在消息队列中加入一条WM_PAINT消息,其无效区为整个客户区。而UpdateWindow直接发送一个WM_PAINT消息,其无效区范围就是消息队列中WM_PAINT消息(最多只有一条)的无效区(估计把消息从消息队列中提出,直接发送给窗口,走关系户的路子)。效果很明显,调用Invalidate之后,屏幕不一定马上更新,因为WM_PAINT消息不一定在队列头部,而调用UpdateWindow会使WM_PAINT消息马上执行的,绕过了消息队列。如果你调用Invalidate之后想马上更新屏幕,那就加上UpdateWindow()这条语句
用WM_PAINT处理重画是异步(asynchronous)的。也就是说,在invalidate之后窗口并不会立即重画而是等到消息队列为空时再重画,这样就有一个时间差。这个事件差有时短到不被注意,但有时就是个大问题(尤其是当程序需要执行耗费时间的任务,如串口I/O)。这时可以采用同步重画法,直接用GetDC()获得hDC执行重画操作。如果非要使用WM_PAINT来同步重画(个人比较喜欢这种方法,和重画有关的代码就应该在WM_PAINT的处理程序里嘛),可以使用UpdateWindow()和RedrawWindow(). 这两个API函数会直接把WM_PAINT送进窗口的消息队列而不是应用程序的消息队列,这样就不用等到最后了。注意前者当update region不为空时才会发送WM_PAINT,后者的控制选项更为丰富。
RedrawWindow相当于先调用InvalidateRect,紧接着又调用UpdateWindow,此外RedrawWindow还提供了一些前两者没法做到的功能。
这个WM_PAINT消息既可能由系统发送,也可能由应用程序人工发送(比如自绘时需要)。当鼠标指向这个区域时加载hover图像以获得hottrack效果。这时操作系统自然不会认为有重画的必要,但程序却必须重画,这时就得人工发送WM_PAINT消息了。注意不要傻乎乎地直接用SendMessage或PostMessage发送WM_PAINT,后面会解释原因。重画很费时间和资源,并且也不是应用程序的“主业”,因此系统也知道要尽量减少重画的次数。系统只在应用程序的消息队列为空的时候才发送WM_PAINT,这就是为什么当程序死锁时窗口图像不会更新。同样为了减少重画的工作量,Windows提出了update region的概念。
比如原来在窗口上面的一个窗口现在挪走了,系统就把新露出来的区域定义为update region(这个过程称为invalidate)。系统不断检测一个窗口的update region是否为空,当update region不为空并且应用程序没有消息要处理(消息队列为空)的时候,系统就通过WM_PAINT告诉应用程序“现在没事干了?窗口的一部分需要重画,你把这一部分重画一下”。应用程序重画了窗口之后,把update region重新设置为空(这个过程称为validate),如此不断循环。如果消息队列不为空,系统就把update region不断更新(采用取并集的方法),等消息队列为空的时候一起处理。这就大大减少了重画的次数。
参考:
http://hi.baidu.com/pro_lily/item/a5c38afffac2495ac9f33721
http://hi.baidu.com/dongyiju2/item/3f8c2a10725e2526f7625c36
--------------------------- WM_PAINT 的产生原因 ------------------------------
当WM_PAINT不是由InvalidateRect产生时,即由最大化,最小化等产生时,或者移动产生(移动有时只会产生WM_ERASEBKGND消息)系统先发送WM_ERASEBKGND消息,再发送WM_PAINT消息.
如果处理WM_ERASEBKGND消息时返回FALSE,BeginPaint标记pt.fErase 为TRUE,如果处理WM_ERASEBKGND时返回TRUE,BeginPaint标记pt.fErase为FALSE.
当WM_PAINT由InvalidateRect产生时,先发送WM_PAINT消息(读书笔记:再看情况发送WM_ERASEBKGND)(异步),如果InvalidateRect的bErase为TRUE,BeginPaint检查到更新区域需要删除背景,向窗口发送一个WM_ERASEBKGND消息,如果处理WM_ERASEBKGND消息时返回FALSE,BeginPaint标记pt.fErase 为TRUE,如果处理WM_ERASEBKGND时返回TRUE,BeginPaint标记pt.fErase为FALSE.
如果pt.fErase标记为TRUE,指示应用程序应该处理背景,但是应用程序不一定需要处理,pt.fErase只是作为一个标记.
http://hi.baidu.com/dirtyface001/item/d6765b0d8a47338f03ce1b28
http://hi.baidu.com/qinfengxiaoyue/item/1a8f260ccbd3c528a0312d08