Creating a DPI-Aware Application
Asked Answered
T

11

82

I have a form application in C#. When I change the monitor's DPI, all the controls move. I used the code this.AutoScaleMode = AutoScaleMode.Dpi, but it didn't avoid the problem.

Does anyone have an idea?

Turman answered 2/11, 2010 at 7:56 Comment(4)
You check check this blog on the matter as well, I think it provides good information on the topic: telerik.com/blogs/…Oresund
FWIW, on latest Windows 10, I had better success doing the opposite: making sure my legacy Windows Forms app was UNAWARE of DPI. This forced Windows 10 to use its default scaling for winforms, which worked perfectly. To test from code, SetProcessDpiAwareness(0). 0 = Unaware in some enum - google for details, need DllImport from "shcore.dll". Its recommended this be done in app.manifest; I just mention code as a test, to be sure.Mulderig
I had a problem in a C# Winforms app where the DPI used by the app window would change from the screen setting of 96 dpi (Scale factor 125%) to 120dpi (Scale factor 100%), but only when running an executable - problem did not occur when running in VS2013 IDE. Attaching debugger to find out where change occurred gave non-reproducible results. Fixed as above by calling SetProcessDpiAwareness(0).Fairyfairyland
2023 update: Please check out my answer https://mcmap.net/q/131459/-problems-getting-winforms-to-scale-correctly-with-dpiWeatherly
M
125

EDIT: As of .NET 4.7, windows forms has improved support for High DPI. Read more about it on learn.microsoft.com It only works for Win 10 Creators Update and higher though, so it might not be feasible to use this yet depending on your user base.


Difficult, but not impossible. Your best option is to move to WPF of course, but that might not be feasible.

I've spent A LOT of time with this problem. Here are some rules/guidelines to make it work correctly without a FlowLayoutPanel or TableLayoutPanel:

  • Always edit/design your apps in default 96 DPI (100%). If you design in 120DPI (125% f.ex) it will get really bad when you go back to 96 DPI to work with it later.
  • I've used AutoScaleMode.Font with success, I haven't tried AutoScaleMode.DPI much.
  • Make sure you use the default font size on all your containers (forms, panels, tabpage, usercontrols etc). 8,25 px. Preferrably it shouldn't be set in the .Designer.cs file at all for all containers so that it uses the default font from the container class.
  • All containers must use the same AutoScaleMode
  • Make sure all containers have the below line set in the Designer.cs file:

this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); // for design in 96 DPI

  • If you need to set different font sizes on labels/textboxes etc. set them per control instead of setting the font on the container class because winforms uses the containers font setting to scale it's contents and having f.ex a panel with a different font size than it's containing form is guaranteed to make problems. It might work if the form and all containers on the form use the same font size, but I haven't tried it.
  • Use another machine or a virtual windows install (VMware, Virtual PC, VirtualBox) with a higher DPI setting to test your design immediatly. Just run the compiled .exe file from the /bin/Debug folder on the DEV machine.

I guarantee that if you follow these guidelines you will be ok, even when you have placed controls with specific anchors and don't use a flowpanel. We have an app built this way deployed on hundreds of machines with different DPI setups and we no longer have any complaints. All forms/containers/grids/buttons/textfield etc sizes are scaled correctly as is the font. Images work too, but they tend to get a little pixellated at high DPI.

EDIT: This link has a lot of good info, especially if you choose to use AutoScaleMode.DPI: link to related stackoverflow question

