How to write WinForms code that auto-scales to system font and dpi settings?

Brian Kennedy picture Brian Kennedy · Mar 29, 2014 · Viewed 84.3k times · Source

Intro: There's a lot of comments out there that say "WinForms doesn't auto-scale to DPI/font settings well; switch to WPF." However, I think that is based on .NET 1.1; it appears they actually did a pretty good job of implementing auto-scaling in .NET 2.0. At least based on our research and testing so far. However, if some of you out there know better, we'd love to hear from you. (Please don't bother arguing we should switch to WPF... that's not an option right now.)

Questions:

  • What in WinForms does NOT auto-scale properly and therefore should be avoided?

  • What design guidelines should programmers follow when writing WinForms code such that it will auto-scale well?

Design Guidelines we have identified so far:

See community wiki answer below.

Are any of those incorrect or inadequate? Any other guidelines we should adopt? Are there any other patterns that need to be avoided? Any other guidance on this would be very appreciated.

Answer

kjbartel picture kjbartel · Apr 21, 2015

Controls which do not support scaling properly:

  • Label with AutoSize = False and Font inherited. Explicitly set Font on the control so it appears in bold in the Properties window.
  • ListView column widths don't scale. Override the form's ScaleControl to do it instead. See this answer
  • SplitContainer's Panel1MinSize, Panel2MinSize and SplitterDistance properties
  • TextBox with MultiLine = True and Font inherited. Explicitly set Font on the control so it appears in bold in the Properties window.
  • ToolStripButton's image. In the form's constructor:

    • Set ToolStrip.AutoSize = False
    • Set ToolStrip.ImageScalingSize according to CreateGraphics.DpiX and .DpiY
    • Set ToolStrip.AutoSize = True if needed.

    Sometimes AutoSize can be left at True but sometimes it fails to resize without those steps. Works without that changes with .NET Framework 4.5.2 and EnableWindowsFormsHighDpiAutoResizing.

  • TreeView's images. Set ImageList.ImageSize according to CreateGraphics.DpiX and .DpiY. For StateImageList, works without that changes with .NET Framework 4.5.1 and EnableWindowsFormsHighDpiAutoResizing.
  • Form's size. Scale fixed size Form's manually after creation.

Design Guidelines:

  • All ContainerControls must be set to the same AutoScaleMode = Font. (Font will handle both DPI changes and changes to the system font size setting; DPI will only handle DPI changes, not changes to the system font size setting.)

  • All ContainerControls must also be set with the same AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);, assuming 96dpi (see the next bullet) and default Font of MS Sans Serif (see the bullet two down). That is auto-added by the designer based on the DPI you open the designer in... but was missing from many of our oldest designer files. Perhaps Visual Studio .NET (the version before VS 2005) was not adding that in properly.

  • Do all your designer work in 96dpi (we might be able to switch to 120dpi; but the wisdom on the internet says to stick to 96dpi; experimentation is in order there; by design, it shouldn't matter as it just changes the AutoScaleDimensions line that the designer inserts). To set Visual Studio to run at a virtual 96dpi on a high-resolution display, find its .exe file, right-click to edit properties, and under Compatibility select "Override high DPI scaling behavior. Scaling performed by: System".

  • Be sure you never set the Font at the container level... only on the leaf controls OR in the constructor of your most base Form if you want an application-wide default Font other than MS Sans Serif. (Setting the Font on a Container seems to turn off the auto-scaling of that container because it alphabetically comes after the setting of AutoScaleMode and AutoScaleDimensions settings.) NOTE that if you do change the Font in your most base Form's constructor, that will cause your AutoScaleDimensions to compute differently than 6x13; in particular, if you change to Segoe UI (the Win 10 default font), then it will be 7x15... you will need to touch every Form in the Designer so that it can recompute all the dimensions in that .designer file, including the AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);.

  • Do NOT use Anchor Right or Bottom anchored to a UserControl... its positioning will not auto-scale; instead, drop a Panel or other container into your UserControl and Anchor your other Controls to that Panel; have the Panel use Dock Right, Bottom, or Fill in your UserControl.

  • Only the controls in the Controls lists when ResumeLayout at the end of InitializeComponent is called will be auto-scaled... if you dynamically add controls, then you need to SuspendLayout(); AutoScaleDimensions = new SizeF(6F, 13F); AutoScaleMode = AutoScaleMode.Font; ResumeLayout(); on that control before you add it in. And your positioning will also need to be adjusted if you are not using Dock modes or a Layout Manager like FlowLayoutPanel or TableLayoutPanel.

  • Base classes derived from ContainerControl should leave AutoScaleMode set to Inherit (the default value set in class ContainerControl; but NOT the default set by the designer). If you set it to anything else, and then your derived class tries to set it to Font (as it should), then the act of setting that to Font will clear out the designer's setting of AutoScaleDimensions, resulting in actually toggling off auto-scaling! (This guideline combined with the prior one means you can never instantiate base classes in a designer... all classes need to either be designed as base classes or as leaf classes!)

  • Avoid using Form.MaxSize statically / in the Designer. MinSize and MaxSize on Form do not scale as much as everything else. So, if you do all your work in 96dpi, then when at higher DPI your MinSize won't cause problems, but may not be as restrictive as you expected, but your MaxSize may limit your Size's scaling, which can cause problems. If you want MinSize == Size == MaxSize, don't do that in the Designer... do that in your constructor or OnLoad override... set both MinSize and MaxSize to your properly-scaled Size.

  • All of the Controls on a particular Panel or Container should either use Anchoring or Docking. If you mix them, the auto-scaling done by that Panel will often misbehave in subtle bizarre ways.

  • When it does its auto-scaling, it will be trying to scale the overall Form... however, if in that process it runs into the upper limit of the screen size, that is a hard limit that can then screw up (clip) the scaling. Therefore, you should make sure all Forms in the Designer at 100%/96dpi are sized no larger than 1024x720 (which corresponds to 150% on a 1080p screen or 300% which is the Windows recommended value on a 4K screen). But you need to subtract out for the giant Win10 title/caption bar... so more like 1000x680 max Size... which in the designer will be like 994x642 ClientSize. (So, you can do a FindAll References on ClientSize to find violators.)