【问题标题】:Using DEFAULT_GUI_FONT in high DPI Windows application在高 DPI Windows 应用程序中使用 DEFAULT_GUI_FONT
【发布时间】:2017-03-13 10:36:06
【问题描述】:

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

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

【问题讨论】:

  • Stop using DEFAULT_GUI_FONT. 你对任何位图字体都不走运;对不起。
  • @andlabs,DEFAULT_GUI_FONT 使用 Tahoma 作为默认字体,这是一种矢量字体。当我使用一台显示器并为我的应用程序设置 DPI-Awareness 时,同时通过 Windows 分辨率设置更改显示器上的 DPI 时,使用 DEFAULT_GUI_FONT 获得的字体会改变它的大小。
  • 这不是真的。这是一种位图字体。 blogs.msdn.microsoft.com/oldnewthing/20050707-00/?p=35013真的,放弃吧。
  • 它可能是 Tahoma,但出于兼容性原因,它是 Tahoma 的位图版本,所以无论如何你都会得到像素化(即使在高 DPI 下字体被放大以尝试减少像素化量)。遗憾的是,Windows 上没有真正好的“一种真正的系统字体”,只有很多像这样的死胡同。以下 RbMm 的答案是最正确的,但采用它取决于您的代码当前所做的事情。 (我仍然怀疑微软只是希望我们对 Segoe UI 9 进行硬编码,而没有再次更改首选字体的计划......)
  • @DavidHeffernan 这不是位图字体,也不是 Tahoma。而且它还没有“死”。 Raymond Chen 和 MSDN 都错了。它是“Microsoft Sans Serif”,不要与“MS Sans Serif”混淆(后者位图字体)。它也不是像“MS Shell Dlg”这样的注册表映射伪字体。至少从 .NET 2.0 开始,它也是 the de-facto default font in C#。这是 所有 经典 Windows 对话框至少从 Vista 开始就使用的字体,例如文件属性或文件夹选项。

标签: windows winapi highdpi


【解决方案1】:

您需要通过SystemParametersInfo(SPI_GETNONCLIENTMETRICS,) 获取NONCLIENTMETRICS,然后使用它的LOGFONT 数据来创建自己的字体。或者您可以查询SystemParametersInfo(SPI_GETICONTITLELOGFONT) 并使用它

【讨论】:

  • 这是我们真正得到的最接近正确答案的答案。我个人使用lfMessageFont自己;我不确定是否有更好的选择。只需在程序初始化中执行一次并使用全局变量作为句柄。如果代码中充斥着GetStockObject(DEFAULT_GUI_FONT) 调用并且简单的查找替换无济于事,那么您可以执行#define GetStockObject(x) ((x)==DEFAULT_GUI_FONT ? hMessageFont : (GetStockObject)(x)) 之类的操作:/ 取决于代码的编写方式。 (当然,这意味着您不会同时调用DeleteObject()。)
【解决方案2】:

不同用途推荐的字体可以从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 中还有许多其他字体。确保为您的目的选择正确的。

您应该按照here 的说明在application manifest 中设置DPI 感知级别,以获得最佳兼容性。

【讨论】:

    【解决方案3】:

    .NET 框架中的 WinForms 在内部转换 DEFAULT_GUI_FONT(实际上在大多数情况下用于获取 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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-11-30
      • 2014-08-13
      • 1970-01-01
      • 2018-12-06
      • 2017-11-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多