WM_PAINT详解和WM_ERASEBKGND

WM_PAINT简介:

WM_PAINT消息在Windows程序设计中是很重要的。当窗口显示区域的一部分显示内容或者全部变为“无效”,以致于必须“更新画面”时,将由这个消息通知程序。

WM_PAINT产生的时机:

1. 系统产生

其实系统会在很多的不同的机制下发送WM_PAINT消息,比如调用UpdateWindow函数,第一次创建窗口,改变了窗口的大小,最大化,最小化等等。这些动作的产生都是有系统来控制的,应用程序只是接收消息,并处理消息。当Window检测到窗口被覆盖的地方需要恢复的时候,它会向用户程序发送一个WM_PAINT消息,消息中包括了需要恢复的区域,然后由用户程序来决定如何恢复被覆盖的内容。窗口过程收到WM_PAINT消息后,并不代表整个客户区都需要被刷新,有可能客户区被覆盖的区域只有一小块,这个区域叫做“无效区域”,程序只需要更新这个区域。

首次创建窗口后,Windows将发送WM_ PAINT消息到窗口过程,实现窗口客户区的绘制。这是WM_PAINT消息的第一个用处。

另外,Windows是一个多任务环境,显示设备是为多个应用程序公用的,某个应用程序的窗口上面可能被对话框或窗口覆盖。当撤销这些对话框或窗口时,这个应用程序窗口中就有一个“空洞”,这个“空洞”就是一块无效的客户区域。像这种情况还很多,比如改变窗口尺寸后,客户区的部分内容被遮盖,窗口最小化、客户区的滚动等都会造成无效的客户区域。为重新显示无效客户区域,Windows发送WM_ PAINT消息,要求根据调整后的窗口尺寸重新绘制窗口。这是WM_PAINT消息的第二个用处。

WM PAINT消息在Windows消息队列中的优先级很低,这就使其他许多消息能够先于WM_PAINT消息被送交给窗口-过程处理。只有在没有其他消息的情况下,才从队列中取出WM_PAINT消息进行处理。这样做是为了让应用程序先完成影响窗口显示结果的其他操作,不至于因为频繁的执行输出而引起显示器的闪烁。

2. 程序产生

Windows并非WM_PAINT消息的唯一来源,使用InvalidateRect或InvalidateRng函数也可以产生绘图窗口的WM_PAINT消息,这两个函数把客户去部分或全部标记成无效区域而要求重新显示。

程序中可以调用InvalidateRect(hwnd,NULL,TRUE);函数或者InvalidateRng来产生WM_PAINT消息和WM_ERASEBKGND消息,注意第三个参数,如果为true,则会先产生WM_ERASEBKGND然后产生WM_PAINT,如果为FALSE,则只产生WM_PAINT消息。WM_PAINT是否产生取决于当前窗口显示区域中是否有无效区域,如果有(在处理WM_PAINT消息的BeginPaint前调用该函数的情况),则InvalidateRect只是重置一下无效区域,不会产生WM_PAINT,如果没有(非处理WM_PAINT消息时,调用该函数的情况),则InvalidateRect设置无效区域后发送WM_PAINT消息。

WM_PAINT消息的处理:

必须使用BeginPaint()EndPaint()处理

Hdc = BeginPaint(hwnd,&ps);

EndPaint(hwnd,&ps);

变量ps是形态为PAINTSTRUCT的结构,该结构的hdc字段是BeginPaint传回的设备内容句柄。PAINTSTRUCT结构中又包含一个名为rcPaint的RECT(矩形)结构,rcPaint定义一个包围窗口显示区域无效范围的矩形。使用BeginPaint获得的设备描述表句柄,只能在这个区域内绘图。BeginPaint调用使该区域有效。

注意:若要响应WM_PAINT消息,则无论如何都要使BeginPaint和EndPaint被执行,否则会出问题(CPU占用近100%)。

需要注意的是:如果窗口函数不处理WM_PAINT消息,则将WM_PAINT消息传递给windows的默认消息处理函数(DefWindowProc)处理,DefWindowProc函数中处理W M_PAI NT消息的代码如下:

case WM PAINT:

hdc=BeginPaint( hwnd,&ps);

//其他GDI操作

EndPaint( hwnd, &ps);

return 0;

