窗口绘制有关的消息整理 WM_PAINT, WM_NCPAINT, WM_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.

http://www.cppblog.com/aaxron/archive/2011/04/15/144284.html

时间: 2024-10-25 21:07:13

窗口绘制有关的消息整理 WM_PAINT, WM_NCPAINT, WM_ERASEBKGND的相关文章

深度分析WM_PAINT和WM_ERASEBKGND消息

做windows开发这么久了,一直以来对WM_PAINT和WM_ERASEBKGND消息总是感觉理解的不准确,每次要自绘一个窗口都因为知其然不知其所以然,偶然发现一篇文章,详细透彻地分了这个两个消息的用途和设计初衷,这篇文章也是我见过最深入也是最准确关于WM_PAINT和WM_ERASEBKGND消息的,文中每一句话都值得咀嚼.先转载如下: 一直以来,对于WM_PAINT和WM_ERASEBKGND消息不是很清楚,从书上和网上找了很多资料,大体上有以下几点说法:1>WM_PAINT先产生,WM_

WM_PAINT和WM_ERASEBKGND消息

1.OnPaint()函数是窗口重绘消息WM_PAINT的响应函数,当窗口重绘时会产生WM_ERASEBKGND消息和WM_PAINT消息,而且WM_ERASEBKGND会先于WM_PAINT产生,所以窗口重绘时,会先调用OnEraseBkGnd()擦除窗口,再调用OnPaint绘制窗口.如果你在OnPaint()里自绘了窗口,在窗口重绘的时候会发现窗口会闪一下,原因就是OnEraseBkGnd()函数中使用默认的画刷(一般为灰白色)来擦除窗口.解决这个问题有三个方法: ① 在OnEraseBk

模态窗口其实就是在当前窗口调用系统的消息循环,响应用户的操作,将相关的消息发送到对应的窗口(转)

上周准备在公司内部转岗,面了3个部门windows客户端相关的工作,最终拿到3个Offer,主要涉及C++和Windows两大块内容,C++的题目基本都答上了,Windows一直都是我的弱项,在这里记录一下Windows相关的题目.有些答不上的问题就没列出来,还有些问题忘了,下面的答案有些大部分是我自己的理解,有些是直接从网上copy的,有问题大家可以讨论. 1:GetMessage和PeekMessage的区别? GetMessage:获取消息队列中的一个消息,存入MSG中,并从消息队列中移除

窗口绘制

WM_PAINT 消息: Windows通过发送 WM_PAINT 消息来通知窗口过程其客户区需要重绘. 大多数 Windows 程序在 WinMain 函数初始化过程中会在进入消息循环之前调用 UpdateWindow() 函数. 这将会向窗口过程函数( WndProc() )发送最初的 WM_PAINT消息,该消息通知窗口过程绘制客户区. 在以下任何事件发生时,窗口过程都会收到一条 WM_PAINT 消息: 用户移动一个窗口,导致原来被遮挡的部分窗口暴露出来. 用户调整窗口大小. 程序调用

Win32知识之窗口绘制.窗口第一讲

Win32知识之窗口本质 一丶摘要 在学习Win32的时候. 很多操作都是窗口进行操作的.那么今天就说一下窗口的本质是什么. 窗口的本质是不断绘制.是windows通过消息机制进行绘制的. 我们知道.内存分为高低2G 低2G是给应用程序使用的. 高2G是给操作系统使用的.而我们画图形的操作都是操作系统通过底层的 win32k.sys这个驱动来提供的. 进程跟线程管理是通过notokerner.exe这个程序来管理的.但是三环不能使用.所以提供接口给三环. 分别是kerner32.dll 跟use

没有躲过的坑--捕获窗口之外的鼠标消息(钩子还是??)

做一个发送表情的对话框,类似微信和QQ一样的,点击表情按钮,弹出表情框,鼠标点击表情框外时,这个表情框被kill. 说白了 就是在这个窗口上获得窗口外面的鼠标消息. Google百度了一番,很多人说道使用钩子,才能获得窗口外面的click事件,这也未免太小题大做了吧. 还好发现了一个简简单单的函数: SetCapture(*this); 函数功能:该函数在属于当前线程的指定窗口里设置鼠标捕获.一旦窗口捕获了鼠标,所有鼠标输入都针对该窗口,无论光标是否在窗口的边界内.同一时刻只能有一个窗口捕获鼠标

WM_PAINT与WM_ERASEBKGND

当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产生时,先

解决WIN32窗口不响应WM_LBUTTONDBLCLK消息

原文链接: http://www.cnblogs.com/xukaixiang/archive/2012/05/27/2520059.html 今天在做一个软件时,发现win32创建的窗体不能响应WM_LBUTTONDBLCLK消息.在一番谷歌百度之后,终于找到了解决方法:废话少说,言归正传. 窗体不响应WM_LBUTTONDBLCLK消息,只要在注册窗口类的时候在WNDCLASS的style成员加上CS_DBLCLKS风格就行了. 下面是几种CS风格: CS_HREDRAW:当窗体的宽度发生变

线程给主窗口发事件(消息),传递字符串

代码来自安晓辉: #ifndef CUSTOMEVENT_H #define CUSTOMEVENT_H #include <QEvent> #include <QString> class CustomEvent : public QEvent // 自定义事件 { public: CustomEvent(const QString & msg); static QEvent::Type m_eventType; // 此事件的类型 static QEvent::Type