WM_PAINT 是 Windows 視窗程式設計中最基礎也最容易被誤解的消息。本文系統梳理其觸發時機、無效區域管理、BeginPaint/EndPaint 的關鍵作用,以及常見重繪函數的區別與陷阱。
CreateWindow 後)ScrollWindow/ScrollDC 滾動客戶區InvalidateRect/InvalidateRgn 手動標記無效區域WM_PAINT 只有在線程消息隊列為空時才會被投遞,且多次
Invalidate產生的無效區域會被合併為單一WM_PAINT。
這設計有雙重優勢:
Windows 為每個視窗維護一個「繪圖信息結構」,記錄無效區域坐標。WM_PAINT 本身不攜帶坐標,必須通過 BeginPaint 獲取:
typedef struct tagPAINTSTRUCT {
HDC hdc; // 繪圖設備上下文
BOOL fErase; // 是否已擦除背景
RECT rcPaint; // 無效區域矩形(核心信息)
BOOL fRestore, fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT;
MSDN 明確指出:
BeginPaint會將視窗的更新區域設置為 NULL。如果處理WM_PAINT時不調用BeginPaint(或以其他方式清空更新區域),系統會持續發送WM_PAINT,形成死循環,導致 CPU 100% 佔用與視窗閃爍。
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 1. 可選:僅繪製 ps.rcPaint 區域以提升性能
// 2. 繪圖代碼...
EndPaint(hwnd, &ps);
return 0;
}
InvalidateRect(..., TRUE)),自動發送 WM_ERASEBKGNDHDC 已設置剪裁區域為 rcPaint,繪圖自動限制在無效區域內| 函數 | 機制 | 特點 | 典型用途 |
|---|---|---|---|
InvalidateRect/Rgn | 標記無效區域,投遞 WM_PAINT 到消息隊列 | 異步、合併、低優先級 | 最常用,數據變更後請求重繪 |
UpdateWindow | 若更新區域非空,直接發送 WM_PAINT 到視窗程序(繞過隊列) | 同步、優先響應、不創建新消息 | ShowWindow 後強制首次繪製 |
RedrawWindow | InvalidateRect + UpdateWindow 的超集 | 可控制非客戶區、背景擦除、強制發送等 | 需精細控制重繂行為時 |
SendMessage(hwnd, WM_PAINT, ...):系統會檢查無效區域,為空則丟棄ValidateRect 可「欺騙」系統:不繪圖直接清空無效區域,阻止後續 WM_PAINTCS_HREDRAW/CS_VREDRAW 類樣式:尺寸變化時自動整個客戶區無效WM_PAINT 是被動通知,由系統根據無效區域異步發送BeginPaint 獲取繪圖信息並清空無效區域,是打破重繪循環的關鍵EndPaint 釋放 HDC,標記繪製完成Invalidate*(異步)、UpdateWindow(同步)、RedrawWindow(全控制)掌握這套機制,能從根源上解決視窗閃爍、重繪死循環、繪圖效率低等經典難題。