当前位置:C++技术网 > 精选软件 > WM_PAINT消息处理中的客户区重绘消息死循环问题详细分析和代码验证

WM_PAINT消息处理中的客户区重绘消息死循环问题详细分析和代码验证

更新时间:2015-12-06 23:16:19浏览次数:1+次

    在处理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消息从消息队列移除并且对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消息的死循环问题。当然最好的问题就是不要产生这样的死循环。如果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消息处理的代码处打上断点,保证每次都可以执行到断点处,也就表明在执行这个死循环。