The WM_PAINT message is generated by the system and should not be sent by an application.The system sends this message when there are no other messages in the application's message queue
也就是说WM_PAINT消息是由系统产生,非要等应用程序的消息队列为空时才发送WM_PAINT消息,并且该消息不应该被程序(自己写代码用SendMessage)来发送。
当调用UpdateWindow函数,或者是Window检测到窗口被覆盖的地方需要恢复的时候,比如,第一次创建窗口,改变了窗口的大小,最大化,最小化等等(其实这些事件发生时会调用UpdateWindow函数,由该函数发送WM_PAINT消息),它会向用户程序发送一个WM_PAINT消息。窗口过程收到WM_PAINT消息后,并不代表整个客户区都需要被刷新,有可能客户区被覆盖的区域只有一小块,这个区域叫做“无效区域”,程序只需要更新这个区域。与WM_TIMER消息类似,WM_PAINT消息也是一个低级别的消息,虽然它不会像WM_TIMER消息一样被丢弃,但Windows总是在消息循环空的时候才把WM_PAINT放入其中。
无效区域的坐标并不附带在WM_PAINT消息的参数中,在程序中有其他方法可以获取。WM_PAINT消息只是通知程序有个区域需要更新而已,所以Windows也不会同时将两条WM_PAINT消息放入消息循环中。当Windows要放入一条WM_PAINT消息的时候,如果发现已经存在一个无效区域了,那么它只需要把新旧两个无效区域合并计算出一个无效区域就可以了,消息循环中还是只需要一条WM_PAINT消息。
实际上,Windows为每个窗口维护一个“绘图信息结构”,无效区域的坐标就在其中,每当消息循环空的时候,如果Windows发现存在一个无效区域,就会放入一个WM_PAINT消息。那么“绘图信息结构”怎么获取呢?BeginPaint函数的第二个参数就是一个绘图信息结构的缓冲区地址,windows会在这里返回绘图信息结构,结构中包含了无效区域的位置和大小,绘图信息结构的定义如下:
typedef struct tagPAINTSTRUCT { // ps
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT;
其中hdc字段是窗口的设备环境句柄,rcPaint字段是一个RECT结构,它指定了无效区域矩形的对角顶点(如果开始有一个((0,0),(40,40)),现在又来一个((20,20),(60,30)),那么拼接后就是((0,0),(60,40))),fErase字段如果为非零值,表示Windows在发送WM_PAINT消息前已经使用背景色擦除了无效区域,后面3个字段是Windows内部使用的,应用程序不必去理会他们。
在某些情况下,显示区域的一部分被临时覆盖,Windows试图保存一个显示区域,并在以后恢复它,但这不一定能成功。Windows可能发送WM_PAINT消息:Windows擦除覆盖了部分窗口的对话框或消息框;菜单下拉出来,然后被释放;显示工具提示消息。
在某些情况下,Windows总是一定保存它所覆盖的显示区域,然后恢复它。这些情况是:鼠标光标穿越显示区域;图标拖过显示区域。
有时候应用也需要能够主动引发窗口中的绘制操作,比如当窗口显示的数据改变的时候,这一般是通过InvalidateRect和 InvalidateRgn函数来完成的。InvalidateRect和InvalidateRgn把指定的区域加到窗口的Update Region中,当应用的消息队列没有其他消息时,如果窗口的Update Region不为空时,系统就会自动产生WM_PAINT消息。
系统为什么不在调用Invalidate时发送WM_PAINT消息呢?又为什么非要等应用消息队列为空时才发送WM_PAINT消息呢?这是因为系统把在窗口中的绘制操作当作一种低优先级的操作,于是尽 可能地推后做。不过这样也有利于提高绘制的效率:两个WM_PAINT消息之间通过InvalidateRect和InvaliateRgn使之失效的区域就会被累加起来,然后在一个WM_PAINT消息中一次得到 更新,不仅能避免多次重复地更新同一区域,也优化了应用的更新操作。像这种通过InvalidateRect和InvalidateRgn来使窗口区域无效,依赖于系统在合适的时机发送WM_PAINT消息的机 制实际上是一种异步工作方式,也就是说,在无效化窗口区域和发送WM_PAINT消息之间是有延迟的;有时候这种延迟并不是我们希望的,这时我们当然可以在无效化窗口区域后利用SendMessage 发送一条WM_PAINT消息来强制立即重画,但不如使用Windows GDI为我们提供的更方便和强大的函数:UpdateWindow和RedrawWindow。UpdateWindow会检查窗口的Update Region,当其不为空时才发送WM_PAINT消息;RedrawWindow则给我们更多的控制:是否重画非客户区和背景,是否总是发送WM_PAINT消息而不管Update Region是否为空等。
BeginPaint
今天在处理WM_PAINT消息时产生了一个低级的错误,并搞的我花了快一个小时才找到原因。我在处理消息时,没有使用BeginPaint和EndPaint这对函数,结果我其余的消息弹不出来,窗口拖动时,不停闪烁(其实那就是重绘)。后来还是在MSDN上找到了答案,现将原话贴出来。(在MSDN的The WM_PAINT Message标题中)
BeginPaint sets the update region of a window to NULL. This clears the region, preventing it fromgenerating subsequent WM_PAINT messages. If an application processes a WM_PAINT message but does not call BeginPaint or otherwise clear the update region, the application continues to receive WM_PAINT messages as long as the region is not empty. In all cases, an application must clear the update region before returning from the WM_PAINT message.
BeginPaint函数的作用就是将窗口需要重绘的区域设置为空(也就是Update Region置空)。在正常情况下,我们接收到了WM_PAINT消息后,窗口的Update Region都是非空的(如果为空就不需要发送WM_PAINT消息了)。而当你响应这个消息的时候又不调用BeginPaint来清空,窗口的Update Region就一直是非空的,系统就会一直发送WM_PAINT消息。这样就形成了一个处理WM_PAINT消息的死循环。
BeginPaint和WM_PAINT消息紧密相关。试一试在WM_PAINT处理函数中不写BeginPaint会怎样?程序会像进入了一个死循环一样达到惊人的CPU占用率,你会发现程序总在处理一个接 一个的WM_PAINT消息。这是因为在通常情况下,当应用收到WM_PAINT消息时,窗口的Update Region都是非空的(如果为空就不需要发送WM_PAINT消息了),BeginPaint的一个作用就是把该Update Region置为空,这样如果不调用BeginPaint,窗口的Update Region就一直不为空,如前所述,系统就会一直发送WM_PAINT消息。
BeginPaint和WM_ERASEBKGND消息也有关系。当窗口的Update Region被标志为需要擦除背景时,BeginPaint会发送WM_ERASEBKGND消息来重画背景,同时在其返回信息里有一个标志表明窗口背景是否被重画过。当我们用InvalidateRect和InvalidateRgn来把指定区域加到Update Region中时,可以设置该区域是否需要被擦除背景,这样下一个BeginPaint就知道是否需要发送WM_ERASEBKGND消息了。
当然关于 WM_PAINT消息还有很多的知识需要学习。另外要注意的一点是,BeginPaint只能在WM_PAINT处理函数中使用,并且在调用了BeginPaint函数后,不要忘记了调用EndPaint函数,他们可是一对的。
重画函数 InvalidateRect,Invalidate,UpdateWindow, RedrawWindow
InvalidateRect(部分区域) 和Invalidate(整个窗口) 仅仅是用来设置无效区域,但是并不重绘窗口。
UpdateWindow 检查窗口有无无效区域,如果有,则立即发送一个WM_PAINT 消息给窗口并立即重画。
RedrawWindow相当于先调用InvalidateRect,紧接着又调用UpdateWindow,此外RedrawWindow还提供了一些前两者没法做到的功能。
如果不调用 InvalidateRect就调用 UpdateWindow,那么UpdateWindow什么都不做,因为没有无效区域。如果调用 InvalidateRect 后不调用UpdateWindow,则系统会自动在窗口消息队列为空的时候,系统自动发送一WM_PAINT消息。