zoukankan      html  css  js  c++  java
  • 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

     

  • 相关阅读:
    Java 基础
    Java 数据类型
    Spring 拦截器实现事物
    SSH 配置日记
    Hibernate 知识提高
    Jsp、Servlet
    leetcode 97. Interleaving String
    leetcode 750. Number Of Corner Rectangles
    leetcode 748. Shortest Completing Word
    leetcode 746. Min Cost Climbing Stairs
  • 原文地址:https://www.cnblogs.com/Bachelor/p/11328817.html
Copyright © 2011-2022 走看看