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设置缩放。
原因分析
- 默认字体未动态适配DPI:通用控件创建时使用系统默认字体,该字体基于创建时的DPI生成,后续DPI变化时不会自动更新。当前代码仅调整了控件尺寸,未处理字体的DPI适配逻辑。
- 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
相关产品推荐
相关产品推荐