这段代码并不进行任何绘图操作,只是简单地将ps指定的无效矩形区域变为有效

请注意,如果应用程序用如下方法处理WM_PAINT消息,那么用户也许将不得不强行行中断程序。

case WM PAINT:

return 0;

如果当前窗口用户区的某一部分变为无效,则Windows会将一个WM_ PAINT消息放到消息队列中,如果不调用BeginPaint和EndPaint函数,(同时,也不调用ValidateRect函数),该无效区域不会变为有效,于是,Windows将会发送另一个WM_PAINT消息,并且会一直发送下去。

BeginPaint函数只做了两件事:

1.使窗口无效区域变得有效,从而使windows不再发送WM_PAINT消息(直到窗口大小改变等,是窗口再次变得无效)。(如果窗口一直无效,则windows会不停的发送WM_PAINT消息)

2.填充PAINTSTRUCT结构。填充这个结构的目的,是让程序员可以根据ps变量中的标志值进行某些操作。

MFC中调用BeginPaint的方法有三种:
1、声明一个CPaintDC类型的变量(即使你什么也不画),CPaintDC的构造函数就是调用BeginPaint,析构函数就是调用EndPaint。
2、调用基类的OnPaint(实际上就是调用API的DefWindowProc,它会自动调用BeginPaint和EndPaint)。
3、自己直接调用BeginPaint和EndPaint。
有的人说如果不调用BeginPaint的话,WM_PAINT消息不会从消息队列中移走,这一点我不太确定,
我觉得我们处理WM_PAINT消息的时候它就已经移出来了,不调用BeginPaint的话,只是会造成系统不断的发送WM_PAINT。

 

WM_ERASEBKGND简介

当窗口背景必须被擦除时 (例如,窗口的移动,窗口的大小的改变)才发送。

其实通过上面WM_PAINT的介绍我们可以发现,WM_ERASEBKGND一般是和WM_PAINT一块产生的,并且先产生WM_ERASEBKGND,紧接着产生WM_PAINT.

注意:上面的标红字是有特殊意义的,必须被擦除时,当WM_ERASEBKGND产生,然后WM_PAINT产生时,我们处理WM_PAINT时,没有调用BeginPaint也没有调用ValidateRect,这时由于无效区域一直无效,所以系统一直发送WM_PAINT,但此时,背景没有必要再被擦除,所以,此时不会一直收到WM_ERASEBKGND.

WM_ERASEBKGND产生时机:

系统产生

当程序第一次绘制时,系统会先产生一个WM_ERASEBKGND,然后产生一个WM_PAINT

当窗口最小化,最大化,窗口被遮住后重新显示,或者窗口移动到屏幕外面再拖回重新显示时,需要被重新显示的区域会被标记为无效区域,系统检测到无效区域后会产生一个或几个WM_ERASEBKGND然后再产生WM_PAINT,如果无效区域持续无效,WM_PAINT会持续产生,WM_ERASEBKGND不会持续产生。

程序产生

上面WM_PAINT中已经介绍,这里不再赘述。

WM_ERASEBKGND消息的处理:

窗口无效

当拖动窗口的一个顶点改变了窗口的大小,窗口由最小化恢复到最大化、窗口的一部分被其他窗口遮住又重新显示、调用MoveWindow函数改变了窗口大小、窗口移动到桌面之外的部分被拖回重新显示时,窗口会变得无效。(注:拖动窗口标题栏移动窗口,只要窗口没有移动到屏幕之外然后再重新显示,那么这两个消息都不会产生)

背景擦除

当窗口无效时,Windows会给窗口发出WM_ERASEBKGND消息和WM_PAINT消息,而且WM_ERASEBKGND先发出次或者几次,紧接着是WM_PAINT.

窗口背景的擦除:默认情况下窗口的背景色是由默认的消息处理函数DefWindowProc擦除的(即这个函数使用注册窗口类时使用的背景刷擦除窗口背景)。什么时候擦除?在窗口函数收到WM_ERASEBKGND消息,DefWindowProc函数以WM_ERASEBKGND为参数,才会擦除窗口背景(注意:当WM_ERASEBKGND消息产生后,窗口一定有一部分变得无效)

例外:InvalidateRect函数的调用会使窗口变得无效,并产生WM_ERASEBKGND消息和WM_PAINT消息,而WM_ERASEBKGND是否产生取决于参数bErase

