天下

记录修行的印记

有关窗口绘制的消息整理

窗口绘制有关的消息整理
WM_PAINTWM_NCPAINTWM_ERASEBKGND

WM_PAINT
WM_PAINT是Windows窗口系统中一条重要的消息,应用程序通过处理该消息实现在窗口上的绘制工作。

WM_NCPAINT
当窗口客户区以外的部分(如窗口标题栏、菜单栏等)需要需要重画时,系统向程序发出该消息。因标准窗口的客户区以外部分为窗口必需部分,因而该消息将默认被发送到DefWindowProc函数进行默认处理。程序可通过截获该消息来实现窗口其他部分的自定义绘制。

WM_ERASEBKGND

The WM_ERASEBKGND message is sent when the window background must be erased (for example, when a window is resized). The message is sent to prepare an invalidated portion of a window for painting.

一、WM_PAINT消息
在系统绘制窗口时向程序发出WM_PAINT消息。程序在接收到WM_PAINT消息后调用BeginPaint函数获取当前的Device Context进行绘图操作,绘图完毕后使用EndPaint释放Device Context。

1. 系统何时发送WM_PAINT消息?

系统会在多个不同的时机发送WM_PAINT消息:当第一次创建一个窗口时,当改变窗口的大小时,当把窗口从另一个窗口背后移出时,当最大化或最小化窗口时,等等,这些动作都是由 系统管理的,应用只是被动地接收该消息,在消息处理函数中进行绘制操作;大多数的时候应用也需要能够主动引发窗口中的绘制操作,比如当窗口显示的数据改变的时候,这一般是通过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消息来强制立即重画。
注解:
SendMessage会block到被发送的消息被处理完才返回,但是WM_PAINT消息的处理时间又是用户不可控制的:“GetMessage returns the WM_PAINT message when there are no other messages in the application's message queue, and DispatchMessage sends the message to the appropriate window procedure. ”(MSDN原文),那么也就是说,你调用SendMessage之后,这个方法需要等待多长时间才能返回是不可控制的。所以MSDN不推荐用户直接发送WM_PAINT消息:“The WM_PAINT message is generated by the system and should not be sent by an application”
但不如使用Windows GDI为我们提供的更方便和强大的函数:
UpdateWindow和RedrawWindow。UpdateWindow会检查窗口的Update Region,当其不为空时才发送WM_PAINT消息;
RedrawWindow则给我们更多的控制:是否重画非客户区和背景,是否总是发送WM_PAINT消息而不管Update Region是否为空等。

WM_PAINT触发机制
如果WM_PAINT不是由InvalidateRect或InvalidateRgn产生时,先发WM_ERASEBKGND,再发WM_PAINT
如果WM_PAINT是由InvalidateRect或InvalidateRgn产生时,则先发WM_PAINT,
然后beginPaint()再根据Invalidate的bErase参数(重画背景)来决定是否发WM_ERASEBKGND消息


当WM_PAINT不是由InvalidateRect产生时,即由最大化,最小化等产生时,或者移动产生(移动有时只会产生WM_ERASEBKGND消息)系统先发送WM_ERASEBKGND消息,再发送WM_PAINT消息.
当WM_PAINT由InvalidateRect()产生时,先发送WM_PAINT消息(异步),如果InvalidateRect的bErase为TRUE,BeginPaint检查到更新区域需要删除背景,向窗口发送一个WM_ERASEBKGND
消息


二、WM_ERASEBKGND消息
Parameters
wParam
Handle to the device context.

lParam
This parameter is not used.

Return Value
An application should return nonzero if it erases the background; otherwise, it should return zero.
也就是说WM_ERASEBKGND消息重绘了背景,应该返回非0值,否则返回0;


Remarks
The DefWindowProc function erases the background by using the class background brush specified by the hbrBackground member of the WNDCLASS structure. If hbrBackground is NULL, the application should process the WM_ERASEBKGND message and erase the background.

An application should return nonzero in response to WM_ERASEBKGND if it processes the message and erases the background; this indicates that no further erasing is required. If the application returns zero, the window will remain marked for erasing. (Typically, this indicates that the fErase member of the PAINTSTRUCT structure will be TRUE.)

补充:
DefWindowProc(hWnd, message, wParam, lParam)处理WM_ERASEBKGND消息时默认用下面的画刷清除背景
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

三、WM_NCPAINT消息
The WM_NCPAINT message is sent to a window when its frame must be painted.

A window receives this message through its WindowProc function.

LRESULT CALLBACK WindowProc(
  HWND hwnd,       // handle to window
  UINT uMsg,       // WM_NCPAINT
  WPARAM wParam,   // handle to update region (HRGN)
  LPARAM lParam    // not used
);
Parameters
wParam
Handle to the update region of the window. The update region is clipped to the window frame. When wParam is 1, the entire window frame needs to be updated.
lParam
This parameter is not used.
Return Values
An application returns zero if it processes this message.

Remarks
The DefWindowProc function paints the window frame.

An application can intercept the WM_NCPAINT message and paint its own custom window frame. The clipping region for a window is always rectangular, even if the shape of the frame is altered.