Martins answered 2/11, 2010 at 9:14 Comment(8)
I already have FlowLayoutPanel, do I write "this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);",to each panel? and where do I write?Turman
I haven't tried this with FlowLayoutPanel, but in each of your forms or usercontrols .Designer.cs file (the partial class file generated by Visual Studio designer) you need the AutoScaleDimensions and AutoScaleMode set. This applies to a "normal" form where you place the controls with anchors, eg. you have a specific x and y coordinate for it's locationMartins
thanks, very useful post. i had this problem in a project that i had to fix after someone else. simply removed all AutoScale* lines from all *.Designer.cs files, and stuff just works now.Celsacelsius
Our experimentation with your advice above shows it to be good advice. ** Since you posted that 3 years ago, have you learned any additional guidelines to follow? ** We have found a couple others... posted here: #22735674Wintery
@BrianKennedy, we moved to WPF and in the last ~1,5 years to web development with SVG for perfect scaling and quality. We still maintain a winforms app though and using the above guidelines have proved sufficient in our case, we never get any complaints from customers. One thing that stopped working after moving from .net 1.1 to 2.0 (then 4.0) is scaling of components drawn with GDI+. We draw large forms looking like paper forms as background for textboxes and labels. The "paper-form" components scale, but not by the same about as the x&y pixel anchored labels and textboxes.Martins
The major issue with this approach is what if I need to edit app with 120DPI? This should work when dimension of my form in design mode is large then screen dimension with 96 DPI where the build will be launched.Urba
@MuratfromDaminionSoftware: Yes, it has it's caveats. At the time of posting (5 years ago), high DPI screens weren't that common, but that has changed rapidly. The problem with developing in a higher DPI is that all the .Designer generated code becomes specific to the higher DPI and returning to a lower DPI after build makes everything scale wrong. Moving up to a higher DPI after build works fine though.Martins
For manual scale Location and Size, I found one issue on TabControl. If one of Tabpage is not yet been opened (not activated once), the font in this tabpage may come to weird. The first of Tabpage is always OK.Sedgewinn
E
52

note: this will not fix the controls moving , when dpi change. this will only fix blurry text!!.


How to fix blurry Windows Forms in high-dpi settings:

  1. Go the the Forms designer, then select your Form (by clicking at its title bar)
  2. Press F4 to open the Properties window,
  3. then locate the AutoScaleMode property
  4. Change it from Font (default) to Dpi.

Now, go to Program.cs (or the file where your Main method is located) and change it to look like:

namespace myApplication
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            // ***this line is added***
            if (Environment.OSVersion.Version.Major >= 6)
                SetProcessDPIAware();

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }

        // ***also dllimport of that function***
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern bool SetProcessDPIAware();
    }
}

Save and compile. Now your form should look crispy again.


source: http://crsouza.com/2015/04/13/how-to-fix-blurry-windows-forms-windows-in-high-dpi-settings/

Elf answered 29/8, 2018 at 9:57 Comment(4)
This works very well on win10 with VS2017! Thank youSubstantiate
If you look at the screen images in the source link, they're quite different. While the approach of using DPI-mode scaling might fix the blurriness, the size of the controls changes, affecting the layout. This is therefore a solution that is brittle and must be carefully applied.Smoothshaven
Works very well on vs 2019 ( resulion 1920 x 1080 ), but the size of control has different size but i am okay with itAdalai
Thank you very much! it worked very well in Windows 11 / VS 2022.Depreciatory
O
19

I finally found solution to problem of both Screen Orientation and DPI handling.
Microsoft has already provided a document explaining it but with a little flaw that will kill DPI handling completely. Just follow solution provided in the document below under "Creating Separate Layout Code for Each Orientation" http://msdn.microsoft.com/en-us/library/ms838174.aspx

Then IMPORTANT part! Inside the code for Landscape() and Portrait() methods at the very end of each add these lines:

this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;

So, the code for these 2 methods would be like:

