WinAPI中WS_POPUP窗口左侧调整大小异常问题排查
WS_POPUP窗口左侧边缘调整异常问题排查与修复
我基于WS_POPUP样式实现了类似WS_OVERLAPPED的窗口调整大小功能,右侧边缘调整正常,但左侧存在异常:拖动左边缘时,鼠标光标与窗口边缘错位,窗口边缘闪烁,非客户区按钮出现双重位置;而拖动右边缘时,窗口边缘能紧跟光标移动(和资源管理器窗口调整效果一致)。不确定问题出在宽/位置计算逻辑还是其他代码部分,相关代码如下:
原WM_MOUSEMOVE代码
case WM_MOUSEMOVE: { if (dragging) { POINT currentPoint = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; GetCursorPos(¤tPoint); ScreenToClient(hwnd, ¤tPoint); // Offset for THE RIGHT SIDE OF THE WINDOW int deltaXRight = currentPoint.x - startPoint.x; // New width OF THE RIGHT SIDE OF THE WINDOW int newWidth = startRect.right - startRect.left + deltaXRight; // Maybe I'm not doing the calculation correctly // Offset for the left SIDE of the WINDOW int deltaXLeft = startPoint.x - currentPoint.x; // New width OF THE LEFT SIDE OF THE WINDOW int newWidthLeft = startRect.right - startRect.left - deltaXRight; // New position of the left side int newCurrentWidthLeft = startRect.left + deltaXRight; if (startPoint.x > startRect.right - startRect.left - BORDERWIDTH) { SetWindowPos(hwnd, NULL, 0, 0, newWidth, startRect.bottom - startRect.top, SWP_NOZORDER | SWP_NOMOVE); } else if (startPoint.x < BORDERWIDTH && newWidthLeft > MIN_WIDTH) { SetWindowPos(hwnd, NULL, newCurrentWidthLeft, startRect.top, newWidthLeft, startRect.bottom - startRect.top, SWP_NOZORDER | SWP_NOACTIVATE); } } break; }
其余相关代码
case WM_NCHITTEST: { POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; ScreenToClient(hwnd, &pt); RECT closeButton = GetCloseButtonRect(hwnd); RECT maximizeButton = GetMaximizeButtonRect(hwnd); RECT minimizeButton = GetMinimizeButtonRect(hwnd); if (PtInRect(&closeButton, pt)) { return HTCLOSE; } else if (PtInRect(&maximizeButton, pt)) { return HTMAXBUTTON; } else if (PtInRect(&minimizeButton, pt)) { return HTMINBUTTON; } // 如果光标在标题栏,返回HTCAPTION if (pt.y < GetTitleBarHeight()) { return HTCAPTION; // 指明光标在标题栏 } LRESULT hit = DefWindowProc(hwnd, message, wParam, lParam); if (hit == HTCLIENT) { RECT rc; GetClientRect(hwnd, &rc); if (pt.x > rc.right - 10) { return HTRIGHT; // 右边缘 } else if (pt.x < 10) { return HTLEFT; // 左边缘 } } return hit; } case WM_NCLBUTTONDOWN: { POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; ScreenToClient(hwnd, &pt); RECT closeButton = GetCloseButtonRect(hwnd); RECT maximizeButton = GetMaximizeButtonRect(hwnd); RECT minimizeButton = GetMinimizeButtonRect(hwnd); if (PtInRect(&closeButton, pt)) { PostMessage(hwnd, WM_CLOSE, 0, 0); } if (PtInRect(&maximizeButton, pt)) { ShowWindow(hwnd, IsZoomed(hwnd) ? SW_RESTORE : SW_MAXIMIZE); } if (PtInRect(&minimizeButton, pt)) { SendMessage(hwnd, WM_SYSCOMMAND, SC_MINIMIZE, 0); } if (pt.y < GetTitleBarHeight()) { // 开始拖动窗口 PostMessage(hwnd, WM_SYSCOMMAND, SC_MOVE | HTCAPTION, lParam); return HTCAPTION; } dragging = true; startPoint.x = GET_X_LPARAM(lParam); startPoint.y = GET_Y_LPARAM(lParam); GetWindowRect(hwnd, &startRect); ScreenToClient(hwnd, &startPoint); SetCapture(hwnd); // 捕获鼠标 return 0; } case WM_LBUTTONUP: { // 释放左键时停止调整窗口大小 if (dragging) { dragging = false; ReleaseCapture(); // 释放鼠标 } break; }

问题根源
- 坐标系统混用:
startPoint是客户端坐标,startRect是屏幕坐标,两者直接运算导致位置计算错位。 - 左边缘逻辑错误:
newCurrentWidthLeft和newWidthLeft的推导逻辑混乱,导致窗口位置与宽度不匹配,引发闪烁和按钮错位。 - 边缘判断不严谨:通过
startPoint.x判断边缘的逻辑不可靠,未明确记录当前拖动的边缘类型。
修复方案
1. 添加全局变量记录拖动边缘
int dragEdge = 0; // 0:无拖动, 1:左边缘, 2:右边缘
2. 修改WM_NCLBUTTONDOWN,明确记录拖动边缘
case WM_NCLBUTTONDOWN: { POINT ptScreen = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; POINT ptClient = ptScreen; ScreenToClient(hwnd, &ptClient); // 按钮判断代码保持不变 RECT closeButton = GetCloseButtonRect(hwnd); RECT maximizeButton = GetMaximizeButtonRect(hwnd); RECT minimizeButton = GetMinimizeButtonRect(hwnd); if (PtInRect(&closeButton, ptClient)) { PostMessage(hwnd, WM_CLOSE, 0, 0); } if (PtInRect(&maximizeButton, ptClient)) { ShowWindow(hwnd, IsZoomed(hwnd) ? SW_RESTORE : SW_MAXIMIZE); } if (PtInRect(&minimizeButton, ptClient)) { SendMessage(hwnd, WM_SYSCOMMAND, SC_MINIMIZE, 0); } if (ptClient.y < GetTitleBarHeight()) { PostMessage(hwnd, WM_SYSCOMMAND, SC_MOVE | HTCAPTION, lParam); return HTCAPTION; } // 判断并记录拖动边缘 RECT rcClient; GetClientRect(hwnd, &rcClient); dragEdge = 0; if (ptClient.x < 10) { dragEdge = 1; } else if (ptClient.x > rcClient.right - 10) { dragEdge = 2; } if (dragEdge != 0) { dragging = true; startPoint = ptScreen; // 保存屏幕坐标,避免转换错误 GetWindowRect(hwnd, &startRect); SetCapture(hwnd); } return 0; }
3. 修正WM_MOUSEMOVE的计算逻辑
case WM_MOUSEMOVE: { if (dragging && dragEdge != 0) { POINT currentPoint; GetCursorPos(¤tPoint); if (dragEdge == 2) { // 右边缘调整 int deltaX = currentPoint.x - startPoint.x; int newWidth = startRect.right - startRect.left + deltaX; if (newWidth < MIN_WIDTH) newWidth = MIN_WIDTH; SetWindowPos(hwnd, NULL, startRect.left, startRect.top, newWidth, startRect.bottom - startRect.top, SWP_NOZORDER | SWP_NOACTIVATE); } else if (dragEdge == 1) { // 左边缘调整 int deltaX = currentPoint.x - startPoint.x; int newLeft = startRect.left + deltaX; int newWidth = startRect.right - newLeft; if (newWidth < MIN_WIDTH) { newLeft = startRect.right - MIN_WIDTH; newWidth = MIN_WIDTH; } SetWindowPos(hwnd, NULL, newLeft, startRect.top, newWidth, startRect.bottom - startRect.top, SWP_NOZORDER | SWP_NOACTIVATE); } } break; }
4. WM_LBUTTONUP中重置拖动状态
case WM_LBUTTONUP: { if (dragging) { dragging = false; dragEdge = 0; ReleaseCapture(); } break; }
修复说明
- 统一坐标系统:全程使用屏幕坐标计算,避免客户端/屏幕坐标转换导致的误差。
- 明确边缘类型:拖动开始时记录边缘,移动时直接按对应逻辑处理,避免重复判断。
- 修正左边缘计算:先计算新的左边界,再推导窗口宽度,确保位置与宽度匹配,解决错位和闪烁问题。
内容的提问来源于stack exchange,提问作者Zend
相关产品推荐
相关产品推荐

