Win32 API的InvalidateRect函数为何看似无法正常工作?
Win32 API的InvalidateRect函数为何看似无法正常工作?
我仔细看了你提供的Win32桌面应用代码,发现几个关键问题导致InvalidateRect看似无法正常工作,甚至引发了相反的效果,咱们一步步拆解:
先贴出你的完整代码方便对照
// Win32-Desktop-Tab-Overlay-Splitter-RichEdit.cpp : Defines the entry point for the application. // #include "framework.h" #include <commctrl.h> #include <iostream> #include "resource.h" #pragma comment(lib, "comctl32.lib") #define ID_TABCTRL 100 #define ID_SPLITTER 101 #define MAX_LOADSTRING 100 HINSTANCE hInst; HWND hWnd; RECT hWnd_Rect; HWND hwndTabMain; HWND hwndDrawArea; HWND hwndOverlay; HWND hSplitter; HWND hRichEdit; int splitterPos = 200; // Initial splitter position static WNDPROC OldSplitterProc; static bool isDragging = false; WCHAR szTitle[MAX_LOADSTRING]; WCHAR szWindowClass[MAX_LOADSTRING]; ATOM RegisterWindowClasses(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK SplitterProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); void AllocateConsole(); void UpdateOverlayPosition(HWND hWnd, HWND hwndTabMain, HWND hwndOverlay); void AllocateConsole() { AllocConsole(); FILE* fpOut; freopen_s(&fpOut, "CONOUT$", "w", stdout); FILE* fpIn; freopen_s(&fpIn, "CONIN$", "r", stdin); FILE* fpErr; freopen_s(&fpErr, "CONOUT$", "w", stderr); std::cout << "Console window initialized successfully!" << std::endl; } LRESULT CALLBACK DrawAreaProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); RECT rect; GetClientRect(hwnd, &rect); InvalidateRect(hWnd, &rect, TRUE); // 这里是第一个大坑! HBRUSH whiteBrush = (HBRUSH)GetStockObject(WHITE_BRUSH); FillRect(hdc, &rect, whiteBrush); HPEN blackPen = (HPEN)GetStockObject(BLACK_PEN); HPEN oldPen = (HPEN)SelectObject(hdc, blackPen); MoveToEx(hdc, 0, 0, NULL); LineTo(hdc, rect.right, rect.bottom); SelectObject(hdc, oldPen); EndPaint(hwnd, &ps); return 0; } case WM_LBUTTONDOWN: { int x = LOWORD(lParam); int y = HIWORD(lParam); std::cout << "DrawArea Left Click at: (" << x << ", " << y << ")" << std::endl; return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } LRESULT CALLBACK OverlayProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); RECT rect; GetClientRect(hwnd, &rect); InvalidateRect(hwnd, &rect, TRUE); // 第二个坑! HPEN redPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0)); HPEN oldPen = (HPEN)SelectObject(hdc, redPen); MoveToEx(hdc, rect.left, rect.top, NULL); LineTo(hdc, rect.right, rect.bottom); MoveToEx(hdc, rect.right, rect.top, NULL); LineTo(hdc, rect.left, rect.bottom); MoveToEx(hdc, rect.left, rect.top, NULL); LineTo(hdc, rect.right, rect.top); MoveToEx(hdc, rect.right, rect.top, NULL); LineTo(hdc, rect.right, rect.bottom); MoveToEx(hdc, rect.right, rect.bottom, NULL); LineTo(hdc, rect.left, rect.bottom); MoveToEx(hdc, rect.left, rect.bottom, NULL); LineTo(hdc, rect.left, rect.top); SelectObject(hdc, oldPen); DeleteObject(redPen); EndPaint(hwnd, &ps); std::cout << "OverlayProc: WM_PAINT completed" << std::endl; return 0; } case WM_LBUTTONDOWN: { int x = LOWORD(lParam); int y = HIWORD(lParam); std::cout << "Overlay Left Click at: (" << x << ", " << y << ")" << std::endl; POINT pt = { x, y }; ClientToScreen(hwnd, &pt); ScreenToClient(hwndDrawArea, &pt); pt.x -= 4; pt.y -= 4; if (pt.x < 0) pt.x = 0; if (pt.y < 0) pt.y = 0; std::cout << "Forwarding click to DrawArea at: (" << pt.x << ", " << y << ")" << std::endl; SendMessage(hwndDrawArea, WM_LBUTTONDOWN, wParam, MAKELPARAM(pt.x, pt.y)); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } LRESULT CALLBACK SplitterProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_LBUTTONDOWN: SetCapture(hWnd); isDragging = true; return 0; case WM_MOUSEMOVE: if (isDragging) { POINT pt; GetCursorPos(&pt); ScreenToClient(GetParent(hWnd), &pt); int newSplitterPos = pt.y; RECT rect; GetClientRect(GetParent(hWnd), &rect); if (newSplitterPos < 100) newSplitterPos = 100; if (newSplitterPos > rect.bottom - 100) newSplitterPos = rect.bottom - 100; MoveWindow(hwndTabMain, 0, 0, rect.right, newSplitterPos, TRUE); MoveWindow(hSplitter, 0, newSplitterPos, rect.right, 5, TRUE); MoveWindow(hRichEdit, 0, newSplitterPos + 5, rect.right, rect.bottom - newSplitterPos - 5, TRUE); RECT displayRect1 = { 0, 0, rect.right, newSplitterPos }; TabCtrl_AdjustRect(hwndTabMain, FALSE, &displayRect1); MoveWindow(hwndDrawArea, displayRect1.left, displayRect1.top, displayRect1.right - displayRect1.left, displayRect1.bottom - displayRect1.top, TRUE); UpdateOverlayPosition(GetParent(hWnd), hwndTabMain, hwndOverlay); splitterPos = newSplitterPos; } return 0; case WM_LBUTTONUP: ReleaseCapture(); isDragging = false; return 0; } return CallWindowProc(OldSplitterProc, hWnd, msg, wParam, lParam); } ATOM RegisterWindowClasses(HINSTANCE hInstance) { WNDCLASSEXW wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32DESKTOPTABOVERLAYSPLITTERRICHEDIT)); wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WIN32DESKTOPTABOVERLAYSPLITTERRICHEDIT); wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); ATOM atom = RegisterClassExW(&wcex); // 注册DrawArea窗口类 WNDCLASSEXW drawClass = {0}; drawClass.cbSize = sizeof(WNDCLASSEX); drawClass.style = CS_HREDRAW | CS_VREDRAW; drawClass.lpfnWndProc = DrawAreaProc; drawClass.hInstance = hInstance; drawClass.hCursor = LoadCursor(nullptr, IDC_CROSS); drawClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); drawClass.lpszClassName = L"DrawAreaClass"; RegisterClassExW(&drawClass); // 注册Overlay窗口类 WNDCLASSEXW overlayClass = {0}; overlayClass.cbSize = sizeof(WNDCLASSEX); overlayClass.style = CS_HREDRAW | CS_VREDRAW; overlayClass.lpfnWndProc = OverlayProc; overlayClass.hInstance = hInstance; overlayClass.hCursor = LoadCursor(nullptr, IDC_HAND); overlayClass.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH); // 透明背景 overlayClass.lpszClassName = L"OverlayClass"; RegisterClassExW(&overlayClass); return atom; } BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { hInst = hInstance; // Store instance handle in our global variable hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // 创建Tab控件 hwndTabMain = CreateWindowW(WC_TABCONTROLW, L"", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, 0, 0, 600, 200, hWnd, (HMENU)ID_TABCTRL, hInstance, nullptr); // 添加Tab项 TCITEMW tci; tci.mask = TCIF_TEXT; tci.pszText = L"Draw Tab"; TabCtrl_InsertItemW(hwndTabMain, 0, &tci); // 创建DrawArea hwndDrawArea = CreateWindowW(L"DrawAreaClass", L"", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, 0, 0, 580, 160, hwndTabMain, nullptr, hInstance, nullptr); // 创建Overlay窗口(覆盖在DrawArea上) hwndOverlay = CreateWindowExW(WS_EX_LAYERED | WS_EX_TRANSPARENT, L"OverlayClass", L"", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, 10, 10, 100, 100, hwndDrawArea, nullptr, hInstance, nullptr); SetLayeredWindowAttributes(hwndOverlay, RGB(0,0,0), 128, LWA_ALPHA); // 半透明 // 创建Splitter hSplitter = CreateWindowW(L"STATIC", L"", WS_CHILD | WS_VISIBLE | SS_SUNKEN, 0, 200, 600, 5, hWnd, (HMENU)ID_SPLITTER, hInstance, nullptr); OldSplitterProc = (WNDPROC)SetWindowLongPtrW(hSplitter, GWLP_WNDPROC, (LONG_PTR)SplitterProc); // 创建RichEdit控件 LoadLibraryW(L"riched20.dll"); hRichEdit = CreateWindowW(RICHEDIT_CLASSW, L"", WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL, 0, 205, 600, 300, hWnd, nullptr, hInstance, nullptr); AllocateConsole(); return TRUE; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_COMMAND: { int wmId = LOWORD(wParam); // Parse the menu selections: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } } break; case WM_SIZE: { RECT rect; GetClientRect(hWnd, &rect); MoveWindow(hwndTabMain, 0, 0, rect.right, splitterPos, TRUE); MoveWindow(hSplitter, 0, splitterPos, rect.right, 5, TRUE); MoveWindow(hRichEdit, 0, splitterPos + 5, rect.right, rect.bottom - splitterPos -5, TRUE); RECT displayRect1 = {0,0, rect.right, splitterPos}; TabCtrl_AdjustRect(hwndTabMain, FALSE, &displayRect1); MoveWindow(hwndDrawArea, displayRect1.left, displayRect1.top, displayRect1.right - displayRect1.left, displayRect1.bottom - displayRect1.top, TRUE); UpdateOverlayPosition(hWnd, hwndTabMain, hwndOverlay); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); switch (message) { case WM_INITDIALOG: return (INT_PTR)TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; } break; } return (INT_PTR)FALSE; } void UpdateOverlayPosition(HWND hWnd, HWND hwndTabMain, HWND hwndOverlay) { RECT tabRect, drawRect, overlayRect; GetWindowRect(hwndTabMain, &tabRect); ScreenToClient(hWnd, (POINT*)&tabRect.left); ScreenToClient(hWnd, (POINT*)&tabRect.right); GetClientRect(hwndDrawArea, &drawRect); MapWindowPoints(hwndDrawArea, hWnd, (POINT*)&drawRect.left, 2); GetClientRect(hwndOverlay, &overlayRect); int overlayX = drawRect.left + (drawRect.right - drawRect.left - overlayRect.right)/2; int overlayY = drawRect.top + (drawRect.bottom - drawRect.top - overlayRect.bottom)/2; MoveWindow(hwndOverlay, overlayX - drawRect.left, overlayY - drawRect.top, overlayRect.right, overlayRect.bottom, TRUE); }
核心问题分析
1. 在WM_PAINT内部调用InvalidateRect是大忌
你在DrawAreaProc和OverlayProc的WM_PAINT处理中都调用了InvalidateRect:
- 当系统处理
WM_PAINT时,BeginPaint会清除窗口的无效区域,完成绘制后EndPaint会更新窗口状态。 - 但你在绘制中间又调用
InvalidateRect,会再次把窗口区域标记为“需要重绘”,系统会立刻再发送一条WM_PAINT消息。 - 这
相关产品推荐
相关产品推荐

