To make your application dpi-aware, you must cancel automatic dpi scaling, and then adjust user interface elements to scale appropriately to the system dpi.
The AutoScaleDimensions property represents the DPI or font setting of the screen that the control was scaled to or designed for. Specifically, at design time this property will be set by the Windows Forms designer to the value your monitor is currently using.
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
propertiesTextBox
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:
ToolStrip.AutoSize = False
ToolStrip.ImageScalingSize
according to CreateGraphics.DpiX
and .DpiY
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.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.)
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 before Font
. 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.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.
Starting with the .NET Framework 4.7, Windows Forms includes enhancements for common high DPI and dynamic DPI scenarios. These include:
Improvements in the scaling and layout of a number of Windows Forms controls, such as the MonthCalendar control and the CheckedListBox control.
Single-pass scaling. In the .NET Framework 4.6 and earlier versions, scaling was performed through multiple passes, which caused some controls to be scaled more than was necessary.
Support for dynamic DPI scenarios in which the user changes the DPI or scale factor after a Windows Forms application has been launched.
To support it, add an application manifest to your application and signal that your app supports Windows 10:
<compatibility xmlns="urn:schemas-microsoft.comn:compatibility.v1">
<application>
<!-- Windows 10 compatibility -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
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!
<System.Windows.Forms.ApplicationConfigurationSection>
<add key="DpiAwareness" value="PerMonitorV2" />
</System.Windows.Forms.ApplicationConfigurationSection>
This PerMonitorV2 is new since Windows 10 Creators Update:
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
Also known as Per Monitor v2. An advancement over the original per-monitor DPI awareness mode, which enables applications to access new DPI-related scaling behaviors on a per top-level window basis.
Child window DPI change notifications - In Per Monitor v2 contexts, the entire window tree is notified of any DPI changes that occur.
Scaling of non-client area - All windows will automatically have their non-client area drawn in a DPI sensitive fashion. Calls to EnableNonClientDpiScaling are unnecessary.
Scaling of Win32 menus - All NTUSER menus created in Per Monitor v2 contexts will be scaling in a per-monitor fashion.
Dialog Scaling - Win32 dialogs created in Per Monitor v2 contexts will automatically respond to DPI changes.
Improved scaling of comctl32 controls - Various comctl32 controls have improved DPI scaling behavior in Per Monitor v2 contexts.
Improved theming behavior - UxTheme handles opened in the context of a Per Monitor v2 window will operate in terms of the DPI associated with that window.
Now you can subscribe to 3 new events to get notified about DPI changes:
Control.DpiChangedAfterParent, which is fired Occurs when the DPI setting for a control is changed programmatically after a DPI change event for it's parent control or form has occurred.
Control.DpiChangedBeforeParent, which is fired when the DPI setting for a control is changed programmatically before a DPI change event for its parent control or form has occurred.
Form.DpiChanged, which is fired when the DPI setting changes on the display device where the form is currently displayed.
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:
For applications that are GDI-based Windows can now DPI scale these on a per-monitor basis. This means that these applications will, magically, become per-monitor DPI aware.
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.
A guide I wrote at work:
WPF works in 'device independent units' which means all controls scale perfectly to high dpi screens. In WinForms, it takes more care.
WinForms works in pixels. Text will be scaled according to the system dpi but it will often be cropped by an unscaled control. To avoid such problems, you must eschew explicit sizing and positioning. Follow these rules:
- Wherever you find it (labels, buttons, panels) set the AutoSize property to True.
- For layout, use FlowLayoutPanel (a la WPF StackPanel) and TableLayoutPanel (a la WPF Grid) for layout, rather than vanilla Panel.
- If you are developing on a high dpi machine, the Visual Studio designer can be a frustration. When you set AutoSize=True, it will resize the control to your screen. If the control has AutoSizeMode=GrowOnly, it will remain this size for people on normal dpi, ie. be bigger than expected. To fix this, open the designer on a computer with normal dpi and do right-click, reset.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With