在垂直调整窗口大小时出现抖动

后端开发 2026-07-08

拖动窗口底部边缘调整大小时,会出现抖动。不过,通过拖动右边缘来调整大小时,这种抖动不会出现。应该如何解决?

我的窗口里面有一个滚动容器,里面放着所有子控件。为了演示,我添加了两个大按钮。

以下给出能够重现该问题的完整最小代码。

#include <windows.h>
#include <windowsx.h>

// ----------------------------------------
// BUTTON
// ----------------------------------------

HWND CreateButton(HWND hwndParent, HINSTANCE hInstance)
{
    return ::CreateWindowExW(
        0,
        L"BUTTON",
        L"PUSHBUTTON",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        10,
        10,
        360,
        360,
        hwndParent,
        reinterpret_cast<HMENU>(1001),
        hInstance,
        nullptr
    );
}

// ----------------------------------------
// DEFPUSHBUTTON
// ----------------------------------------

HWND CreateDefPushButton(HWND hwndParent, HINSTANCE hInstance)
{
    return ::CreateWindowExW(
        0,
        L"BUTTON",
        L"DEFPUSHBUTTON",
        WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
        375,
        10,
        360,
        360,
        hwndParent,
        reinterpret_cast<HMENU>(1002),
        hInstance,
        nullptr
    );
}

// ----------------------------------------
// CONTAINER WINDOW
// ----------------------------------------

PCWSTR CONTAINER_CLASS_NAME = L"ContainerWindowClass";
int xScrollPos = 0;
int yScrollPos = 0;

SIZE GetContentSize(HWND hContainer)
{
    SIZE contentSize = { 0, 0 };

    HWND hChild = GetWindow(hContainer, GW_CHILD);

    while (hChild)
    {
        RECT rc;
        GetWindowRect(hChild, &rc);

        MapWindowPoints(
            HWND_DESKTOP,
            hContainer,
            (LPPOINT)&rc,
            2
        );

        rc.left += xScrollPos;
        rc.right += xScrollPos;

        rc.top += yScrollPos;
        rc.bottom += yScrollPos;

        if (rc.right > contentSize.cx)
            contentSize.cx = rc.right;

        if (rc.bottom > contentSize.cy)
            contentSize.cy = rc.bottom;

        hChild = GetWindow(hChild, GW_HWNDNEXT);
    }

    contentSize.cx += 20;
    contentSize.cy += 20;

    return contentSize;
}

LRESULT CALLBACK ContainerProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_SIZE:
    {
        int clientWidth = LOWORD(lParam);
        int clientHeight = HIWORD(lParam);
        SIZE contentSize = GetContentSize(hWnd);

        SCROLLINFO si{};
        si.cbSize = sizeof(si);
        si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;

        int oldY = yScrollPos;
        int oldX = xScrollPos;

        int maxPos = max(0, contentSize.cy - clientHeight);
        yScrollPos = min(yScrollPos, maxPos);

        int maxPosHorz = max(0, contentSize.cx - clientWidth);
        xScrollPos = min(xScrollPos, maxPosHorz);

        if (oldY != yScrollPos || oldX != xScrollPos)
        {
            ScrollWindowEx(
                hWnd,
                oldX - xScrollPos,
                oldY - yScrollPos,
                NULL,
                NULL,
                NULL,
                NULL,
                SW_INVALIDATE | SW_SCROLLCHILDREN
            );
        }

        si.nMin = 0;
        si.nMax = contentSize.cy;
        si.nPage = clientHeight;
        si.nPos = yScrollPos;
        SetScrollInfo(hWnd, SB_VERT, &si, TRUE);

        si.nMin = 0;
        si.nMax = contentSize.cx;
        si.nPage = clientWidth;
        si.nPos = xScrollPos;
        SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);

        return 0;
    }

    case WM_VSCROLL:
    {
        SCROLLINFO si = { 0 };
        si.cbSize = sizeof(si);
        si.fMask = SIF_ALL;
        GetScrollInfo(hWnd, SB_VERT, &si);

        int originalPos = si.nPos;

        switch (LOWORD(wParam))
        {
        case SB_TOP:           si.nPos = si.nMin; break;
        case SB_BOTTOM:        si.nPos = si.nMax; break;
        case SB_LINEUP:        si.nPos -= 10; break;
        case SB_LINEDOWN:      si.nPos += 10; break;
        case SB_PAGEUP:        si.nPos -= si.nPage; break;
        case SB_PAGEDOWN:      si.nPos += si.nPage; break;
        case SB_THUMBTRACK:    si.nPos = si.nTrackPos; break;
        }

        si.fMask = SIF_POS;
        SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
        GetScrollInfo(hWnd, SB_VERT, &si);

        if (si.nPos != originalPos)
        {
            int yDelta = originalPos - si.nPos;
            yScrollPos = si.nPos;

            ScrollWindowEx(hWnd, 0, yDelta, NULL, NULL, NULL, NULL, SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN);
            UpdateWindow(hWnd);
        }

        return 0;
    }

    case WM_HSCROLL:
    {
        SCROLLINFO si = { 0 };
        si.cbSize = sizeof(si);
        si.fMask = SIF_ALL;
        GetScrollInfo(hWnd, SB_HORZ, &si);

        int originalPos = si.nPos;

        switch (LOWORD(wParam))
        {
        case SB_LEFT:            si.nPos = si.nMin; break;
        case SB_RIGHT:           si.nPos = si.nMax; break;
        case SB_LINELEFT:        si.nPos -= 10; break;
        case SB_LINERIGHT:       si.nPos += 10; break;
        case SB_PAGELEFT:        si.nPos -= si.nPage; break;
        case SB_PAGERIGHT:       si.nPos += si.nPage; break;
        case SB_THUMBTRACK:      si.nPos = si.nTrackPos; break;
        }

        si.fMask = SIF_POS;
        SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);
        GetScrollInfo(hWnd, SB_HORZ, &si);

        if (si.nPos != originalPos)
        {
            int xDelta = originalPos - si.nPos;
            xScrollPos = si.nPos;

            ScrollWindowEx(hWnd, xDelta, 0, NULL, NULL, NULL, NULL, SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN);
            UpdateWindow(hWnd);
        }

        return 0;
    }

    case WM_MOUSEWHEEL:
    {
        int zDelta = (short)HIWORD(wParam);

        if (wParam & MK_SHIFT) {
            WORD scrollCode = (zDelta > 0) ? SB_LINELEFT : SB_LINERIGHT;
            SendMessage(hWnd, WM_HSCROLL, MAKEWPARAM(scrollCode, 0), 0);
        }
        else {
            WORD scrollCode = (zDelta > 0) ? SB_LINEUP : SB_LINEDOWN;
            SendMessage(hWnd, WM_VSCROLL, MAKEWPARAM(scrollCode, 0), 0);
        }
        return 0;
    }


    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = ::BeginPaint(hWnd, &ps);
        ::FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
        ::EndPaint(hWnd, &ps);
        return 0;
    }
    }
    return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
}