protected void Portrait()
{
   this.SuspendLayout();
   this.crawlTime.Location = new System.Drawing.Point(88, 216);
   this.crawlTime.Size = new System.Drawing.Size(136, 16);
   this.crawlTimeLabel.Location = new System.Drawing.Point(10, 216);
   this.crawlTimeLabel.Size = new System.Drawing.Size(64, 16);
   this.crawlStartTime.Location = new System.Drawing.Point(88, 200);
   this.crawlStartTime.Size = new System.Drawing.Size(136, 16);
   this.crawlStartedLabel.Location = new System.Drawing.Point(10, 200);
   this.crawlStartedLabel.Size = new System.Drawing.Size(64, 16);
   this.light1.Location = new System.Drawing.Point(208, 66);
   this.light1.Size = new System.Drawing.Size(16, 16);
   this.light0.Location = new System.Drawing.Point(192, 66);
   this.light0.Size = new System.Drawing.Size(16, 16);
   this.linkCount.Location = new System.Drawing.Point(88, 182);
   this.linkCount.Size = new System.Drawing.Size(136, 16);
   this.linkCountLabel.Location = new System.Drawing.Point(10, 182);
   this.linkCountLabel.Size = new System.Drawing.Size(64, 16);
   this.currentPageBox.Location = new System.Drawing.Point(10, 84);
   this.currentPageBox.Size = new System.Drawing.Size(214, 90);
   this.currentPageLabel.Location = new System.Drawing.Point(10, 68);
   this.currentPageLabel.Size = new System.Drawing.Size(100, 16);
   this.addressLabel.Location = new System.Drawing.Point(10, 4);
   this.addressLabel.Size = new System.Drawing.Size(214, 16);
   this.noProxyCheck.Location = new System.Drawing.Point(10, 48);
   this.noProxyCheck.Size = new System.Drawing.Size(214, 20);
   this.startButton.Location = new System.Drawing.Point(8, 240);
   this.startButton.Size = new System.Drawing.Size(216, 20);
   this.addressBox.Location = new System.Drawing.Point(10, 24);
   this.addressBox.Size = new System.Drawing.Size(214, 22);

   //note! USING JUST AUTOSCALEMODE WILL NOT SOLVE ISSUE. MUST USE BOTH!
   this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); //IMPORTANT
   this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;   //IMPORTANT
   this.ResumeLayout(false);
}

protected void Landscape()
{
   this.SuspendLayout();
   this.crawlTime.Location = new System.Drawing.Point(216, 136);
   this.crawlTime.Size = new System.Drawing.Size(96, 16);
   this.crawlTimeLabel.Location = new System.Drawing.Point(160, 136);
   this.crawlTimeLabel.Size = new System.Drawing.Size(48, 16);
   this.crawlStartTime.Location = new System.Drawing.Point(64, 120);
   this.crawlStartTime.Size = new System.Drawing.Size(248, 16);
   this.crawlStartedLabel.Location = new System.Drawing.Point(8, 120);
   this.crawlStartedLabel.Size = new System.Drawing.Size(48, 16);
   this.light1.Location = new System.Drawing.Point(296, 48);
   this.light1.Size = new System.Drawing.Size(16, 16);
   this.light0.Location = new System.Drawing.Point(280, 48);
   this.light0.Size = new System.Drawing.Size(16, 16);
   this.linkCount.Location = new System.Drawing.Point(80, 136);
   this.linkCount.Size = new System.Drawing.Size(72, 16);
   this.linkCountLabel.Location = new System.Drawing.Point(8, 136);
   this.linkCountLabel.Size = new System.Drawing.Size(64, 16);
   this.currentPageBox.Location = new System.Drawing.Point(10, 64);
   this.currentPageBox.Size = new System.Drawing.Size(302, 48);
   this.currentPageLabel.Location = new System.Drawing.Point(10, 48);
   this.currentPageLabel.Size = new System.Drawing.Size(100, 16);
   this.addressLabel.Location = new System.Drawing.Point(10, 4);
   this.addressLabel.Size = new System.Drawing.Size(50, 16);
   this.noProxyCheck.Location = new System.Drawing.Point(168, 16);
   this.noProxyCheck.Size = new System.Drawing.Size(152, 24);
   this.startButton.Location = new System.Drawing.Point(8, 160);
   this.startButton.Size = new System.Drawing.Size(304, 20);
   this.addressBox.Location = new System.Drawing.Point(10, 20);
   this.addressBox.Size = new System.Drawing.Size(150, 22);

   //note! USING JUST AUTOSCALEMODE WILL NOT SOLVE ISSUE. MUST USE BOTH!
   this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); //IMPORTANT
   this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;   //IMPORTANT
   this.ResumeLayout(false);
}

Works like charm for me.

Otorhinolaryngology answered 12/4, 2012 at 22:59 Comment(1)
Creating Separate Layout Code for Each Orientation part is irrelevant to the original question, a winform application.Incretion
M
6

