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

如何修复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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 04:42:42