High-DPI Scaling Improvements for Desktop Applications in the Windows 10 Creators Update
In the previous blog post about High-dots-per-inch (DPI) scaling improvements, we talk about how desktop applications can be blurry or sized incorrectly when run on high-DPI displays. This is especially noticeable when docking and undocking or when using remoting technologies such as Remote Desktop Protocol (RDP).
In the Windows 10 Anniversary Update we chipped away at this problem by introducing mixed-mode DPI scaling and other high-DPI-related APIs. These APIs made it less expensive for developers to update desktop applications to handle dynamic DPI situations (situations where desktop applications are expected to detect and respond to DPI changes at runtime). We’re still working on improving the high-DPI story for you and in this article we’ll go over some of the improvements coming in the Windows 10 Creators Update. Before we dive into that, here’s a quick recap of the issue:
The image above illustrates the types of issues you’ll see in Windows 10 when using multiple displays with different DPI values. In this case a low-DPI primary (“main”) display docked to a high-DPI external display. In this picture you can see:
- Some applications (Word) render blurry on the high-DPI display
- Some applications (PowerPoint and Skype for Business) are crisp but render at the wrong size
- Desktop icons are sized incorrectly on the high-DPI display
- Tooltips are sized incorrectly
- The desktop watermark is sized incorrectly
Note that these are just a few of the types of issues and that all the items that are too small in this picture could easily be too large if the display topology was reversed (high-DPI primary and low-DPI external display). Spoiler alert: many (but not all) of these issues have been fixed in the Creators Update.
Developer Improvements in the Creators Update
The high-DPI improvements in the Creators Update fall in two categories:
- Improvements for desktop application developers
- Improvements for end users
Let’s talk about the developer-focused improvements first. For Microsoft to be successful in reducing the number of blurry or incorrectly sized desktop applications that end-users see, updating desktop applications to handle dynamic DPI scaling properly needs to be as easy as possible for you. We are approaching this is by incrementally adding automatic per-monitor DPI scaling to desktop UI frameworks. Here are some of the improvements in the Creators Update:
Per-monitor DPI awareness V2
The Anniversary Update introduced the concept of mixed-mode DPI scaling which let you have different DPI-awareness contexts (modes) for each top-level window within a process, and these contexts could be different than the process-wide default. This enabled you to ease in to the world of per-monitor scaling by focusing on the parts of the UI that matter the most while letting Windows handle bitmap stretching other top-level windows. In the Creators Update we added a new awareness context, (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) which we refer to as per-monitor version 2 (PMv2).
PMv2 is technically a DPI_AWARENESS_CONTEXT and not one of the process-wide DPI awareness modes defined in the PROCESS_DPI_AWARENESS enumeration. PMv2 is designed to provide per-monitor scaling functionality that is missing from the original implementation of per-monitor awareness. This context enables the following:
Child window DPI change notifications
When a top-level window or process is running in per-monitor (V1) mode, only the top-level window is notified if the DPI changes. If you want to pass this notification to child windows that have their own window procedures, it’s up to you to pass along this notification. With PMv2 all child HWNDs in an HWND tree are notified in a bottoms-up, then tops-down manner via two new messages:
WM_DPICHANGED_BEFOREPARENT (bottoms-up)
WM_DPICHANGED_AFTERPARENT (tops-down)
The WPARAM and LPARAM parameters are unused and zero for both of these messages. They are simply nudges to notify your child HWNDs that a DPI change is occurring and has occurred.
Scaling of non-client area
Prior to the Anniversary Update there was no way to have the Windows-drawn non-client area DPI scale (caption bar, system menus, top-level scroll bars, menu bars, etc.). This meant that if you created a per-monitor application you’d be left with incorrectly sized (too big or too small) non-client area after the DPI change without any recourse other than drawing all that stuff yourself. In the Anniversary Update we added an API that you could call to turn on non-client scaling, EnableNonClientDpiScaling but now with PMv2 you get this automatically.
Automatic DPI scaling for dialogs
Win32 dialogs (dialogs that are created from a dialog template via the CreateDialog* functions) did not DPI scale previous to the Creators Update. With PMv2 these dialogs will automatically DPI scale when the DPI changes. Be aware that Windows will only DPI scale the content that is defined in the dialog template. This means that additional content that is added or manipulated outside of the dialog template will not automatically scale.
Fine-grained control over dialog scaling
There are scenarios where you’ll want control over how Windows DPI scales dialogs or even children HWNDs of a dialog. When you want to opt a dialog or an HWND in a dialog out of automatic DPI scaling you can use SetDialogDpiChangeBehavior/SetDialogControlDpiChangeBehavior, respectively.
typedef enum DIALOG_DPI_CHANGE_BEHAVIORS { DDC_DEFAULT = 0x0000, DDC_DISABLE_ALL = 0x0001, DDC_DISABLE_RESIZE = 0x0002, DDC_DISABLE_CONTROL_RELAYOUT = 0x0004, } DIALOG_DPI_CHANGE_BEHAVIORS; BOOL WINAPI SetDialogDpiChangeBehavior(HWND hDlg, DIALOG_DPI_CHANGE_BEHAVIORS mask, DIALOG_DPI_CHANGE_BEHAVIORS values); typedef enum DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS { DCDC_DEFAULT = 0x0000, DCDC_DISABLE_FONT_UPDATE = 0x0001, DCDC_DISABLE_RELAYOUT = 0x0002, } DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS; BOOL WINAPI SetDialogControlDpiChangeBehavior(HWND hWnd, DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS mask, DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS values);
NOTE: SetDialogControlDpiChangeBehavior only affects first-level children of a PMv2 dialog. If you have a more complex dialog tree you’ll have to handle the HWND scaling of child windows yourself.
With these APIs you can opt a specific window in a dialog (or the entire dialog itself) out of DPI scaling functionality. When your dialog is DPI scaled by the system a new font is sent to all HWNDs in the dialog. You might, for example, want to opt out of having Windows send a DPI-scaled font to a specific HWND. You could use SetDialogControlDpiChangeBehavior in this case.
Use GetDialogDpiChangeBehavior and GetDialogControlDpiChangeBehavior to query the scaling behaviors applied to a dialog or to specific dialog HWNDs, respectively.
Windows Common Control improvements
When a DPI changed in per-monitor version 1 (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE) you could resize and reposition your HWNDs, but if any of these HWNDs were Windows common controls (pushbuttons, checkboxes, etc.) Windows would not redraw the bitmaps (which we refer to as “theme drawn parts” or “theme parts” because they’re drawn by UXTheme) for these buttons. This meant that the bitmaps were either too large or too small, depending on your display topology. There wasn’t really anything that you could do about this, short of drawing the bitmaps yourself.
PMv2 helps draw these bitmaps at the correct DPI. Below we’re showing how the bitmaps in common controls in per-monitor version 1 wouldn’t DPI scale properly and how they do DPI scale if they’re in a PMv2 context:
PMv1 |
Common Control theme parts rendering larger than expected when the window was moved from a high-DPI display (200% scaling) to a low-DPI display (100% scaling) |
PMv2 |
Common control theme parts rendering at the correct size after the window was moved from a high-DPI display (200% scaling) to a low-DPI display (100% scaling) |
Specifying DPI awareness fallback behavior
It is recommended that you specify the DPI awareness of your process in the application manifest. In the Anniversary Update we introduced a new DPI awareness tag, <dpiAwareness>. With this tag you can specify a fallback behavior for the process-wide default DPI awareness mode or context. This is useful in that you can specify different DPI awareness modes for when your application is run on different version of Windows. For example, the following manifest settings:
<dpiAware>True/PM</dpiAware> <dipAwareness>PerMonitorV2, PerMonitor</dpiAwareness>
This will result in the following process-wide default on different versions of Windows:
Windows Version | Applied process-wide DPI awareness mode default | Default DPI awareness context for new windows |
Vista | System | N/A |
Windows 7 | System | N/A |
Windows 8 | System | N/A |
Windows 8.1 | Per Monitor V1 | N/A |
Windows 10 (1507) | Per Monitor V1 | N/A |
November Update (1511) | Per Monitor V1 | N/A |
Anniversary Update (1607) | Per Monitor V1 | Per Monitor V1 |
Creators Update (1703) | Per Monitor V1 | Per Monitor V2 |
Programmatically setting the process-wide default context
SetProcessDpiAwarenessContext(…) lets you specify the default DPI awareness context for your process programmatically. SetProcessDpiAwareness wouldn’t accept a DPI awareness context so there was no way to specify PMv2 as your process-wide default awareness context programmatically.
End-user DPI-scaling improvements
In addition to the work that we’re doing to make it easier for you, as a developer, to update your desktop applications to be per-monitor DPI aware, we’re also working to make your life as a Windows user better where it comes to using Windows in mixed-DPI environments. Here are some of the improvements that are part of the Creators Update:
DPI-scaling overrides
There are times when a Windows desktop application is defined to run in a certain DPI-awareness mode, such as System DPI awareness, but for various reasons you’d need it to run in a different mode. One scenario for this is when you’re running desktop applications that don’t render well on a high-DPI display (this can happen if the developer hasn’t tested and fixed up their applications for the DPI scale factors seen on the latest hardware). In situations like this you might want to force the application to run as a DPI-unaware process. Although this would result in the application being blurry it would at least be sized correctly. There are situations where this can make an unusable application usable, albeit blurry. You can enable this functionality in the .exe properties:
There are three settings that you can specify:
Setting | Effect |
Application | This forces the process to run in per-monitor DPI awareness mode. This setting was previously referred to as “Disable display scaling on high-DPI settings.”
This setting effectively tells Windows not to bitmap stretch UI from the exe in question when the DPI changes |
System | This is Windows’ standard way of handling system-DPI aware processes. Windows will bitmap stretch the UI when the DPI changes |
System (Enhanced) | GDI Scaling (see below) |
“System (enhanced)” DPI scaling
While we want to make it as easy as possible for you to update your applications to handle DPI scaling properly, we fully recognize that there are many applications that can never be updated, for various reasons. For this class of applications, we’re looking at ways that Windows can do a better job of automatically DPI scaling. In the Creators Update you’ll see the first version of this work.
There is new functionality in the Creators Update that results in text and primitives rendering crisply on high-DPI displays for GDI-based apps only. 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. Now keep in mind that this solution is not a silver bullet. There are some limitations:
- GDI+ content doesn’t DPI scale
- DX content doesn’t DPI scale
- Bitmap-based content won’t be crisp
- It won’t be possible for end users to determine which apps will benefit from this feature without trying it out on an app-by-app basis.
Even with all of these limitations, when this GDI scaling works it’s quite impressive. So much so that we’ve turned it on by default for some in-box apps. The Microsoft Management Console (mmc.exe) will be GDI scaled, by default, in the Creators Update. This means that many in-box Windows snap ins, such as Device Manager, will benefit from this feature in the Creators Update. Here are some screenshots:
A DPI-unaware application running on a Surface Book High-DPI display (200% DPI scaling) | The same application being GDI scaled (“System (Enhanced)” DPI scaling) |
Notice how the text is crisp in the GDI-scaled version of the application (on the right) although the bitmaps are blurry (because they’re being bitmap stretched from a low-DPI to high)
What’s more, this functionality can be turned on for non-Windows application in the exe properties via the “System (Enhanced)” compatibility setting under “Override high-DPI scaling behavior. Scaling performed by:” (under the compatibility tab of the properties for an exe. See the screenshot earlier in this post). Note that this option isn’t available for Microsoft applications that ship in-box with Windows.
Internet Explorer
Per-Monitor DPI awareness support was first introduced in Windows 8.1 but many in-box Windows applications did not properly adopt this DPI scaling model. One of the most notable offenders was Internet Explorer. In the Creators Update IE has been updated to dynamically DPI scale.
Before the Creators Update, if you moved Internet Explorer to a display with a different DPI or otherwise changed the DPI of the display that it was on (docking/undocking/settings change/RDP/etc.) the content of the web page you were viewing would DPI scale but the app frame would not. In the image below we’re showing Internet Explorer and Edge, side by side, while being run on a secondary display with 100% display scaling. In this scenario, the primary display was using a high-DPI scale factor and then the app windows were moved to the low-DPI secondary display. You’ll see that the Edge UI scaled down but the Internet Explorer frame still rendered at the scale factor of the primary display.
Notice:
- The text in the navigation bar is huge
- The navigation buttons are huge
- The vertical scroll bar is huge
- The minimize/maximize/close buttons are rendered at the correct size but are spaced out as if they were on the high-DPI display
Here is how things look in the Creators Update:
Desktop icons
One of the biggest complaints we’ve heard (and experienced ourselves) was that the desktop icons would not DPI scale if you were running in “extend” display mode with multiple displays containing different DPI/display scaling values. Updates in the Creators Update fix this issue. Here is what you’d see before the Creators Update:
And here is what you’ll see in the Creators Update:
Office sizing issues
Although this isn’t specifically tied to the Creators Update, Office 2016 with the latest updates no longer has issues with Skype for Business and PowerPoint being sized incorrectly in mixed-DPI configurations.
What we didn’t get to
Hopefully you’ll enjoy these improvements, both as a developer and as an end user. While we’ve done a ton of work and are hopefully moving the needle, so to speak, on how Windows handles dynamic DPI support there is still a lot of work ahead of us. Here are a few things that we still need to work on:
High-DPI developer documentation
As of writing this blog, the high-DPI documentation available on MSDN is horribly outdated. The guides for writing per-monitor DPI aware applications were written in the Windows 8.1 timeframe and haven’t seen any significant updates since then. Also, many Windows API have DPI sensitivities that are not documented. That is to say that some Windows API will behave differently if they’re being called from a system-DPI-aware context vs. a per-monitor DPI aware context. All of this will need to be cleaned up and well documented so that you’re not left guessing when trying to update your application. We’re working on it, so keep an eye out for some improvements here.
“Magic numbers”
There are things in Windows that when scaling to higher DPI, don’t get it quite right. The reason for this is lurking “magic numbers” in the code that assumes that the world is always running at 96 DPI. While, in the past, some of these “magic numbers” didn’t result in significant problems, as DPI increases these issues become more and more noticeable. We’ll need to scrub Windows for as many of these as we can find. A couple of places that you can see “magic numbers” in use are 1) the width of a Windows button border and 2) the padding in non-client menu bars (such as can be seen in Notepad). In the case of the menu bar, as you run Notepad on higher and higher DPI values, the space between each menu item decreases.
Child-window DPI scaling
In the Windows 10 Anniversary Update we introduced mixed-mode DPI scaling (sub-process DPI scaling) that enabled you to have different DPI scaling modes within each top-level window in an application. We do not, however, support child-window DPI scaling. There are many interesting scenarios where this could come in handy, such as when you’re hosting external HWND content that could be running in a different DPI awareness mode. If you don’t have the ability to control the code that generates that hosted content it would be nice to have Windows DPI scale it for you. Unfortunately, this is not supported today.
MFC
Although we’ve added per-monitor DPI scaling support to Win32, WPF and WinForms, MFC does not natively support this functionality.
Display settings page & mouse input
When you have multiple displays with different DPI/Display Scaling values, the Display Settings Page will show you a representation of your display topology. The displays are represented in terms of their resolution, whereas it might be more if they were represented per their physical/real-world dimensions.
Furthermore, when you have two displays that are nearly the same physical size but with different display scaling values (one 4K and one 1080p display, for example) and move the mouse between the displays you can hit “dead zones” where the mouse cannot move. Here it would be more intuitive to have the mouse movement reflect the physical size/dimensions of the displays, not the resolutions of the displays.
Dragging Windows
When you drag an application window from one display to a display with a different DPI, you’ll see an oddly shaped representation of the window. Part of the window will render at the wrong DPI for the display that it’s on, until you move enough of it to the new display. At that point the window snaps to the new DPI and looks odd on the display that it came from. It would be nice to have a smoother transition here.
The need to log out and back in
Most of all, the fact that you have to log out and back into Windows to get most applications to render correctly after a DPI change is a huge pain-point. We do not currently have a solution for this beyond having applications updated to support per-monitor DPI awareness.
Conclusion
Again, hopefully you’ll enjoy these improvements. We still have a long way to go but we are working on improving Windows 10 high-DPI functionality and support. Do you have any other ideas of things that you’d like to see in this space? If so, please leave a comment below or reach out @WindowsUI.
Source: High-DPI Scaling Improvements for Desktop Applications in the Windows 10 Creators Update
Leave a Reply