我有一个 Windows 应用程序,我想在高 DPI 显示器上看起来不错。该应用程序在很多地方都使用了 DEFAULT_GUI_FONT,并且以这种方式创建的字体无法正确缩放。
有没有什么简单的方法可以解决这个问题而不会太痛苦?
你需要NONCLIENTMETRICS
通过SystemParametersInfo
( SPI_GETNONCLIENTMETRICS
,) 然后使用它 LOGFONT 数据来创建自己的字体。或者您可以查询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
里面还有很多其他字体。确保为您的目的选择正确的。
.NET 框架中DEFAULT_GUI_FONT
的 WinForms 通过将其高度从像素(这是 GDI 字体本机使用的单位)缩放到点(这是在大多数情况下用于获取 WinForms 窗体和控件的默认字体)在内部转换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#。但除此之外,本文建议您应该在假设 96 dpi 设计的情况下缩放像素尺寸,并用于GetDpiForWindow
确定实际 DPI。请注意,上面公式中的“72”与显示器 DPI 设置无关,这是因为 .NET 喜欢使用以点而不是像素指定的字体(否则只需将 LOGFONT 的高度缩放 DPIy/96) .
该站点提出了类似的建议,但带有GetDpiForMonitor
.
我不能确定根据某些 DPI 相关因素手动缩放字体大小的一般方法是否是缩放字体的可靠且面向未来的方法(尽管它似乎是缩放非字体 GUI 元素的方法) . 但是,由于 .NET 基本上也只是根据某种 DPI 值计算一些魔术因子,因此这可能是一个不错的猜测。
此外,您还需要缓存该HFONT
. HFONT
-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+,然后以这种方式检索到的 GDI+ 字体使用以下命令进行转换FontInPoints
:
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