void InvalidateRect(LPCRECTlpRect, BOOL bErase = TRUE);

当参数bErase= TRUE时,WM_ERASEBKGND消息产生,当bErase为false时WM_ERASEBKGND消息不产生。则InvalidateRect函数的最后一个参数会指定是否擦除背景。如果为FALSE则windows不会擦除背景,当WM_PAINT不是由InvalidateRect产生时,即由最大化、最小化、等产生时,或者移动产生(移动时只会产生WM_ERASEBKGND消息)系统先发送WM_ERASEBKGND消息,再发送WM_PAINT消息。

如果处理WM_ERASEBKGND消息时返回FALSE(表示未擦除背景),BeginPaint标记ps.fErase(表示是否需要先擦除背景)为TRUE,如果处理WM_ERASEBKGND时返回TRUE(表示已成功擦除背景),BeginPaint标记ps.fErase为FALSE.

如果ps.fErase标记为TRUE,表示应用程序应该先擦除背景,但是应用程序不一定非要处理,ps.fErase只是作为一个标记。

在这个消息中如果调用了hdc= BeginPaint(hWnd, &ps);函数,则此函数只做了两件事:填充ps结构、使窗口重新变得有效,另外DefWindowProc函数也会使窗口变得有效。

关于ps.fErase:这个参数和窗口函数WndProc处理完WM_ERASEBKGND消息的返回值有关,当窗口函数WndProc返回true;则产生WM_PAINT消息时,ps.fErase就是false;表明系统擦除了背景,不需要应用程序擦除;当窗口函数WndProc返回false,ps.fErase就为true,表明系统没有擦除背景,需要应用程序擦除。

补充:DefWindowProc(hWnd,message, wParam, lParam)处理WM_ERASEBKGND消息时默认用下面的花刷擦除背景

wcex.hbrBackground =(HBRUSH)(COLOR_WINDOW+1);

WM_ERASEBKGND消息处理后返回TRUE或返回FALSE是一个规范,一般情况下没有什么区别,但是如果什么时候用到了,会根据函数返回值判断后续处理,因此最好按照要求返回数据。

 

WM_PAINT的处理经验

在处理WM_PAINT消息时,为了在更新的矩形外绘图,可以在调用BeginPaint之前使用如下函数:

InvalidateRect(hwnd,NULL,TRUE);它使整个显示区域变为无效,并擦除背景(就是产生WM_PAINT之前先产生WM_ERASEBKGND)。但是,如果最后一个参数为FALSE,则不擦除背景(不产生WM_ERASEBKGND,只产生WM_PAINT),原有的东西将保留在原处。通常这是windows程序在无论何时收到WM_PAINT消息而不考虑rcPaint结构的情况下简单的重画整个客户区最方便的方法。例如,如果在客户区的显示输出中包括了一个圆,但是只有圆的一部分落到了无效矩形中,它就仅绘制圆在无效矩形中的部分。它对于画整个圆来说是毫无意义的。注意:使用从BeginPaint返回的设备描述表句柄时,windows不会绘制rcPaint矩形外的任何部分。在BeginPaint调用之气使用上面那个函数,可使rcPaint结构中的无效矩形为整个客户区,就不愁绘制圆了。

在处理WM_PAINT消息时,必须成对的调用BeginPaint和EndPaint。如果窗口过程不处理WM_PAINT消息,则它必须将WM_PAINT消息传递给windows中的DefWindowProc(默认窗口过程)。

DefWindowProc以下列代码处理WM_PAINT消息:

case WM_PAINT:

       BeginPaint(hwnd, &ps);

       EndPaint(hwnd,&ps);

       return  0;

这两个BeginPaint和EndPaint调用之间没有任何语句,仅仅使先前无效区域变为有效。但以下方法是错误的:

case WM_PAINT:

       return  0;    //WRONG!!!

Windows将一个WM_PAINT消息放到消息队列中,是因为客户区的一部分无效。如果不调用BeginPaint和EndPaint(或者ValidateRect),则windows不会使该区域变为有效。

若一直保持无效,windows将会再发送另一个WM_PAINT消息,并一直发送下去。


你可能感兴趣的:(WM_PAINT,WM_ERASEBKGND)