ATOM RegisterContainerWindowClass(HINSTANCE hInstance)
{
    WNDCLASSW wc = {};
    wc.lpfnWndProc = ContainerProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CONTAINER_CLASS_NAME;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    return ::RegisterClassW(&wc);
}

HWND CreateContainerWindow(HWND hwndParent, HINSTANCE hInstance)
{
    static bool isContainerClassRegistered = false;
    if (!isContainerClassRegistered)
    {
        ::RegisterContainerWindowClass(hInstance);
        isContainerClassRegistered = true;
    }

    return ::CreateWindowExW(
        0, CONTAINER_CLASS_NAME, nullptr,
        WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
        WS_VSCROLL | WS_HSCROLL,
        0, 0, 875, 480,
        hwndParent, NULL, hInstance, NULL
    );
}

// ----------------------------------------
// MAIN WINDOW
// ----------------------------------------

PCWSTR CLASS_NAME = L"EmptyWindowClass";

HWND hWnd;
HWND hContainer;

LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_SIZE:
        {
            int parentWidth = LOWORD(lParam);
            int parentHeight = HIWORD(lParam);
            MoveWindow(
                hContainer,
                0, 0,
                parentWidth,
                parentHeight,
                TRUE
            );
            return 0;
        }

        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = ::BeginPaint(hWnd, &ps);
            ::FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
            ::EndPaint(hWnd, &ps);
            return 0;
        }

        case WM_DESTROY:
        {
            ::PostQuitMessage(0);
            return 0;
        }
    }
    return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
}

ATOM RegisterMainWindowClass(HINSTANCE hInstance)
{
    WNDCLASSW wc = {};
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    wc.hCursor = ::LoadCursorW(NULL, MAKEINTRESOURCEW((ULONG_PTR)IDC_ARROW));
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = nullptr;
    return ::RegisterClassW(&wc);
}

HWND CreateMainWindow(HWND hwndParent, HINSTANCE hInstance)
{
    static bool isClassRegistered = false;
    if (!isClassRegistered)
    {
        ::RegisterMainWindowClass(hInstance);
        isClassRegistered = true;
    }

    return ::CreateWindowExW(
        0, CLASS_NAME, L"Empty Window",
        WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,
        CW_USEDEFAULT, CW_USEDEFAULT, 875, 480,
        NULL, NULL, hInstance, NULL
    );
}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow)
{
    hWnd = CreateMainWindow(nullptr, hInstance);
    hContainer = CreateContainerWindow(hWnd, hInstance);

    HWND hButton = CreateButton(hContainer, hInstance);
    HWND hDefPushButton = CreateDefPushButton(hContainer, hInstance);

    ::ShowWindow(hWnd, nCmdShow);
    ::UpdateWindow(hWnd);

    MSG msg = { 0 };
    while (::GetMessageW(&msg, NULL, 0, 0))
    {
        ::TranslateMessage(&msg);
        ::DispatchMessageW(&msg);
    }
    return 0;
}

更新: 环境 - Windows 11。

要在本地重现这个问题,你可以在Visual Studio中创建一个空项目(类型:Windows桌面应用,语言:C++),将上面的代码复制并粘贴到主文件中并运行。

解决方案

在你的清单文件中添加 dpiAwarenessdpiAware 设置:

  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
        PerMonitorV2
      </dpiAwareness>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
        true/pm
      </dpiAware>
    </windowsSettings>
  </application>

并使用 cl 以及标志 /MANIFEST:EMBED /MANIFESTINPUT:app.manifest 重新构建应用程序。

我的完整清单如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">

  <dependency>
    <dependentAssembly>
      <assemblyIdentity
        type="win32"
        name="Microsoft.Windows.Common-Controls"
        version="6.0.0.0"
        processorArchitecture="*"
        publicKeyToken="6595b64144ccf1df"
        language="*"/>
    </dependentAssembly>
  </dependency>

  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
        PerMonitorV2
      </dpiAwareness>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
        true/pm
      </dpiAware>
    </windowsSettings>
  </application>

</assembly>

Microsoft文档:https://learn.microsoft.com/en-us/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章