The wParam value can be passed to GetDCEx as in the following example.

case WM_NCPAINT:
{
    HDC hdc;
    hdc = GetDCEx(hwnd, (HRGN)wParam, DCX_WINDOW|DCX_INTERSECTRGN);
    // Paint into this DC
    ReleaseDC(hwnd, hdc);
}

四、PAINTSTRUCT结构体
The PAINTSTRUCT structure contains information for an application. This information can be used to paint the client area of a window owned by that application.

typedef struct tagPAINTSTRUCT {
HDC  hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT, *PPAINTSTRUCT;

Members
hdc
Handle to the display DC to be used for painting.

fErase
Specifies whether the background must be erased. This value is nonzero if the application should erase the background. The application is responsible for erasing the background if a window class is created without a background brush. For more information, see the description of the hbrBackground member of the WNDCLASS structure.

rcPaint
Specifies a RECT structure that specifies the upper left and lower right corners of the rectangle in which the painting is requested, in device units relative to the upper-left corner of the client area.

五、相关函数
1) BeginPaint
The BeginPaint function prepares the specified window for painting and fills a PAINTSTRUCT structure with information about the painting.
HDC BeginPaint(
  HWND hwnd,            // handle to window
  LPPAINTSTRUCT lpPaint // paint information
);
Parameters
hwnd
[in] Handle to the window to be repainted.
lpPaint
[out] Pointer to the PAINTSTRUCT structure that will receive painting information.
Return Values
If the function succeeds, the return value is the handle to a display device context for the specified window.
If the function fails, the return value is NULL, indicating that no display device context is available.
Windows NT/2000/XP: To get extended error information, call GetLastError.

Remarks
The BeginPaint function automatically sets the clipping region of the device context to exclude any area outside the update region. The update region is set by the InvalidateRect or InvalidateRgn function and by the system after sizing, moving, creating, scrolling, or any other operation that affects the client area. If the update region is marked for erasing, BeginPaint sends a WM_ERASEBKGND message to the window.

An application should not call BeginPaint except in response to a WM_PAINT message. Each call to BeginPaint must have a corresponding call to the EndPaint function.

If the caret is in the area to be painted, BeginPaint automatically hides the caret to prevent it from being erased.

If the window's class has a background brush, BeginPaint uses that brush to erase the background of the update region before returning.

BeginPaint
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_ERASEBKGND时返回TRUE,BeginPaint标记ps.fErase为FALSE,
如果处理WM_ERASEBKGND消息时返回FALSE,BeginPaint标记ps.fErase 为TRUE.

另外要注意的一点是,BeginPaint只能在WM_PAINT处理函数中使用。


2)InvalidateRect
   InvalidateRect只是增加重绘区域,在下次WM_PAINT的时候才生效,InvalidateRect函数中的参数TRUE表示系统会在你画之前用背景色将所选区域覆盖一次,默认背景色为白色,可以通过设置BRUSH来改变背景色。

   InvalidateRect(hWnd,
&rect,TRUE);向hWnd窗体发出WM_PAINT的消息,强制客户区域重绘制,rect是你指定要刷新的区域,此区域外的客户区域不被重绘,这样防止客户区域的一个局部的改动,而导致整个客户区域重绘而导致闪烁,如果最后的参数为TRUE,则还向窗体发送WM_ERASEBKGND消息,使背景重绘,当然在客户区域重绘之前。

(3)UpdateWindow
   UpdateWindow只向窗体发送WM_PAINT消息,在发送之前判断GetUpdateRect(hWnd,NULL,TRUE)看有无可绘制的客户区域,如果没有,则不发送WM_PAINT。如果希望立即刷新无效区域,可以在调用InvalidateRect之后调用UpdateWindow,如果客户区的任一部分无效,则UpdateWindow将导致Windows用WM_PAINT消息调用窗口过程(如果整个客户区有效,则不调用窗口过程)。这一WM_PAINT消息不进入消息队列,直接由WINDOWS调用窗口过程。窗口过程完成刷新以后立刻退出,WINDOWS将控制返回给程序中UpdateWindow调用之后的语句。(windows程序设计第5版 P98)

4)EndPaint
The EndPaint function marks the end of painting in the specified window. This function is required for each call to the BeginPaint function, but only after painting is complete.

BOOL EndPaint(
  HWND hWnd,                  // handle to window
  CONST PAINTSTRUCT *lpPaint  // paint data
);
Parameters
hWnd
[in] Handle to the window that has been repainted.
lpPaint
[in] Pointer to a PAINTSTRUCT structure that contains the painting information retrieved by BeginPaint.
Return Values
The return value is always nonzero.

Remarks
If the caret was hidden by BeginPaint, EndPaint restores the caret to the screen.


posted on 2011-04-15 11:46 天下 阅读(2234) 评论(1)  编辑 收藏 引用 所属分类: Win32

评论

# re: 有关窗口绘制的消息整理 2012-06-15 14:30 开通

很好。
能再详细点就更好了  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


<2011年4月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

常用链接

留言簿(4)

随笔分类(378)

随笔档案(329)

链接

最新随笔

搜索

最新评论