14
votes

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.

2
You probably need to upgrade to .NET Framework 4.7.2 preview blogs.msdn.microsoft.com/dotnet/2018/02/05/… and try again, as Rich Lander mentioned a few more patches in High DPI area when 4.7.1 was announced, blogs.msdn.microsoft.com/dotnet/2017/10/17/…Lex Li

2 Answers

14
votes

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.

8
votes

In my case, I ran into too many glitches with the recommended approach of setting process DPI awareness: minimum form size won't work correctly, random messing up when moving forms across monitors with different DPI, forms cropped for no apparent reason, font Segoe UI won't scale correctly, anchoring controls within splitter containers makes everything float in the wrong position, etc.

There's also the issue that icons become smaller and you need to update your code to selectively load higher resolution versions for buttons, ListView, TreeView, and so on.

The alternative I ended up using is to use Windows 10's setting to override scaling behavior with "GDI Scaling". This can be declared in the app's manifest.

Here's the setting, that you can try it out first and see if it meets your needs:

enter image description here

This setting can be set automatically by modifying the app.manifest file in your project and adding the following entry:

<asmv3:application>
  <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">
    <gdiScaling>true</gdiScaling>
  </asmv3:windowsSettings>
</asmv3:application>

The code may give you an error that "asmv3" is not recognized. To add the asmv3 namespace, modify your app.manifest to include it:

Before:

<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">

After:

<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">