It looks like this is a problem with Windows. Taking out these two lines fixed everything.

this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;

This is where I got the solution:

Mchenry answered 30/3, 2017 at 19:31 Comment(0)
A
4

It is really hard to design DPI aware applications in Windows Forms. You would have to use layout containers that resize properly when the DPI is changed (such as TableLayoutPanel or FlowLayoutPanel). All controls need resizing as well. The configuration of those containers can be a challenge.

For simple applications it can be done within a reasonable amount of time, but for big applications it is really alot of work.

Arteaga answered 2/11, 2010 at 8:8 Comment(0)
C
3

From experience:

  • don't use DPI awareness with windows forms unless critical
  • to this end always set AutoScaleMode property to None on all forms and user controls in your app
  • The result: WYSIWYG type of interface when DPI settings change
Cesar answered 2/11, 2010 at 8:53 Comment(1)
I had a situation where setting AutoScaleMode to font mode would cause the app to crash as soon as I tried to run Windows in Medium (125% or 120ppi) instead of the usual 'Smaller' (96ppi). I set AutoScaleMode to None as you said and everything appears fine so far...Annetteannex
L
2

I struggled with this for a while eventually I found a super simple solution for windows 10 and potentially other systems.

In your WinForms App.config file paste this:

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

Then create a app.manifest file and paste or comment in this line:

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

After doing the above I was able to get great DPI results on my 4k screens.

Check out this and this for more information.

Lohrman answered 17/2, 2021 at 22:47 Comment(2)
FYI, this method does not work in .NET 5.0 or later as the tag "System.Windows.Forms.ApplicationConfigurationSection" will cause compilation error Ref: github.com/microsoft/dotnet/issues/374Willams
@Willams I think from .NET 5.0 the DPI is fine without my change... at least on my system starting a new app already had a good DPILohrman
C
1
  1. If you want your WinForms application to be DPI-Aware application, In addition to Trygve good answer, If you have big project you may want to scale your forms and their content automatically, You can do this by creating ScaleByDPI function:

ScaleByDPI function will receive a Control parameter that is usually a form, and than recursively iterate through all sub controls (if (control.HasChildren == true)), and scale location and sizes off your application controls & sizes and sizes of fonts to the OS configured DPI. You can try to implement it also for images, icons & graphics.

Special notes for ScaleByDPI function:

a. For all controls with default Font sizes, you will need to set their Font.Size to 8.25.

b. You can get devicePixelRatioX and devicePixelRatioY values by (control.CreateGraphics().DpiX / 96) and (control.CreateGraphics().DpiY / 96).

c. You will need scale Control.Size & Control.Location by algorithm that based on control.Dock & control.Anchor values. Be noticed that control.Dock may have 1 of 6 possible values and that control.Anchor may have 1 of 16 possible values.

d. this algorithm will need set values to next bool variables isDoSizeWidth, isDoSizeHeight, isDoLocationX, isDoLocationY, isDoRefactorSizeWidth, isDoRefactorSizeHeight, isDoRefactorLocationX, isDoRefactorLocationY, isDoClacLocationXBasedOnRight, isDoClacLocationYBasedOnBottom.

e. If your project uses a control library other then Microsoft controls, this controls may need a special treatment.

More info on above (d.) bool variables:

*Sometimes a group of controls (may be a buttons) need to be placed one after another on same vertical line, and their Anchor value include Right but not Left, or they need to be placed one after another on same horizontal line, and their Anchor value include Bottom but not Top, in this case you need to re-calculate controls Location values.

*In case of controls that Anchor contains Top & Bottom and\or Left & Right, you will need to re-factor controls Size & Location values.

Uses of ScaleByDPI function:

a. Add next command to the end off any Form constructor: ScaleByDPI(this);

