在高 DPI Windows 应用程序中使用 DEFAULT_GUI_FONT

Using DEFAULT_GUI_FONT in high DPI Windows application

我有一个 Windows 应用程序,我希望它在高 DPI 显示器上看起来不错。该应用程序在很多地方使用 DEFAULT_GUI_FONT,并且以这种方式创建的字体无法正确缩放。

有什么简单的方法可以解决这个问题而不是太痛苦吗?

您需要获取 NONCLIENTMETRICS by SystemParametersInfo(SPI_GETNONCLIENTMETRICS,) and then use it LOGFONT data, for create self font. or you can query for SystemParametersInfo(SPI_GETICONTITLELOGFONT) 并使用它

不同用途的推荐字体可以从NONCLIENTMETRICS结构中获取。

对于自动 DPI 缩放字体(Windows 10 1607+,必须是每个显示器的 DPI 感知):

// Your window's handle
HWND window;

// Get the DPI for which your window should scale to
UINT dpi = GetDpiForWindow(window);

// Obtain the recommended fonts, which are already correctly scaled for the current DPI
NONCLIENTMETRICSW non_client_metrics;

if (!SystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, sizeof(non_client_metrics), &non_client_metrics, 0, dpi)
{
    // Error handling
}

// Create an appropriate font(s)
HFONT message_font = CreateFontIndirectW(&non_client_metrics.lfMessageFont);

if (!message_font)
{
    // Error handling
}

对于较旧的 Windows 版本,您可以使用系统范围的 DPI 并手动缩放字体(Windows 7+,必须是系统 DPI 感知):

// Your window's handle
HWND window;

// Obtain the recommended fonts, which are already correctly scaled for the current DPI
NONCLIENTMETRICSW non_client_metrics;

if (!SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(non_client_metrics), &non_client_metrics, 0)
{
    // Error handling
}

// Get the system-wide DPI
HDC hdc = GetDC(nullptr);

if (!hdc)
{
    // Error handling
}

UINT dpi = GetDeviceCaps(hdc, LOGPIXELSY);

ReleaseDC(nullptr, hdc);

// Scale the font(s)
constexpr UINT font_size = 12;

non_client_metrics.lfMessageFont.lfHeight = -((font_size * dpi) / 72);

// Create the appropriate font(s)
HFONT message_font = CreateFontIndirectW(&non_client_metrics.lfMessageFont);

if (!message_font)
{
    // Error handling
}

NONCLIENTMETRICS 中还有许多其他字体。确保根据您的目的选择正确的。

您应该在 application manifest as described here 中设置 DPI 感知级别以获得最佳兼容性。

.NET 框架中的 WinForms 通过从像素(这是单位GDI 字体本机使用)到点(GDI+ 首选)。使用点绘制文本意味着渲染文本的物理大小取决于显示器 DPI 设置。

System.Drawing.Font.SizeInPoints:

float emHeightInPoints;
                    
IntPtr screenDC = UnsafeNativeMethods.GetDC(NativeMethods.NullHandleRef);

try {
    using( Graphics graphics = Graphics.FromHdcInternal(screenDC)){
        float pixelsPerPoint      = (float) (graphics.DpiY / 72.0);
        float lineSpacingInPixels = this.GetHeight(graphics);
        float emHeightInPixels    = lineSpacingInPixels * FontFamily.GetEmHeight(Style)  / FontFamily.GetLineSpacing(Style);
        
        emHeightInPoints    = emHeightInPixels / pixelsPerPoint;
    }
}
finally {
    UnsafeNativeMethods.ReleaseDC(NativeMethods.NullHandleRef, new HandleRef(null, screenDC));
}

return emHeightInPoints;

显然你不能直接使用它,因为它是 C#。但除此之外,this article 建议您假设 96 dpi 设计缩放像素尺寸,并使用 GetDpiForWindow 确定实际 DPI。请注意,上面公式中的“72”与显示器 DPI 设置无关,它来自于 .NET 喜欢使用以点而不是像素指定的字体(否则只需将 LOGFONT 的高度缩放 ​​DPIy/96).

This site 提出类似的建议,但 GetDpiForMonitor.

我不能确定根据某些 DPI 相关因素手动缩放字体大小的一般方法是否是缩放字体的可靠且面向未来的方法(这似乎是缩放非字体 GUI 元素)。然而,由于 .NET 基本上也只是根据某种 DPI 值计算一些魔法因子,所以这可能是一个很好的猜测。

此外,您需要缓存 HFONTHFONT - LOGFONT 转换不可忽略。


另见(参考):

WinForms 使用 GetStockObject(DEFAULT_GUI_FONT) 获取其默认值(尽管有一些例外,大部分已过时):

IntPtr handle = UnsafeNativeMethods.GetStockObject(NativeMethods.DEFAULT_GUI_FONT);        
try {
    Font fontInWorldUnits = null;

    // SECREVIEW : We know that we got the handle from the stock object,
    //           : so this is always safe.
    //
    IntSecurity.ObjectFromWin32Handle.Assert();
    try {
        fontInWorldUnits = Font.FromHfont(handle);
    }
    finally {
        CodeAccessPermission.RevertAssert();
    }

    try{
        defaultFont = FontInPoints(fontInWorldUnits);
    }
    finally{
        fontInWorldUnits.Dispose();
    }
}
catch (ArgumentException) {
}

https://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/SystemFonts.cs,355

将HFONT转换为GDI+,然后使用FontInPoints对这样检索到的GDI+字体进行转换:

private static Font FontInPoints(Font font) {
    return new Font(font.FontFamily, font.SizeInPoints, font.Style, GraphicsUnit.Point, font.GdiCharSet, font.GdiVerticalFont);
}

https://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/SystemFonts.cs,452

SizeInPointsgetter的内容已经在上面列出了。

https://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/Advanced/Font.cs,992