为何无法用ValidateRect()替代BeginPaint()?
我理解BeginPaint和EndPaint可用于绘制屏幕并验证绘制的矩形区域,还能避免WM_PAINT消息刷屏(因为窗口不再有无效区域)。基于此,我认为理论上若在WM_CREATE中用BitBlt()绘制窗口,再用ValidateRect验证整个客户区,代码应能正常运行。但实际尝试后却失败了——将WM_PAINT中的代码替换为ValidateRect(hwnd,NULL);break;后,整个窗口不再显示。请问我哪里理解有误或代码存在问题?以下是我使用的代码,IDB_BITMAP1可替换为任意合适的位图:
#include <windows.h> #include "resource.h" HBITMAP hBitMap = NULL; LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: { BITMAP infoBM; hBitMap = LoadBitmap(GetModuleHandle(NULL), IDB_BITMAP1); if (hBitMap == NULL) { MessageBoxA(NULL, "COULD NOT LOAD CAT BITMAP", "ERROR", MB_OK); } HDC winDC = GetDC(hwnd); HDC hMem = CreateCompatibleDC(winDC); GetObject(hBitMap, sizeof(infoBM), &infoBM); HBITMAP hMemOld = SelectObject(hMem, hBitMap); BitBlt(winDC, 0, 0, infoBM.bmWidth, infoBM.bmHeight, hMem, 0, 0, SRCCOPY); SelectObject(hMem, hMemOld); DeleteDC(hMem); ReleaseDC(hwnd,winDC); break; } case WM_PAINT: //<<<--------- 我把这里的代码替换成ValidateRect(hwnd,NULL);break;后窗口就不显示了,为什么? { BITMAP infoBM; PAINTSTRUCT ps; HDC winDC = BeginPaint(hwnd, &ps); GetObject(hBitMap, sizeof(infoBM), &infoBM); HDC hMem = CreateCompatibleDC(winDC); HBITMAP hOldMem = SelectObject(hMem, hBitMap); BitBlt(winDC, 0, 0, infoBM.bmWidth, infoBM.bmHeight, hMem, 0, 0, SRCCOPY); SelectObject(hMem, hOldMem); DeleteObject(hMem); EndPaint(hwnd, &ps); break; } case WM_CLOSE: DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default:return DefWindowProcA(hwnd, msg, wParam, lParam); } return 0; } INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc = { sizeof(WNDCLASSEX),0,WindowProc,0,0,hInstance,NULL,LoadCursor(NULL,IDC_ARROW),(HBRUSH)(COLOR_WINDOW+1),NULL,"WindowClass",LoadIcon(NULL,IDI_APPLICATION)}; if (!RegisterClassEx(&wc)) { MessageBoxA(NULL, "WINDOW REGISTRATION FAILED", "ERROR", MB_OK); } HWND hwnd = CreateWindowEx(WS_EX_LAYERED, "WindowClass", "Title", WS_POPUP, 200, 200, 300, 300, NULL, NULL, hInstance, NULL); if (!hwnd) { MessageBoxA(NULL, "WINDOW CREATION FAILED", "ERROR", MB_OK); } ShowWindow(hwnd, SW_SHOW); UpdateWindow(hwnd); if (!SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 255, LWA_ALPHA)) { MessageBoxA(NULL, "WINDOW LAYERED ATTRIBUTES FAILED", "ERROR", MB_OK); } MSG msg; while (GetMessage(&msg, hwnd, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; }
问题原因分析
WM_CREATE阶段的绘制会被覆盖
WM_CREATE是窗口创建初期的消息,此时窗口还未执行ShowWindow和UpdateWindow,你用GetDC绘制的内容会被后续窗口显示流程中的系统刷新操作清空,根本无法保留到窗口显示的时候。分层窗口的绘制机制不兼容普通GDI操作
你创建的是带WS_EX_LAYERED扩展风格的分层窗口,这类窗口的图像合成由系统单独处理,普通的GetDC+BitBlt绘制不会被系统正确捕获并渲染到屏幕上,必须使用分层窗口专用的绘制接口(比如UpdateLayeredWindow),或者在WM_PAINT中配合BeginPaint/EndPaint完成绘制。WM_PAINT的处理不符合系统要求
WM_PAINT消息必须配套调用BeginPaint和EndPaint,不能仅用ValidateRect。BeginPaint不仅会标记无效区域为有效,还会初始化绘制上下文的状态,对于分层窗口来说,缺少这对函数的正确调用会导致系统无法完成窗口图像的合成,最终显示空白。
修正方案
方案一:去掉分层窗口风格(如果不需要分层效果)
修改CreateWindowEx的扩展风格为0,然后将所有绘制逻辑保留在WM_PAINT中(这是标准窗口的正确绘制方式):
HWND hwnd = CreateWindowEx(0, "WindowClass", "Title", WS_POPUP, 200, 200, 300, 300, NULL, NULL, hInstance, NULL);
方案二:使用分层窗口的正确绘制方式
如果需要保留WS_EX_LAYERED,可以改用UpdateLayeredWindow来设置窗口图像,示例如下(在WM_CREATE中完成绘制并更新分层窗口):
case WM_CREATE: { BITMAP infoBM; hBitMap = LoadBitmap(GetModuleHandle(NULL), IDB_BITMAP1); if (hBitMap == NULL) { MessageBoxA(NULL, "COULD NOT LOAD CAT BITMAP", "ERROR", MB_OK); } GetObject(hBitMap, sizeof(infoBM), &infoBM); HDC winDC = GetDC(hwnd); HDC hMem = CreateCompatibleDC(winDC); HBITMAP hOldMem = SelectObject(hMem, hBitMap); POINT ptSrc = {0,0}; POINT ptDst = {0,0}; SIZE szWnd = {infoBM.bmWidth, infoBM.bmHeight}; BLENDFUNCTION blend = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; UpdateLayeredWindow(hwnd, NULL, NULL, &szWnd, hMem, &ptSrc, 0, &blend, ULW_ALPHA); SelectObject(hMem, hOldMem); DeleteDC(hMem); ReleaseDC(hwnd, winDC); break; } case WM_PAINT: { // 分层窗口无需处理WM_PAINT,直接返回即可 ValidateRect(hwnd, NULL); break; }
内容的提问来源于stack exchange,提问作者bugger

