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 ... \n");
            WriteConsole(hOutput, "WM_PAIN\n", 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

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM