The EASY way to make a .Net 8.0 WinForms app per monitor DPI aware

As posted in a rather old post, many apps have problems with windows scaling.

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

With previous versions of .Net Framework this have been somewhat cumbersome, see my post here.

But with .Net 8.0 WinForms (well also 7.0 and 6.0), and Windows 11 (or newer 10), it is now fairly simple, as long as you follow a few rules, that can be relatively easy applied copying existing Framework projects. I experimented with VS 17.8 on Win 11 23H2.

Below I have used C#, but not limited to that.

Guide creating a NEW .net 8.0 WinForm App

1)

In the outer program.cs, be sure it calls

ApplicationConfiguration.Initialize();

BEFORE

Application.Run(new Form1());

(A new .net 8.0 WinForm project has this as default)

 

2)

You would need a project file similar to this with settings used by the ApplicationConfiguration.Initialize()

<Project Sdk=”Microsoft.NET.Sdk”>

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>

<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
<!– https://learn.microsoft.com/en-us/dotnet/desktop/winforms/whats-new/net60?view=netdesktop-6.0#project-level-application-settings –>

</PropertyGroup>

</Project>
The other settings in the link, are not important for the scaling.

3)

To get ListBoxes, CheckBoxes et cetera to work, a little trick is needed, as they are (still) not properly supported, as the bottom of any list creeps up, moving back and forth between monitors, ignoring any anchor(!), the bug originates in that they want to keep the height an integral multiple of the item height, and by a bug do not handle rounding issues.

Fix: Place each and every bottom-anchored list-control in its own dummy Panel and use DockStyle.Fill for the list-control filling the panel, as fill overrules the creeping! And they DO support that the panels themselves can be either docked or anchored as you please….

(Obviously if the list is already filling a control, say a split-panel, there is no need for an extra panel, just make sure it uses DockStyle.Fill and not just anchors)

That is really all there is to it… AFAIK

There might be other limitations that needs workarounds. If you encounter any types not mentioned, please comment.

 

Here the trick applied to the right list only:

Here the left bottom anchored list is placed directly in a split panel. (the right one in a dummy panel).
If I put the list-box directly on the native form, it collapses completely(!) on the first return to 100% making it totally useless, in a container it is faulty but not entirely useless…. And with the extra panel, it works!

Here the source if you would like to experiment: WinFormsAppCreepingListOnRescale_workaround

You perhaps noticed that the window certainly isn’t 125% scaled above. It is 114% × 133%.  Selecting 350% gave 335% × 382%.
But that is a more general and unrelated Windows scaling bug, out of scope here…

 


Guide in upgrading an EXISTING .Net Framework 4.8 app

Start a new .Net 8.0 WinForms project.

Close VS, copy all your code files in, be careful on program.cs. (Avoid .vs, bin and obj dirs, and the xxxx.sln, xxxx.csproj and xxxx.csproj.user files)
(if your program.cs is standard, ignore it. If it your code in, then just make sure to add the initialisation-line as in the NEW project above, before creating any form)

Note that the below fix MIGHT only be needed if the parent control is a Horizontal splitContainer, I’m not sure though, if other controls can cause the same issue..

The fix can be done either by UI or by code

Method 1, by VS UI

  1. Repeat the below steps for all list-type controls, for all forms in your project…
  2. If the list is already filling a control, say a split-panel, just make sure it uses DockStyle.Fill and not just anchors. And continue to next list
  3. Else note the alignments (or size and position) and any anchors of the list-control.
  4. Shrink the list substantially (to less than 50%, both directions).
  5. Add a dummy panel large enough to contain the list in current reduced size.
  6. Move the shrunken list into the panel.
  7. Adjust the panel-size to match the size&position of the original list, this is fairly easy in the normal case where it was aligned with something.
  8. Apply the new panel the same anchors as the list had.
  9. Select the list, and select Dock = Fill, filling the whole new panel.

Method 2, pure code

Doing it codewise may look hard, but if you got a large project you might want to make something that automates this, rather than doing it in the UI

For each form-designer file do the following:

For each list type object, replace code lik below (I did it in the designer, here the resulting difference…)

replace

    this.checkedListBoxSaved = new System.Windows.Forms.CheckedListBox();

with

    checkedListBoxSaved = new CheckedListBox();
    panelSavedList = new Panel();
    panelSavedList.SuspendLayout();

and add the panel with the anchors and dimensions from the lits, and changethe list position, and mark it Dock.Fill

    // 
    // checkedListBoxSaved
    // 
    this.checkedListBoxSaved.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top
    | System.Windows.Forms.AnchorStyles.Bottom) 
    | System.Windows.Forms.AnchorStyles.Left) 
    | System.Windows.Forms.AnchorStyles.Right)));
    this.checkedListBoxSaved.FormattingEnabled = true;
    this.checkedListBoxSaved.HorizontalScrollbar = true;
    this.checkedListBoxSaved.Location = new System.Drawing.Point(69, 58);
    this.checkedListBoxSaved.Margin = new System.Windows.Forms.Padding(3, 0, 3, 0);
    this.checkedListBoxSaved.Name = "checkedListBoxSaved";
    this.checkedListBoxSaved.Size = new System.Drawing.Size(622, 94);
    this.checkedListBoxSaved.TabIndex = 6;

with

    // 
    // checkedListBoxSaved
    // 
    checkedListBoxSaved.Dock = DockStyle.Fill;
    checkedListBoxSaved.FormattingEnabled = true;
    checkedListBoxSaved.HorizontalScrollbar = true;
    checkedListBoxSaved.Location = new Point(0, 0);
    checkedListBoxSaved.Margin = new Padding(3, 0, 3, 0);
    checkedListBoxSaved.Name = "checkedListBoxSaved";
    checkedListBoxSaved.Size = new Size(616, 130);
    checkedListBoxSaved.TabIndex = 6;
    // 
    // panelSavedList
    // 
    panelSavedList.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
    panelSavedList.Controls.Add(checkedListBoxSaved);
    panelSavedList.Location = new Point(71, 76);
    panelSavedList.Name = "panelSavedList";
    panelSavedList.Size = new Size(616, 130);
    panelSavedList.TabIndex = 14;

and where added to the parent change to adding the panel

    this.splitContainer1.Panel1.Controls.Add(this.checkedListBoxSaved);

with

    splitContainer1.Panel1.Controls.Add(panelSavedList);

and extend the declaration

    private System.Windows.Forms.CheckedListBox checkedListBoxSaved;

to

    private CheckedListBox checkedListBoxSaved;
    private Panel panelSavedList;

and in the Form section insert a

    panelSavedList.ResumeLayout(false);

And repeat…


Visual Studio long standing bug

Micrososoft at the least have admitted to their scale-bug, but I would much prefer that they fixed it!!!

It has been like this for years, the VS2022 17.8 still shows this warning if the primary monitor is NOT at 100%. And they still do not make new form app scalable by default….