如何修复Per-Monitor v2 DPI缩放时Win32菜单栏下方的未绘制像素行?
如何绕过Windows Per-Monitor v2 DPI下菜单栏残留像素的Bug?
在自研应用中发现菜单栏下方残留未绘制(透明)像素的问题,同时在Windows Media Player中也能复现该现象,推测这是Windows系统层面的Bug。以下是问题复现步骤及可行的绕过方案:
复现步骤
- 在Windows 11 24H2系统中,初始设置150%文本缩放比例。
- 运行程序,旧版Windows Media Player初始界面:

- 将文本缩放比例修改为225%。
- 再次查看Windows Media Player,界面出现异常:

可见菜单栏下方残留一行未绘制像素,不符合预期,推测所有支持Per-Monitor v2 DPI的程序都可能遇到该问题。
本地复现代码
#include <Windows.h> #pragma comment(lib, "user32") DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); WINUSERAPI BOOL WINAPI SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT); #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4) int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) { ((decltype(&SetProcessDpiAwarenessContext))GetProcAddress( LoadLibrary(TEXT("user32.dll")), "SetProcessDpiAwarenessContext") )(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); HMENU hMenu = CreateMenu(); AppendMenu(hMenu, MF_POPUP, (UINT_PTR)CreatePopupMenu(), TEXT("File")); HWND hwnd = CreateWindowEx(0, TEXT("#32770"), TEXT("Title"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nCmdShow, NULL, hMenu, hInstance, NULL); MSG Msg; while (GetMessage(&Msg, NULL, 0, 0) > 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam; }
可行的绕过方案
1. 响应DPI变化时强制重绘整个窗口
自定义窗口过程,在接收到WM_DPICHANGED消息时,调整窗口大小后强制重绘整个窗口(包括菜单栏区域):
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_DPICHANGED: { RECT* pNewRect = (RECT*)lParam; SetWindowPos(hwnd, NULL, pNewRect->left, pNewRect->top, pNewRect->right - pNewRect->left, pNewRect->bottom - pNewRect->top, SWP_NOZORDER | SWP_NOACTIVATE); // 强制重绘整个窗口及子控件 RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN); return 0; } case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } }
注意:需替换默认的#32770窗口过程,因为默认窗口过程未正确处理DPI变化后的重绘逻辑。
2. 手动填充菜单栏下方残留区域
在WM_PAINT消息中,获取菜单栏位置并手动填充其下方的残留像素区域:
case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); MENUBARINFO mbi = { sizeof(MENUBARINFO) }; if (GetMenuBarInfo(hwnd, OBJID_MENU, 0, &mbi)) { RECT fillRect = { mbi.rcBar.left, mbi.rcBar.bottom, mbi.rcBar.right, mbi.rcBar.bottom + 1 }; HBRUSH hBrush = (HBRUSH)GetClassLongPtr(hwnd, GCLP_HBRBACKGROUND); FillRect(hdc, &fillRect, hBrush); } EndPaint(hwnd, &ps); return 0; }
3. 切换到Per-Monitor v1 DPI感知(临时方案)
如果无需Per-Monitor v2的特性,可切换到v1模式,该模式下此Bug大概率不会出现:
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3) // 替换SetProcessDpiAwarenessContext的参数 ((decltype(&SetProcessDpiAwarenessContext))GetProcAddress( LoadLibrary(TEXT("user32.dll")), "SetProcessDpiAwarenessContext") )(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
缺点是v1不支持子窗口独立DPI感知等v2专属特性。
内容的提问来源于stack exchange,提问作者user541686
相关产品推荐
相关产品推荐

