在垂直调整窗口大小时出现抖动
拖动窗口底部边缘调整大小时,会出现抖动。不过,通过拖动右边缘来调整大小时,这种抖动不会出现。应该如何解决?
我的窗口里面有一个滚动容器,里面放着所有子控件。为了演示,我添加了两个大按钮。
以下给出能够重现该问题的完整最小代码。
#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++),将上面的代码复制并粘贴到主文件中并运行。
解决方案
在你的清单文件中添加 dpiAwareness 与 dpiAware 设置:
<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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。