Also being late in the game, I would like to hint on two approaches that also work with WinForms. Which approach to take depends on the use case.
Approach A: Control.PreferredSize
Each control has a "designed" size, typically done with default scaling, i.e. 100%. Let's take a simple auto-sized label, e.g. 116 pixels wide and 13 pixels in height. Break the first time the parent's Paint
event is invoked: At 125%, the label's PreferredSize
reports e.g. {Width = 155 Height = 17}
. Hmm... 133% rather than 125%? AutoScaleMode.Font
apparently takes other things into account.
Now let's do the same with a control that has no text, e.g. 245 pixels wide and 295 pixels in height. After InitializeComponent()
, the control's constructor will still state these values. Again break the first time the parent's Paint
event is invoked: At 125%, the control's PreferredSize
report e.g. {Width = 342 Height = 363}
. The width is limited by the parent, by the height is free to scale, 363/295 = 1.23, i.e. pretty close to the 125% settings.
While this approach doesn't give the exact scaling value, it gives exact values how controls get scaled by WinForms.
Approach B: Control.DeviceDpi
According to https://learn.microsoft.com/en-us/windows/win32/learnwin32/dpi-and-device-independent-pixels, 100% scaling is 96 DPI:
const int DefaultDpi = 96;
Any form or control can easily calculate the current scaling:
var scaling = (float)DeviceDpi / DefaultDpi; // = 1.25 with a 125% setting
The default of 96 DPI should also works on Linux, e.g. https://wiki.archlinux.org/title/HiDPI.