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.
I found it to be very hard to get WinForms to play nice with high DPI. So, I wrote a VB.NET method to override the form behavior:
I recently came across this problem, especially in combination with Visual Studio rescaling when the editor is opened on high-dpi system. I found it best to keep
AutoScaleMode = Font
, but to set the Forms Font to the default font, but specifying the size in pixel, not point, i.e.:Font = MS Sans; 11px
. In code, I then reset the font to the default:Font = SystemFonts.DefaultFont
and all is fine.Just my two cents. I thought I share, because “keeping AutoScaleMode=Font”, and “Set font size in pixel for the Designer” was something I did not find on the internet.
I have some more details on my Blog: http://www.sgrottel.de/?p=1581&lang=en
In addition to the anchors not working very well: I would go a step farther and say that exact positioning (aka, using the Location property) does not work very well with the font scaling. I've had to address this issue in two different projects. In both of them, we had to convert the positioning of all the WinForms controls to using the TableLayoutPanel and FlowLayoutPanel. Using the Dock (usually set to Fill) property inside the TableLayoutPanel works very well and scales fine with the system font DPI.
My experience has been fairly different to the current top voted answer. By stepping through the .NET framework code and perusing the reference source code, I concluded that everything is in place for auto-scaling to work, and there was only a subtle issue somewhere messing it up. This turned out to be true.
If you create a properly reflowable / auto-sized layout, then almost everything works exactly like it should, automatically, with the default settings used by Visual Studio (namely, AutoSizeMode = Font on the parent form, and Inherit on everything else).
The only gotcha is if you have set the Font property on the form in the designer. The generated code will sort the assignments alphabetically, which means that
AutoScaleDimensions
will be assigned beforeFont
. Unfortunately, this completely breaks WinForms auto scaling logic.The fix is simple though. Either don't set the
Font
property in the designer at all (set it in your form constructor), or manually reorder these assignments (but then you have to keep doing this every time you edit the form in the designer). Voila, nearly perfect and fully automatic scaling with minimal hassle. Even the form sizes are scaled correctly.I will list known problems here as I encounter them:
TableLayoutPanel
calculates control margins incorrectly. No known work-around short of avoiding margins and paddings altogether - or avoiding nested table layout panels.Controls which do not support scaling properly:
Label
withAutoSize = False
andFont
inherited. Explicitly setFont
on the control so it appears in bold in the Properties window.ListView
column widths don't scale. Override the form'sScaleControl
to do it instead. See this answerSplitContainer
'sPanel1MinSize
,Panel2MinSize
andSplitterDistance
propertiesTextBox
withMultiLine = True
andFont
inherited. Explicitly setFont
on the control so it appears in bold in the Properties window.ToolStripButton
's image. In the form's constructor:ToolStrip.AutoSize = False
ToolStrip.ImageScalingSize
according toCreateGraphics.DpiX
and.DpiY
ToolStrip.AutoSize = True
if needed.Sometimes
AutoSize
can be left atTrue
but sometimes it fails to resize without those steps. Works without that changes with .NET Framework 4.5.2 andEnableWindowsFormsHighDpiAutoResizing
.TreeView
's images. SetImageList.ImageSize
according toCreateGraphics.DpiX
and.DpiY
. ForStateImageList
, works without that changes with .NET Framework 4.5.1 andEnableWindowsFormsHighDpiAutoResizing
.Form
's size. Scale fixed sizeForm
'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
AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
, assuming 96dpi (see the next bullet). 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. (Setting the Font on a Container seems to turn off the auto-scaling of that container.)
Do NOT use Anchor
Right
orBottom
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 DockRight
or DockBottom
in your UserControl.Only the controls in the Controls lists when
ResumeLayout
at the end ofInitializeComponent
is called will be auto-scaled... if you dynamically add controls, then you need toSuspendLayout();
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 likeFlowLayoutPanel
orTableLayoutPanel
.Base classes derived from
ContainerControl
should leaveAutoScaleMode
set toInherit
(the default value set in classContainerControl
; 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 toFont
will clear out the designer's setting ofAutoScaleDimensions
, 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
andMaxSize
on Form do not scale as much as everything else. So, if you do all your work in 96dpi, then when at higher DPI yourMinSize
won't cause problems, but may not be as restrictive as you expected, but yourMaxSize
may limit your Size's scaling, which can cause problems. If you wantMinSize == Size == MaxSize
, don't do that in the Designer... do that in your constructor orOnLoad
override... set bothMinSize
andMaxSize
to your properly-scaled Size.All of the Controls on a particular
Panel
orContainer
should either use Anchoring or Docking. If you mix them, the auto-scaling done by thatPanel
will often misbehave in subtle bizarre ways.Target your Application for .Net Framework 4.7 and run it under Windows 10 v1703 (Creators Update Build 15063). With .Net 4.7 under Windows 10 (v1703), MS made a lot of DPI improvements.
To support it, add an application manifest to your application and signal that your app supports Windows 10:
Next, add an
app.config
and declare the app Per Monitor Aware. This is NOW done in app.config and NOT in the manifest like before!This PerMonitorV2 is new since Windows 10 Creators Update:
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
Now you can subscribe to 3 new events to get notified about DPI changes:
You also have 3 helper methods about DPI handling/scaling:
Control.LogicalToDeviceUnits, which converts a value from logical to device pixels.
Control.ScaleBitmapLogicalToDevice, which scales a bitmap image to the logical DPI for a device.
Control.DeviceDpi, which returns the DPI for the current device.
If you still see issues, you can opt-out of the DPI improvements via app.config entries.
If you don't have access to source code, you can go to application properties in Windows Explorer, go to compatibility and select
System (Enhanced)
which activates GDI scaling to also improve DPI handling:
Do all those steps and you should get a better DPI experience for WinForms applications. But remember, you need to target your app for .net 4.7 and need at least Windows 10 Build 15063 (Creators Update). In next Windows 10 Update 1709, we might get more improvements.