You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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(&currentPoint);
        ScreenToClient(hwnd, &currentPoint);

        // 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;
}

窗口调整异常效果


问题根源

  1. 坐标系统混用:startPoint是客户端坐标,startRect是屏幕坐标,两者直接运算导致位置计算错位。
  2. 左边缘逻辑错误:newCurrentWidthLeft和newWidthLeft的推导逻辑混乱,导致窗口位置与宽度不匹配,引发闪烁和按钮错位。
  3. 边缘判断不严谨:通过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(&currentPoint);

        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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 18:24:50