• WM_PAINT 消息详细解析


    WM_PAINT是Windows窗口系统中一条重要的消息,

    应用程序通过处理该消息实现在窗口上的绘制工作。

    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消息来强制立即重画,但不如使用Windows GDI为我们提供的更方便和强大的函数:UpdateWindow和RedrawWindow。

       UpdateWindow会检查窗口的Update Region,当其不为空时才发送WM_PAINT消息;RedrawWindow则给我们更多的控制:是否重画非客户区和背景,是否总是发送WM_PAINT消息而不管Update Region是否为空等。



    2. 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消息了。

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

     

     


    补充几点:

    1.WM_Paint 是一个被动消息,不能通过普通的方法简单的 sendmessage WM_paint 了事
    这是不行的;但通过消息由程序员引发不是不可能;通过几个特殊的常数可以做到,不过要到delphi下找

    2.sendmessage 可以将消息发送到消息队列;但windows会自动判断是否存在无效的画图区域;
    如果存在无效的画图区域,则可能会重画,反之则弃用该消息.

    3.可以使用 InvalidateRect 等几个APi将屏幕上任意一个个矩形区域设置为无效区域,在UpdateWindow后调用后,windows会自动查找是否存在无效,并重画,该矩形区

     

    WM_PAINT的触发时机分别进行验证:
    1、程序启动时,绘制窗口时触发。
            在我们启动程序时,由于需要绘制窗口,会触发WM_PAINT消息,此时会打印上述字符串:


    2、用鼠标调整窗口的大小,时会连续触发:
            由于调整窗口大小时,需要不断的重绘窗口,所以此时表现出来的就是不断的触发WM_PAINT消息:


    3、最小化时不会触发WM_PAINT消息,但是从最小化还原时会进行触发
            下面这张图,是在两次从最小化到还原窗口的过程,可以看到多了两次字符串的打印


    4、最大化时会触发WM_PAINT消息
            当图片最大化和还原后分别触发一次WM_PAINT消息,如下图所示:

    5、当向屏幕外拖动窗口时,不会触发WM_PAINT消息,但是拉回到屏幕内时会不断的触发WM_PAINT消息
            下面的截图,就是在将窗口拉回屏幕时,窗口在不断的进行重绘,触发着WM_PAINT消息。

    6、使用InvalidateRect函数触发WM_PAINT消息
            InvalidateRect的函数原型如下,每次调用都会触发一次WM_PAINT消息:

        BOOL InvalidateRect(
        HWND hWnd, // handle of window with changed update region
        CONST RECT *lpRect, // address of rectangle coordinates
        BOOL bErase // erase-background flag
        );

    hWnd:要更新的客户区所在的窗体的句柄。如果为NULL,则系统将在函数返回前重新绘制所有的窗口, 然后发送 WM_ERASEBKGND 和 WM_PAINT 给窗口过程处理函数。
    lpRect:无效区域的矩形代表,它是一个结构体指针,存放着矩形的大小。如果为NULL,全部的窗口客户区域将被增加到更新区域中。
    bErase:指出无效矩形被标记为有效后,是否重画该区域,重画时用预先定义好的画刷。当指定TRUE时需要重画。
    返回值:
    函数成功则返回非零值,否则返回零值。

     

     

    #include "stdafx.h"
    #include "PaintTS.h"
    #include "stdio.h"
    #include <atltrace.h>
    
    HINSTANCE g_hInstance = 0;  
    HANDLE hOutput;
    void DrawRect()
    {
    
    }
    
    //窗口处理函数  
    LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)  
    {  
        
        switch (uMsg)  
        {  
        case WM_DESTROY:  
            PostQuitMessage(0);//可以使GetMessage返回0  
            break;
        case  WM_PAINT:
            {
                PAINTSTRUCT pt;
                HDC hdc;
                hdc=BeginPaint(hWnd,&pt);
                Rectangle(hdc,0,0,100,100);
                ATLTRACE("paint ... 
    ");
                WriteConsole(hOutput, "WM_PAIN
    ", 10, NULL, NULL);
                EndPaint(hWnd,&pt);
            }
        case WM_LBUTTONDOWN:
            {
                //InvalidateRect(hWnd,NULL,true);
            }
            break;
        default:  
            break;  
        }  
        return DefWindowProc(hWnd, uMsg, wParam, lParam);  
    }  
    
    //注册窗口类  
    BOOL Register(LPSTR lpClassName, WNDPROC wndProc)  
    {  
        WNDCLASSEX wce = { 0 };  
        wce.cbSize = sizeof(wce);  
        wce.cbClsExtra = 0;  
        wce.cbWndExtra = 0;  
        wce.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);  
        wce.hCursor = NULL;  
        wce.hIcon = NULL;  
        wce.hIconSm = NULL;  
        wce.hInstance = g_hInstance;  
        wce.lpfnWndProc = wndProc;  
        wce.lpszClassName = lpClassName;  
        wce.lpszMenuName = NULL;  
        wce.style = CS_HREDRAW | CS_VREDRAW;  
        ATOM nAtom = RegisterClassEx(&wce);  
        if (nAtom == 0)  
            return FALSE;  
        return true;  
    
    }  
    //创建主窗口  
    HWND CreateMain(LPSTR lpClassName, LPSTR lpWndName)  
    {  
        HWND hWnd = CreateWindowEx(0, lpClassName, lpWndName,  
            WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, g_hInstance, NULL);  
        return hWnd;  
    }  
    //显示窗口  
    void Display(HWND hWnd)  
    {  
        ShowWindow(hWnd, SW_SHOW);  
        UpdateWindow(hWnd);  
    }  
    //消息循环  
    void Message()  
    {  
        MSG nMsg = { 0 };  
        while (GetMessage(&nMsg, NULL, 0, 0))  
        {  
            TranslateMessage(&nMsg);  
            DispatchMessage(&nMsg);  
        }  
    }  
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance,  
        _In_opt_ HINSTANCE hPrevInstance,  
        _In_ LPWSTR    lpCmdLine,  
        _In_ int       nCmdShow)  
    {  
        AllocConsole();
        hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
        g_hInstance = hInstance;  
        BOOL nRet = Register("Main", WndProc);  
        if (!nRet)  
        {  
            MessageBox(NULL, "注册失败", "Infor", MB_OK);  
            return 0;  
        }  
        HWND hWnd = CreateMain("Main", "window");  
        Display(hWnd);  
        Message();  
        return 0;  
    }  
    View Code

     

  • 相关阅读:
    二读《活着》有感
    Linux系统上安装JDK和Tomcat服务器
    在阿里云服务器上安装完成并启动Tomcat后,通过http不能访问--解决办法
    安装JDK出现错误:-bash: /usr/java/jdk1.7.0_71/bin/java: /lib/ld-linux.so.2: bad ELF interpreter: No such file or directory解决办法
    python结巴(jieba)分词
    mysql数据库优化
    ansible常见模块
    CentOS6.5 64位下安装部署Ansible
    [Python] 利用commands模块执行Linux shell命令
    python迭代器
  • 原文地址:https://www.cnblogs.com/Bachelor/p/11328817.html
Copyright © 2020-2023  润新知