As you have probably heard, Microsoft introduced support for DPI scaling per monitor in Windows 8.1. You have also probably noticed that after you’ve upgraded to Windows 8.1, a number of your applications got blurry on high-DPI displays. With this blog, we will try to explain why this happened and how the scaling works both in the context of the operating system and in the context of WinForms. We will also try to give you some hints of how to develop DPI-aware WinForms applications.
Let’s start with explaining what is DPI. DPI stands for Dots per Inch and indicates the number of pixels your monitor has on a linear inch. In other words, this is the pixel density of the monitor. In the past almost all monitors had the same density of 96 DPI. However, as the density increased and the UI shrank, the need of UI scaling at operating system level arose. This problem can be easily solved in platforms like WPF or Direct2D where the measurement unit is not device dependent. However, this is not the case with GDI, GDI+ and WinForms where the device dependent pixel is the main measurement unit. This means that if you define a button which is 100 pixels wide and you run the form on a 140 DPI display, the button will look smaller than it looks on a 96 DPI screen.
Now let’s go through a brief history of how scaling worked in previous versions of Windows and how it evolved over the years.
The first implementation of any kind of scaling dates back to Windows XP. It basically scales only system fonts and some system UI elements like titlebars, scrollbars, etc. leaving the rest of the interface intact. So when you move your application to a higher DPI environment, the only difference is that the text suddenly gets bigger. You might imagine what type of problems this will bring. This method is called “XP style scaling” due to the version of Windows it was introduced in.
An improved scaling system called “DPI Virtualization” is released with Windows Vista. When your application doesn’t explicitly state that it can handle different DPI settings on its own, the operating system puts it in a sandbox and renders it in its native resolution of 96 DPI. The result of the render is taken as a bitmap and it is scaled up. The output as you may guess is a blurry and fuzzy image and therefore this method is only used when the DPI setting is higher than 120. You also have the option to disable this for the entire system via the checkbox “Use XP style scaling”. Applications that want to handle scaling on their own must declare that in a manifest file which we will explain in detail later.
Windows 7 and Windows 8 use the same as the method above. Windows 8.1 introduces a new “DPI scaling per monitor” feature. It detects the pixel density of each of your screens and sets the best DPI for it. This enables you to have your high-density full HD 13 inch laptop attached to your normal-density 24 inch desktop monitor and avoid the enormously large interface one would expect on it. The “Use XP style scaling” option is entirely removed and DPI virtualization is more aggressively used. Therefore applications that do not declare themselves as DPI aware always get bitmap-scaled even though they can render properly under high DPI settings. For this reason after the upgrade, even applications like Chrome and Skype got automatically scaled and suddenly appear annoyingly blurry.
So what are the options of creating an application that will be used under different DPI settings?
The first one is to declare our application to be non-DPI-aware (not declaring anything will default to this). In this case, the operating system will render our application under the expected 96 DPI and then will do to the bitmap scaling that we discussed before. The result will be a blurry looking application, but with a correct layout.
The second option is to declare the application as DPI-aware. In this case, the OS will not do any scaling and will let your application render according to the original DPI of the screen. In case of a per-monitor-DPI environment, your application will be rendered with the highest DPI of all the screens, then this bitmap will be scaled down to the proper size for each monitor. Downscaling results in a better viewing experience than upscaling but you might still notice some fuzziness.
If you want to avoid that, you must declare your application as per-monitor-DPI-aware. Then you must detect when your application is dragged across different monitors and render according to the DPI of the current one.
Declaring the DPI awareness is done in a manifest file. If you don’t have such in your project, you should first add one. Then you can use the following markup to set the dpiAware flag. Possible values are True for DPI-aware app, False for non-DPI-aware app and True/PM for per-monitor-DPI-aware app. The code below can be taken from here.
sender, EventArgs e)
Workbook wb =
Process_DPI_Unaware = 0,
Process_System_DPI_Aware = 1,
Process_Per_Monitor_DPI_Aware = 2
Now let’s see how this fits into the context of WinForms. The WinForms platform has its own scaling mechanism which calculates the scaling difference between the system that the form has been designed on and the system it is running on. Then it modifies the size and the location of all controls according to the calculated factor. Note that this scaling will only trigger if your application declares to be DPI-aware, otherwise it will be rendered in the 96 DPI sandbox and the bitmap scaling of the OS will be used.
You have probably noticed the following two properties in you designer generated files:
.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
In the AutoScaleDimensions property the Visual Studio designer will serialize the dimensions of the unit used for comparison (either Font or DPI). These are the dimensions of the unit on the system the form is being designed on. When you run the form on a system with different settings, its dimensions are obtained and compared against the serialized dimensions. The scaling factor is computed based on that and then it is applied.
The AutoScaleMode property indicates the method of calculating the scale factor. Depending on it, the scaling mechanism will calculate the scale factor according to the dimensions of the system font or the system DPI. If you set it to None, no scaling will be performed at all.
When the scale factor is calculated, the framework calls the Scale method of the form which basically recalculates the Size and the Location of all child controls on it. Then their Scale method is also called so they can properly scale.
Unfortunately, this mechanism does not work as seamlessly as the one in WPF where you don’t even have to worry about scaling. As you can imagine, changing the bounds of the controls is not sufficient to perform proper scaling – the content drawn inside the controls also needs to be scaled, as well as any layout calculations that use pixel constants also need to perform scaling. In addition to that, even the built-in scaling of the bounds of the controls might break the layout of the form if it hasn’t been designed with scaling in mind.
Ivan Todorov is a senior software developer working on Telerik UI for WinForms. He joined the company back in 2010 after graduating Telerik Academy. Ivan is now back with the team after taking a short break to spend some months working for a fast-growing startup in the Valley. He holds a bachelor in Computer Science from Sofia University. In his spare time, Ivan is a casual photographer and he enjoys various outdoor activities.
Copyright © 2017, Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
Progress, Telerik, and certain product names used herein are trademarks or registered trademarks of Progress Software Corporation and/or one of its subsidiaries or affiliates in the U.S. and/or other countries. See Trademarks or appropriate markings.