Windows 10强制立即重绘后窗口调整大小卡顿问题
Windows 10强制立即重绘导致窗口调整大小异常的问题解析与解决方案
问题1:为什么会出现调整大小卡住的情况?
Windows窗口调整大小由系统的模态消息循环处理:当你拖动窗口边框时,系统会接管消息处理,持续响应鼠标移动来调整窗口尺寸,直到捕获到WM_LBUTTONUP(鼠标释放)才退出这个循环。
而使用RDW_INVALIDATE | RDW_UPDATENOW或者UpdateWindow()时,会强制立即同步执行重绘——这会打断系统的模态循环,抢占消息处理优先级,导致系统无法及时接收和处理鼠标释放等关键消息。同时主循环每一轮都调用立即重绘,会持续压制系统的调整大小相关消息,窗口始终认为用户还在拖动,因此卡在调整模式,甚至ESC键的消息也得不到处理。
对比RDW_INTERNALPAINT:它只是标记窗口需要重绘,将WM_PAINT加入消息队列,不会立即执行重绘,系统的模态循环可以正常处理鼠标消息,所以调整大小行为正常。
问题2:如何在强制立即重绘的同时保持正常的调整大小行为?
可以通过检测窗口是否处于调整/移动状态,动态切换重绘策略:
- 在窗口过程中监听
WM_ENTERSIZEMOVE和WM_EXITSIZEMOVE消息,记录当前是否处于调整大小或移动窗口的状态。 - 主循环里根据这个状态选择重绘方式:非调整状态下用立即重绘,调整状态下改用异步重绘,避免干扰系统的模态循环。
修改后的示例代码:
// clang -std=c23 -o win_resize_stuck.exe -luser32 -lgdi32 ./win_resize_stuck.c #include <stdio.h> #include <assert.h> #include <string.h> #include <windows.h> #include <windowsx.h> static BOOL is_resizing = FALSE; // 标记是否处于窗口调整/移动状态 static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CREATE: break; case WM_CLOSE: PostQuitMessage(0); break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); // 这里添加你的重绘逻辑,比如绘制背景或内容 EndPaint(hWnd, &ps); break; } case WM_ENTERSIZEMOVE: is_resizing = TRUE; // 进入调整/移动状态 break; case WM_EXITSIZEMOVE: is_resizing = FALSE; // 退出调整/移动状态 break; default: break; } return DefWindowProc(hWnd, uMsg, wParam, lParam); } HWND window; #define WIN_CLASS_NAME "tmp" #define APP_NAME "tmp" static void create_window() { WNDCLASSEX win_class; win_class.cbSize = sizeof(WNDCLASSEX); win_class.style = CS_HREDRAW | CS_VREDRAW; win_class.lpfnWndProc = WndProc; win_class.cbClsExtra = 0; win_class.cbWndExtra = 0; win_class.hInstance = GetModuleHandle(NULL); win_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); win_class.hCursor = LoadCursor(NULL, IDC_ARROW); win_class.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); win_class.lpszMenuName = NULL; win_class.lpszClassName = WIN_CLASS_NAME; win_class.hIconSm = LoadIcon(NULL, IDI_WINLOGO); if (!RegisterClassEx(&win_class)) { fprintf(stderr, "Unexpected error trying to start the application!\n"); fflush(stderr); exit(1); } RECT wr = {0, 0, 500, 500}; AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE); window = CreateWindowEx(0, WIN_CLASS_NAME, APP_NAME, WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_SYSMENU, 100, 100, wr.right - wr.left, wr.bottom - wr.top, NULL, NULL, GetModuleHandle(NULL), NULL); } int main(int argc, const char** argv) { (void)argc; (void)argv; create_window(); MSG msg; BOOL done = FALSE; while (!done) { while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) { done = TRUE; break; } TranslateMessage(&msg); DispatchMessage(&msg); } // 根据窗口状态选择重绘方式 if (is_resizing) { // 调整状态下用异步重绘,不干扰系统循环 RedrawWindow(window, NULL, NULL, RDW_INTERNALPAINT); } else { // 非调整状态下强制立即重绘 RedrawWindow(window, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW); } } return 0; }
关键说明:
WM_ENTERSIZEMOVE:当用户开始拖动窗口边框或标题栏时触发,标记窗口进入调整/移动状态。WM_EXITSIZEMOVE:当用户释放鼠标,结束调整/移动时触发,取消标记。- 主循环优先处理完所有消息再执行重绘,避免消息堆积;同时根据状态切换重绘策略,既满足立即重绘的需求,又不干扰系统的调整大小逻辑。
另外,WM_PAINT中必须调用BeginPaint和EndPaint,否则系统会持续发送WM_PAINT消息,造成额外性能开销。
内容的提问来源于stack exchange,提问作者user18490
相关产品推荐
相关产品推荐

