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

DPI感知应用中通用控件字体不随DPI缩放的问题排查与解决

DPI感知应用中通用控件字体不缩放问题解决

问题背景

在DPI感知应用中,除通用控件(common controls)的字体外,所有元素均能正确缩放。当系统DPI为96(100%缩放设置)时,按钮显示正常;切换至175%缩放设置后,按钮尺寸已正确调整,但字体大小未发生变化。

相关代码如下:

WM_DPICHANGED_AFTERPARENT处理函数

// Handler for WM_DPICHANGED_AFTERPARENT
bool ChildWindow::OnResizeForDpiAfter() noexcept
{
    // TODO: This won't change font DPI, ex. of controls
    const int dpiX = ToPixelsX(mSizeRect.left);
    const int dpiY = ToPixelsY(mSizeRect.top);
    const int dpiWidth = ToPixelsX(mSizeRect.right);
    const int dpiHeight = ToPixelsY(mSizeRect.bottom);

    return SetWindowPos(mhWnd, HWND_TOP,
        dpiX, dpiY, dpiWidth, dpiHeight,
        SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOACTIVATE) != FALSE;
}

DIP尺寸存储与转换逻辑

/** Window size and position at 96 DPI */
D2D1_RECT_U mSizeRect;

// ToPixelsX is defined as follows
template<typename ReturnType = int, typename Param>
[[nodiscard]] inline ReturnType ToPixelsX(Param dips) const noexcept
{
    // pixels = (DIP × DPI) / 96
    // pixels = DIP x (DPI / 96)
    // mDpiScale = DPI / 96.0
    return static_cast<ReturnType>(std::ceil(dips * mDpiScale.x));
}

通用控件初始化代码

INITCOMMONCONTROLSEX ctrl{ };
ctrl.dwSize = sizeof(INITCOMMONCONTROLSEX);
ctrl.dwICC = ICC_STANDARD_CLASSES | ICC_LISTVIEW_CLASSES;

InitCommonControlsEx(&ctrl);

按钮以WS_CHILD | BS_PUSHBUTTON样式创建,通过SetWindowTextW(hWnd, L"File")设置固定文本。

核心疑问

通用控件尺寸随DPI调整后,其标题字体大小保持不变。为何字体不缩放?如何实现字体随DPI调整?
注:通用控件由GDI而非DirectX管理,根据微软文档,GDI字体应随当前DPI设置缩放。


原因分析

  1. 默认字体未动态适配DPI:通用控件创建时使用系统默认字体,该字体基于创建时的DPI生成,后续DPI变化时不会自动更新。当前代码仅调整了控件尺寸,未处理字体的DPI适配逻辑。
  2. WM_DPICHANGED_AFTERPARENT处理不完整:现有处理函数仅负责窗口位置与大小调整,缺失字体更新的关键步骤。

解决方案

1. 在DPI变化时重新生成适配字体

修改OnResizeForDpiAfter函数,新增字体更新逻辑:

bool ChildWindow::OnResizeForDpiAfter() noexcept
{
    // 原有尺寸调整逻辑
    const int dpiX = ToPixelsX(mSizeRect.left);
    const int dpiY = ToPixelsY(mSizeRect.top);
    const int dpiWidth = ToPixelsX(mSizeRect.right);
    const int dpiHeight = ToPixelsY(mSizeRect.bottom);

    // 新增:创建适配当前DPI的字体并设置给控件
    LOGFONTW logFont{};
    // 获取系统默认标题字体的参数
    SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(LOGFONTW), &logFont, 0);
    
    // 按当前DPI缩放字体高度(负号表示以像素为单位,使用字符单元格高度)
    logFont.lfHeight = -MulDiv(logFont.lfHeight, static_cast<int>(mDpiScale.x * 96), USER_DEFAULT_SCREEN_DPI);
    
    HFONT hNewFont = CreateFontIndirectW(&logFont);
    if (hNewFont)
    {
        // 获取控件当前使用的旧字体
        HFONT hOldFont = reinterpret_cast<HFONT>(SendMessageW(mhWnd, WM_GETFONT, 0, 0));
        // 设置新字体并立即重绘
        SendMessageW(mhWnd, WM_SETFONT, reinterpret_cast<WPARAM>(hNewFont), TRUE);
        // 释放旧字体资源(避免内存泄漏)
        if (hOldFont && hOldFont != hNewFont)
        {
            DeleteObject(hOldFont);
        }
    }

    return SetWindowPos(mhWnd, HWND_TOP,
        dpiX, dpiY, dpiWidth, dpiHeight,
        SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOACTIVATE) != FALSE;
}

2. 控件创建时初始化适配字体

在按钮创建完成后,立即设置一次适配初始DPI的字体,避免初始状态字体不匹配:

// 按钮创建代码执行完成后添加
LOGFONTW logFont{};
SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(LOGFONTW), &logFont, 0);
logFont.lfHeight = -MulDiv(logFont.lfHeight, static_cast<int>(mDpiScale.x * 96), USER_DEFAULT_SCREEN_DPI);
HFONT hFont = CreateFontIndirectW(&logFont);
if (hFont)
{
    SendMessageW(hButtonWnd, WM_SETFONT, reinterpret_cast<WPARAM>(hFont), TRUE);
}

关键说明

  • MulDiv函数:用于精确计算缩放后的字体高度,避免浮点运算误差,确保字体大小与DPI缩放比例严格匹配。
  • WM_SETFONT消息:最后一个参数设为TRUE,会触发控件立即重绘,让新字体生效。
  • 资源管理:每次更新字体时必须释放旧字体资源,防止GDI资源泄漏。

内容的提问来源于stack exchange,提问作者metablaster

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 05:37:09