WinForms 4K and 1080p scaling / high DPI?

Chris K picture Chris K · Feb 27, 2018 · Viewed 11.4k times · Source

So, here is the scenario: I am trying to make all of my forms (which are Winforms) look good in 4K and 1080p also known as high-DPI or "dpi-aware". I (for the purpose of this question) have three forms: frmCompanyMasterEdit which inherits from frmBaseEdit which inherits from frmBase, which inherits form System.Windows.Forms.Form.

I have tried the old way, by making my application DPI-aware in the manifest:

  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    </windowsSettings>
  </application>

That way, other than minor anchoring issues which I can fix, the form looks perfect at 4K and looks just like that but a bit blurrier in 1080p. Here it is in 4K: 4K both old and new ways.

Anyway, so I scratch that and try it the new way described in .NET 4.7, targeting the 4.7 framework and adding the following code:

to app.config

<System.Windows.Forms.ApplicationConfigurationSection>
   <add key="DpiAwareness" value="PerMonitorV2" />
</System.Windows.Forms.ApplicationConfigurationSection>

to app.manifest

<!-- Windows 10 compatibility -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />

and also taking the old code out of the app.manifest so as to not override the new .NET 4.7 way. I made sure to put the code in the appropriate places. So here the form looks good in 4K as the image above but now in 1080p it is very zoomed in as shown here: 1080p new way

So either way, the form looks great in 4k other than minor anchoring issues, and it either (the old way) is the right size but a bit blurry in 1080p or is not blurry in 1080p but is really zoomed in. I also had to change these two lines in all the designer.vb files as shown below:

Me.AutoScaleDimensions = New System.Drawing.SizeF(96.0!, 96.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi

I don't know why I just can't get it looking appropriate in 1080p. Like I said, I am targeting the 4.7 .NET framework. I am running on the appropriate version of Windows 10 (Version 1709 / Creator's edition). The 1080p is scaled at 100%. Also, we do not have the resources to upgrade to WPF.

Answer

Nolonar picture Nolonar · Apr 9, 2018

I've successfully added DPI support for 2 of my Winforms applications targeting .NET 4.5 and 4.7 respectively.

In the past, I tried adding support via a manifest file with no luck. Fortunately, I found the following solution:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WinformsApp
{
    static class Program
    {
        [DllImport("Shcore.dll")]
        static extern int SetProcessDpiAwareness(int PROCESS_DPI_AWARENESS);

        // According to https://msdn.microsoft.com/en-us/library/windows/desktop/dn280512(v=vs.85).aspx
        private enum DpiAwareness
        {
            None = 0,
            SystemAware = 1,
            PerMonitorAware = 2
        }

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            SetProcessDpiAwareness((int)DpiAwareness.PerMonitorAware);

            Application.Run(new MainForm());
        }
    }
}

The above code is what your Program.cs file should look like. Of course, you'll have to port this to VB, but it should be easy enough to do.

This worked perfectly in Windows 10 without the need for any other modification.

In one of the two Winforms applications I was rendering strings through the Graphics class using pixel coordinates, which caused my strings to be offset. The fix was pretty trivial:

private void DrawString(Graphics g, string text, int x, int y)
{
    using (var font = new Font("Arial", 12))
    using (var brush = new SolidBrush(Color.White))
        g.DrawString(text, font, brush, LogicalToDeviceUnits(x), LogicalToDeviceUnits(y));
}

Basically, I had to use Control.LogicalToDeviceUnits(int value) to get the pixel coordinates to scale.

Other than that, I didn't need to touch my code at all.