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.

What is DPI?

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.

Scaling Methods

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.

DPI Awareness

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.

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" >
   <asmv3:application>
        <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
             <dpiAware>true</dpiAware>
        </asmv3:windowsSettings>
   </asmv3:application>
</assembly>

DPI Awareness and Document Processing Libraries

Telerik’s document processing library (RadSpreadProcessing, RadWordProecessing or RadPdfProcessing) is referencing assemblies which are used in WPF. All WPF-based applications are DPI-aware by default and this is declared in the manifests of the WPF assemblies. Therefore, if you use the document processing library in WinForms applications that are not DPI-aware, they might suddenly become DPI-aware at run time when you instantiate a type from the DPL assemblies (when the DPL assemblies are loaded by the CLR, this will also load the WPF assemblies which they depend on, which in turn will make the application DPI-aware). If you intend to use your application on machines where the DPI scaling is larger than 100 percent, you should explicitly set the application to be DPI-unaware:
private void workbookTestButton_Click(object sender, EventArgs e)
{
    SetProcessDpiAwareness(_Process_DPI_Awareness.Process_DPI_Unaware);
    Workbook wb = new Workbook();
}
  
[DllImport("shcore.dll")]
static extern int SetProcessDpiAwareness(_Process_DPI_Awareness value);
  
enum _Process_DPI_Awareness
{
    Process_DPI_Unaware = 0,
    Process_System_DPI_Aware = 1,
    Process_Per_Monitor_DPI_Aware = 2
}

DPI Scaling in WinForms

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:

this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.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.

General Tips for Designing Scalable Forms

Nevertheless, there are a few simple guidelines that could help your application scale well in most of the cases.
  • Design the interface of your forms so that it can “reflow” – use Anchored, Docked, AutoSized controls where possible.
  • Design your forms under 96 DPI – as mentioned above, Visual Studio will serialize the scaling size of the form at design time and there are often problems when the form has been designed under higher DPI.
  • If you have some custom layout logic, always keep in mind that the sizes and the locations of the controls will be different if the form is scaled. Also keep in mind that you should manually scale any constants you use if they denote pixels.

A Per-Monitor-DPI-Aware Example

So far so good, but how do we handle multi-monitor environment. Unfortunately, neither WinForms nor WPF can handle the per-monitor DPI scaling out of the box, so we have to do this on our own. The methods that do the scaling in WinForms are public so we can use them in combination with the native API for detecting a monitor change. The native API sends a message to notify you when your form has been dragged to a different monitor. The message is sent when the form has bigger area on the new screen than on the old one. Be careful when handling the border case - when scaling down immediately after the message will cause sending another message. 

You can see this in action in the following samples:
C# / VB

As you will notice, scaling in WinForms requires special handling. Even though the framework scales the controls, it doesn’t scale the font size. You will also need to scale any other sizes and measurement properties like ItemHeight in RadListControl. In conclusion, yes, scaling in WinForms is possible, but it might turn out this is a rather challenging task that needs some effort put on.

Here are the results of placing the same form on my 13 inch laptop (left) and the 24 desktop monitor (right), both with the same resolution of 1920x1080 pixels.

DPI Setting in Multi-Monitor envorinment with Windows8.1


About the author

Ivan Todorov

Ivan Todorov

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.


Related Posts

Comments