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:
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.
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:
- Nested
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.
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:
Public Shared Sub ScaleForm(WindowsForm As System.Windows.Forms.Form)
Using g As System.Drawing.Graphics = WindowsForm.CreateGraphics
Dim sngScaleFactor As Single = 1
Dim sngFontFactor As Single = 1
If g.DpiX > 96 Then
sngScaleFactor = g.DpiX / 96
\'sngFontFactor = 96 / g.DpiY
End If
If WindowsForm.AutoScaleDimensions = WindowsForm.CurrentAutoScaleDimensions Then
\'ucWindowsFormHost.ScaleControl(WindowsForm, sngFontFactor)
WindowsForm.Scale(sngScaleFactor)
End If
End Using
End Sub
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.
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