What is DPI? How is high DPI handled in Windows and WinForms? How can you scale WinForms apps today? Let’s explore the world of high DPI.
In these days of high-density pixel displays, scaling is becoming an increasingly important topic. Let's dive into the specifics of high DPI and try to understand different problems that high DPI displays cause, and how to address them. We will go through the emergence of high DPI displays and how Windows handles high DPI. Then we will introduce the concept of DPI awareness and see the specifics for WinForms. We will see how scaling works in WinForms. And finally we'll wrap up with some tips for designing scalable WinForms and an example.
Dots per inch, more commonly known as DPI, represents the number of dots that can be placed in a one-inch line during printing. When it comes to monitors the proper term is PPI (pixels per inch), but the two terms are mostly interchangeable today.
At the beginning Microsoft Windows used a default display DPI of 96 PPI, and Apple/Macintosh used 72 PPI. While the reasoning behind this comes from the 72 points per inch standard and Microsoft wanting to have 33% more space for better image and font drawing, you can imagine the problems that arose with the new monitors that support higher pixel density.
The solution is the concept of logical PPI. The operating system creates a virtual screen with default DPI (let’s say 96). This virtual screen is used by the software to render its content on. Then the OS draws the virtual screen to the physical one.
Today there are many devices with different DPIs—there are many monitors with 200-300 DPI and smartphones going as high as 800 DPI. This means that if you draw a square of 4x4 pixels, it will look physically smaller on a higher DPI device than on a lower DPI device.
|Display 1: 8x6 pixels
|Display 2: 16x12 pixels
Our 4x4 pixel square looks quite smaller on the second display, where we have four times as many pixels as the first display. But if we are to scale our square by a factor of two on the second display, we will get our desired result.
|Display 1: 8x6 pixels
|Display 2: 16x12 pixels
So how does Windows handle high DPI? Starting with Windows XP, Microsoft introduced the GDI+ library, which allows the scaling of fonts and some other UI elements. Unfortunately, this would mostly make the text bigger in a high DPI setting, which was problematic. Microsoft added some support for GDI/GDI+ scaling recently. It does not work flawlessly—it generally scales fonts and some graphics. When using it you should always be cautious and check if the result is better or not.
Windows Vista introduced DPI awareness and DPI virtualization. An application can declare itself as DPI-aware and scale its own UI elements. If not, the OS will use DPI virtualization, which will render the application using a native 96 DPI as a bitmap and then scale the bitmap on the screen. This would produce correct, but fuzzy rendering of the application. There is also a global option to turn off the virtualization and use the old XP-style rendering. At the same time, WPF appeared with .NET Framework 3.0. Applications designed with WPF natively support DPI scaling. DirectX and vector graphics is used for drawing their elements and scaling comes out of the box. WPF has one limitation—it cannot scale hosted controls.
Windows 7 made DPI per user and enabled easier DPI switching with log off only. The OS also reads the monitor DPI and sets a recommended scaling. The standard settings are 100% (96 DPI), 125% (120 DPI), and 150% (144 DPI). Windows 8 removed the numerical values and added some minor improvements. With Windows 8.1 came per monitor DPI scaling, which was improved by the Windows 10 Creators Update (1703). More scale factors were added as well—175%, 200%, 225% etc. And the user can set a custom scale factor up to 500%.
So far so good. Windows has support for HDPI (high DPI) scaling. But how do we approach it? When building a DPI-aware application, firstly we must declare it as such, so the OS would know how to handle it. Furthermore, you can have DPI awareness per process or per thread.
There are four different DPI awareness modes in Windows.
Here is a list of the different ways you can use to choose DPI awareness mode for an application:
The most common way to declare an application as DPI aware is through the application manifest file. There are two settings that you can use - <dpiAware> and <dpiAwareness>. Here is a small table that describes the different states that you can use with each setting.
Minimum Supported OS
Windows 10, version 1607
You can use both settings in the manifest file. If so, Windows 10, version 1607, will ignore <dpiAware> and use only the newer <dpiAwareness>. Older versions will ignore <dpiAwareness>. Here is how the manifest should look if you want to configure DPI awareness for different Windows versions.
There are three native API calls that can set awareness now:
The latest API offers PerMonitorV2 support, so it is the currently recommend one.
NB! You must set the DPI mode before your window (HWND) has been created.
Another way that was introduced for Windows Forms in .NET Framework 4.7 is in the app.config file. Microsoft added a new element to add different features to a Windows Forms app called <System.Windows.Forms.ApplicationConfigurationSection>. In order to use it you need to do the following:
With the <System.Windows.Forms.ApplicationConfigurationSection> element you can set other DPI features as well. For more information you can check this article.
NB! Using <dpiAware>/<dpiAwareness> in the manifest file overrides the settings defined in the app.config file.
With .NET Core 3.0 Microsoft introduced a new way to set a high DPI mode for Windows Forms. A static method called Application.SetHighDpiMode(HighDpiMode), where HighDpiMode is enum with the following values:
The call must be done at the application entry point. If you have used a manifest file to set the DPI awareness mode, this method call will fail.
Windows 10 Anniversary update, build 1607, added mixed-mode support for DPI scaling. The main idea is to set DPI scaling to top level windows that is different than the DPI scaling of the process. This is done by using the API SetThreadDpiAwarenessContext. You can call the API just before creating a new window. After the window has been created you can call the API again to revert to the original mode. For more information on the topic I would suggest reading this article.
So, when it comes to Windows Forms, you can use a variety of techniques to define DPI awareness, depending on the framework you are using. With the development of .NET Core and the upcoming .NET5 Microsoft is moving towards using a static method inside your application to define HDPI support. But if you are using an older framework, you can safely use the manifest file or the app.config file. Native calls are generally not recommended. Just keep in mind that the manifest file declaration overrides the other declarations.
At the beginning Windows Forms did not support modern scaling. Starting with .NET Framework 4.5.1, Microsoft made improvements for HDPI scenarios in Windows Forms. In .NET Framework 4.5.2 the following controls can be resized using the system DPI setting:
Version 4.6 added support for the following controls:
This behavior can be enabled with a setting in the app.config file:
.NET Framework 4.7 introduced several quality improvements to the DPI scaling in Windows Forms.
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:
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.
NB! All containers must use the same AutoScaleMode. Mixing different scale modes is not supported and can lead to unexpected results.
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. But with the improvements that started with .NET Framework 4.5.1 the scaling has been improved.
For more information check out this article.
As of 2017, Telerik UI for WinForms offers built-in HDPI support. The good news is that you don’t need the latest framework—the feature is supported with .NET Framework 2.0 and up. Check out the blog post and the documentation for more information.
The Telerik 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:
If you are designing a scalable application there are few simple guidelines that will help a lot:
When using Telerik UI for WinForms, you have automatic scaling out of the box even for .NET Framework 2.0. You will just have to mark your application as DPI-aware. You can look at the example here: C#/VB . The examples work for any .NET Framework after version 2.0. You will need to install a Telerik UI for WinForms trial, which you can do through the link below.
WinForms has come a long way since its emergence. At its early days there was no need for DPI scaling. Nowadays, with the development of better displays, scaling is becoming more and more mandatory. And since there are a lot of applications written back in the day using WinForms, there is a need for the platform to grow. Today WinForms has a good HDPI support and with Telerik UI on top you can have seamless HDPI application out of the box.
Editor's Note: This post was originally published in 2014 by Ivan Todorov, and has since been revised and updated for accuracy and completeness.
Stoyan was member of the WinForms team at Progress.
Subscribe to be the first to get our expert-written articles and tutorials for developers!
All fields are required