不仅如此,我们还要理解WM_PAINT和无效区域的关系。我们知道WM_PAINT是用来绘制客户区的,无效区域是指定要绘画的地方的。也就是说,WM_PAINT消息是告诉窗口要重绘了,而无效区域就是指定重绘的地方,一唱一和。同时也暗含了一个意思,WM_PAINT和无效区域是相关联的。如果客户区内没有无效矩形,也就代表窗口没有需要重新绘制的地方,也就不需要重绘,那么此时就不需要WM_PAINT消息了,因为WM_PAINT消息就是要重绘窗口无效区域的。同样,如果窗口的消息队列中没有WM_PAINT消息,也表示窗口不需要重绘客户区。
在处理WM_PAINT消息之前,如果产生了多个无效区域,系统会将多个无效区域按照最小包含矩形来合并多一个小无效矩形,形成一个大的无效矩形。这个在前面提到的《无效矩形无效区域到底是什么东西》文中已经讲述了。
从这里可以看出,无效区域再处理之前就被合并,其实多个WM_PAINT未被处理的话,也会被合并为一个WM_PAINT消息来处理掉。反正都是重绘,既然无效区域都合并了,那么WM_PAINT合并也是自然而然的事情,况且还提高了效率。
你可以看到,无效区域和WM_PAINT真是郎才女貌,天生一对呀,哈哈哈。
那么问题来了。在WM_PAINT消息处理时,就会将消息队列的WM_PAINT消息从消息队列移除并且对WM_PAINT消息处理,这是通过消息循环的GetMessage来提取的。然而这里我们处理WM_PAINT消息时,只是简单的返回了0。这到底意味着什么呢?
返回0我们告诉了操作系统我们处理了这个消息,但是,实际上有没有处理,我们心知肚明。当然,系统还是知道的,你骗不了它哦。简单的返回0实际意义上并没有处理WM_PAINT消息。那么处理这个消息至少要做什么事呢?
默认将WM_PAINT消息丢给默认处理,系统只会简单的调用以下代码:
BeginPaint(hwnd,&ps);
EndPaint(hwnd,&ps);
虽然这里表面上好像什么都没有做,实际上却做了一个关键的处理。从上面解释的无效区域和WM_PAINT的成对出现,也就意味着,从消息队列中移除了WM_PAINT消息,那么也就表明无效区域一定要被消除,消除一般也就是会执行重绘。这样就达到了处理这个消息的目的。然而,你可以不重绘,但是必然要先取消无效区域,也就是让无效区域变成有效,表明这个区域被你重新绘制了一遍。当然,你可以不绘制,而只是简单的让这个区域变成有效的,这样消掉了无效区域,WM_PAINT消息也从消息队列移除,这样就保持了逻辑的一致,就表示简单的处理完毕了。而BeginPaint函数就可以让整个客户区变得有效,那么这个客户区中即使是其中一个小区域是无效的,因为在客户区内,也会被变成有效的。
让无效区域变得有效的函数有BeginPaint和ValidateRect,所以,只要你调用了其中一个,就表示对WM_PAINT消息做了处理。默认处理函数使用的是BeginPaint,你可以使用ValidateRect。不过,ValidateRect设置无效区域有效后,会擦除上次绘制的内容。
如果这两个函数你都不用,只简单返回0的话,那么在这个消息处理中客户区中的无效区域就永远不会被变成有效,因此要产生一个WM_PAINT消息进而来对无效区域重新绘制。而这个消息的处理代码又始终不能让无效区域变得有效,那么就一直会循环下去。这就是WM_PAINT消息处理中的客户区重绘消息死循环问题产生的原因。以下是图示循环过程:
当然,如果你在其他消息处理时让客户区变成了有效的,就消除了无效区域,这样就可以结束WM_PAINT消息的死循环问题。当然最好的问题就是不要产生这样的死循环。如果WM_PAINT消息你做了其他的画图操作,就是没有调用BeginPaint或ValidateRect使无效区域变成有效,那么就会一直循环的执行WM_PAINT消息处理下的代码。那问题就是大大的,如果你还不知道怎么回事,就玩大了。
这里提示一下,ValidateRect使无效区域变成有效后,会擦除无效区域范围内的上次绘制的内容,而BeginPaint则会根据PAINTSTRUCT结构体的成员标志是否擦除背景来决定是否擦除背景。
完整代码如下:
#include "windows.h"
#include <tchar.h>
// - 项目是Unicode字符集
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
static int i=0;
RECT rect;
switch (message)
{
case WM_PAINT:// - 未处理WM_PAINT消息,未使无效区域变为有效,从而产生了WM_PAINT消息处理死循环
i++;
return 0;
case WM_LBUTTONDOWN:
GetClientRect(hwnd,&rect);// - 是客户区变为有效,打破WM_PAINT消息处理死循环
ValidateRect(hwnd,&rect);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrev,LPSTR lpCmd,int iShow)
{
TCHAR ClassName[] = _T("MyClass");
TCHAR title1[] = _T("C++技术网http://www.cjjjs.com");
WNDCLASS wndClass;
wndClass.cbClsExtra=0;
wndClass.cbWndExtra=0;
wndClass.hbrBackground= (HBRUSH)GetStockObject(GRAY_BRUSH);
wndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
wndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wndClass.hInstance = hInstance;
wndClass.lpfnWndProc = WinProc;
wndClass.lpszClassName = ClassName;
wndClass.lpszMenuName=NULL;
wndClass.style=CS_HREDRAW|CS_VREDRAW;
if(!RegisterClass(&wndClass))
{
return 0;
}
HWND hwnd = CreateWindow(ClassName,title1,WS_OVERLAPPEDWINDOW,0,0,440,400,NULL,NULL,hInstance,NULL);
ShowWindow(hwnd,SW_SHOWNORMAL);
MSG msg;
while (GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
要检验是否进入了WM_PAINT消息处理死循环,可以在程序调试运行时在WM_PAINT消息处理的代码处打上断点,保证每次都可以执行到断点处,也就表明在执行这个死循环。