The EASY way to make a C# Windows.forms app really per monitor DPI aware

As posted in another post, many apps have problems with windows scaling.

See these screen-dumps from Windows lengthy guide that was found here

UPDATE 2020. After Windows creators update, they made things slightly more complicated, sorry…

Here is a reasonably minimalistic approach. You will often just need code like this:

    public partial class Form1 : Form {
         public Form1() {
             InitializeComponent();
             DPI_Per_Monitor.TryEnableDPIAware(this, SetUserFonts);
         }
         void SetUserFonts(float scaleFactorX, float scaleFactorY) {
            var OldFont = Font;
            Font = new Font(OldFont.FontFamily, 11f * scaleFactorX, OldFont.Style, GraphicsUnit.Pixel); 
            OldFont.Dispose();
         }
         protected override void DefWndProc(ref Message m) {
             DPI_Per_Monitor.Check_WM_DPICHANGED(SetUserFonts,m, this.Handle);
             base.DefWndProc(ref m);
         }
     }

You will need to obey a few rules, but often it can be implemented in existing code in minutes!

UPDATE: You will now also have add (or modify) an app.manifest source-file for your project, with content similar to this

<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity version="1.0.0.0" name="YourAppName"/>
  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
    </windowsSettings>
  </application>
</assembly>

For the whole thing to work we need a small helper class, source here (it is also updated 2020)

Here is the comment from the top of that:

DPI_Per_Monitor

Use to make a simple Windows form app truly DPI-aware.
That is AVOID woolly apps!

1) Make sure your forms are designed with font size set in PIXELS, not POINTS (yes!!)
All controls possible should use inherited fonts
Each font explicitly given in PIXEL needs to be handled  in the callback function
provided to Check_WM_DPICHANGED
The standard “8.25pt” matches “11.0px” (8.25 *96/72 = 11)

2) Leave the form in the default Autoscale=Font mode

3) Insert call to DPI_Per_Monitor.TryEnableDPIAware() , right after InitializeComponent();

4) Add suitable call to DPI_Per_Monitor.Check_WM_DPICHANGED_WM_NCCREATE in DefWndProc
If a DefWndProc override is not already present add e.g. these lines

protected override void DefWndProc(ref Message m) {
DPI_Per_Monitor.Check_WM_DPICHANGED_WM_NCCREATE(SetUserFonts,m, this.Handle);
base.DefWndProc(ref m);
}

that has a call-back function you must provide, that set fonts as needed (in pixels or points),
if all are inherited, then just one set:

void SetUserFonts(float scaleFactorX, float scaleFactorY) {
var OldFont = Font;
Font = new Font(OldFont.FontFamily, 11f * scaleFactorX, OldFont.Style, GraphicsUnit.Pixel);
OldFont.Dispose();
}

5) And a really odd one, due to a Visual Studio BUG.
This ONLY works, if your PRIMARY monitor is scaled at 100% at COMPILE time!!!
It is NOT just a matter of using a different reference than the 96 dpi below, and
it does not help to run it from a secondary monitor set to 100% !!!
And to make things worse, Visual Studio is one of the programs that doesn’t handle
change of scale on primary monitor, without at the least a sign out….

NOTE that if you got (Checked)ListBoxes, repeated autosizing (e.g. move between monitors)
might fail as it rounds the height down to a multipla of the itemheight. So despite a
bottom-anchor it will ‘creep’ upwards…
So I recommend to place an empty and/or hidden bottom-anchored label just below the boxes,
to scale the spacing and set e.g. :  yourList.Height=yourAnchor.Top-yourList.Top

Also note that not everything gets scaled automatically. Only new updates of Win10 handles
the titlebar correctly. Also the squares of checkboxes are forgotten.

UPDATE:¬† a TINY improvement/change on the checkboxes after creators update. IF you start an app on a monitor not 100%, and stay there they ARE scaled correctly initially. But if you move the window to a monitor with a different DPI (or change DPI of current monitor), they still do not allow us to scale them correctly – but what can you expect with a product only a few years old and with only a few billion users…

But at the least it seems your primary monitor still does no longer has to be at 100% for VS to not mess up thing at compile time with VS2019 16.6.3, as was the case earlier (It messes with your source code changing positions…) Note that the new DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 mode is the only aware mode supported in VS now, no backwards compatibility after Win X “Creators update”!!

UPDATE: A few bugs have been ironed out, thanks to Robin Krom in the comments