b. Also when adding any control dynamically to a Form call to ScaleByDPI([ControlName]).

  1. When you set Size or Location of any control dynamically after constructor ended, create and use one of next functions in order to get the scaled values of Size or Location: ScaleByDPI_X \ ScaleByDPI_Y \ ScaleByDPI_Size \ ScaleByDPI_Point

  2. In order to mark your application as being DPI-aware, add the dpiAware element to your application's assembly manifest.

  3. Set GraphicsUnit of all Control.Font to System.Drawing.GraphicsUnit.Point

  4. In *.Designer.cs files of all containers, set AutoScaleMode value to System.Windows.Forms.AutoScaleMode.None

  5. in controls like ComboBox & TextBox, changing Control.Size.Hieght have no affect. In this case changing Control.Font.Size will fix control's height.

  6. If form StartPosition value is FormStartPosition.CenterScreen, you will need to recalculate the location of the window.

Chalaza answered 9/3, 2015 at 9:17 Comment(2)
Isn't AutoScaleMode.Font preferred for most applications, with AutoScaleMode.DPI only useful for apps that are trying to take up a certain percentage of the screen?Conidiophore
Do we have to edit the Designer.cs file? Winforms moans about the user editing that file since it's supposed to be auto-created by VS when you create the GUI in the VS Designer itself. Wouldn't adding it to the Form1_Load event be sufficient?Annetteannex
P
1

I have been trying all kinds of solutions as a developer with a 4K monitor. All WPF windows are fine, but all WinForms scale wrong. It happens on a WinForms app within WPF, but also as standalone WinForms. Both applications in .NET 4.7.2. so it should work as explained in all other answers, but it doesn't.

This are my settings enter image description here

The final solution I ended with is using "unaware" instead of "PerMonitorV2"

In the app.manifest file

  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">unaware</dpiAwareness>
    </windowsSettings>
  </application>
Promptbook answered 11/4, 2023 at 14:2 Comment(0)
S
1
  1. Project file add (yourproject.csproj):

    <DpiAwarenessPerMonitor>true</DpiAwarenessPerMonitor>
    
  2. Program.cs file if exists

    ApplicationConfiguration.Initialize();
    
  3. Make it free. Remove this line. Because it sets up bootstrap's dpi preferences, custom it to project's preferences.

    Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(MainForm);
    
  4. MainForm.cs constructor function add

    this.AutoScaleMode = AutoScaleMode.Dpi;
    
  5. And remove from MainForm.designer.cs

    this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    

These work for .Net 6 Winform application clearly.

Schaeffer answered 24/11, 2023 at 11:5 Comment(0)
O
-1

Since a Winform application form may content controls AND images, allowing the system to resize YOUR window is NOT a solution, but if you could manage to have one form per DPI resolution, with properly scaled images... And that's not a good idea, since as the screen size grow, the font size diminishes.

When using a different DPI resolution the system forces your form to redefine its control's size, location and font, BUT NOT IMAGES, the solution is to change the form's DPI at runtime, when loading, so that everything goes back to original size and location.

This is possible solution, which I've tested it with a card game application where I've gott some 80 image buttons, TabControls etc.

In each form form_Load event, add this code snippet:

  Dim dpi As Graphics = Me.CreateGraphics
    Select Case dpi.DpiX
        Case 120
            '-- Do nothing if your app has been desigbned with 120 dpi
        Case Else
    '-- I use 125 AND NOT 120 because 120 is 25% more than 96
            Me.Font = New Font(Me.Font.FontFamily, Me.Font.Size * 125 / dpi.DpiX)
    End Select

Besides, a quick trick for testing various resolutions on the same computer, without restarting:

From control panel, change the resolution. Do not restart! Instead close your session and open a new one with same user.

There is another caveat: If you set a control's size and position at runtime, then you should apply the same DPI factor (eg. 125 / Dpi.Dpix) to the new coordinates. So you'd better set up a DPIFactor global variable from application.startup event.

Last but not least:

DO NOT open your application in Visual Studio from another resolution than the original one, or ALL YOUR CONTROLS will move and resize as you open each form, and there is no way back...

Hope this helps, happy programming.

Odeen answered 30/12, 2010 at 16:33 Comment(2)
Instead of setting your form's DPI back to 96, have you tried using AutoScaleMode.None?Incretion
This works until your user has a high def monitor. Then you end up with unreadable content. This approach is essentially disabling high def support.Symphonic

© 2022 - 2024 — McMap. All rights reserved.