我有一个Windows应用程序,我希望在高DPI监视器上看起来很好。应用程序在很多地方使用DEFAULT_GUI_FONT,这种方式创建的字体无法正确缩放。
有没有简单的方法来解决这个问题而不是太痛苦?
答案 0 :(得分:3)
您需要NONCLIENTMETRICS
(SPI_GETNONCLIENTMETRICS
)获取SystemParametersInfo
,然后使用LOGFONT数据创建自己的字体。或者您可以查询SystemParametersInfo
(SPI_GETICONTITLELOGFONT
)并使用它
答案 1 :(得分:0)
可以从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所述的here设置DPI感知级别,以获得最佳兼容性。
答案 2 :(得分:0)
.NET 框架中的 WinForms 通过从像素(单位 GDI 字体)缩放其高度,在内部转换 DEFAULT_GUI_FONT
(实际上在大多数情况下用于获取 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#。但除此之外,this article 建议您应该在假设 96 dpi 设计的情况下缩放像素尺寸,并使用 GetDpiForWindow
来确定实际的 DPI。请注意,上面公式中的“72”与显示器 DPI 设置无关,这是因为 .NET 喜欢使用以点而不是像素指定的字体(否则只需按 DPIy/96 缩放 LOGFONT 的高度) .
This site 表示类似的东西,但使用 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+,然后使用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