deserg
deserg

Reputation: 71

Using DEFAULT_GUI_FONT in high DPI Windows application

I have a Windows application which I want to look good at high DPI monitors. The application is using DEFAULT_GUI_FONT in lots of places, and the font created this way doesn't scale correctly.

Is there any simple way to fix this problem with not too much pain?

Upvotes: 0

Views: 3089

Answers (3)

tambre
tambre

Reputation: 4843

The recommended fonts for different purposes can be obtained from the NONCLIENTMETRICS structure.

For automatically DPI-scaled fonts (Windows 10 1607+, must be per-monitor DPI-aware):

// Your window's handle
HWND window;

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

// Obtain the recommended fonts, which are already correctly scaled for the current DPI
NONCLIENTMETRICSW non_client_metrics;
non_client_metrics.cbSize = sizeof(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
}

For older Windows versions you can use the system-wide DPI and scale the font manually (Windows 7+, must be system DPI-aware):

// Your window's handle
HWND window;

// Obtain the recommended fonts, which are already correctly scaled for the current DPI
NONCLIENTMETRICSW non_client_metrics;
non_client_metrics.cbSize = sizeof(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
}

const 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 has also many other fonts in it. Make sure to choose the right one for your purpose.

You should set the DPI-awareness level in your application manifest as described here for best compatibility.

Upvotes: 4

dialer
dialer

Reputation: 4835

WinForms in the .NET framework internally converts the DEFAULT_GUI_FONT (which is in fact used to get the default font for WinForms Forms and Controls in most situations) by scaling its height from pixels (which is the unit GDI fonts use natively) to Points (which is preferred by GDI+). Drawing text using points implies that the physical size of the rendered text depends on the monitor DPI setting.

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;

Obviously you cannot use this directly as it's C#. But besides that, this article suggests that you should scale pixel dimensions assuming a 96 dpi design, and use GetDpiForWindow to determine the actual DPI. Note that the "72" in the formula above has nothing to do with the monitor DPI setting, it comes from the fact that .NET likes to use fonts specified in points rather than pixels (otherwise just scale the LOGFONT's height by DPIy/96).

This site suggests something similar, but with GetDpiForMonitor.

I cannot say for sure whether the general approach of manually scaling the font size according to some DPI-dependent factor is a robust and future-proof for scaling fonts (it seems to be the way to go about scaling non-font GUI elements though). However, since .NET basically also just calculates some magic factor based on some sort of DPI value, it's probably a pretty good guess.

Also, you'll want to cache that HFONT. HFONT - LOGFONT conversions are not negligible.


See also (references):

WinForms gets its default using GetStockObject(DEFAULT_GUI_FONT) (there are a few exceptions though, mostly obsolete):

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

The HFONT is converted to GDI+, and then the GDI+ font retrieved this way is transformed using 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

The content of the SizeInPoints getter is already listed above.

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

Upvotes: 1

RbMm
RbMm

Reputation: 33706

you need get NONCLIENTMETRICS by SystemParametersInfo(SPI_GETNONCLIENTMETRICS,) and then use it LOGFONT data, for create self font. or you can query for SystemParametersInfo(SPI_GETICONTITLELOGFONT) and use it

Upvotes: 4

Related Questions