October 24, 2016 2:20 pm

High DPI Scaling Improvements for Desktop Applications and “Mixed Mode” DPI Scaling in the Windows 10 Anniversary Update

By / Program Manager at Microsoft

As display technology has improved over time, the cutting edge has moved towards having more pixels packed into each physical square inch, and away from simply making displays physically larger. This trend has increased the dots per inch (DPI) of the displays on the market today. The Surface Pro 4, for example, has roughly 192 DPI (while legacy displays have 96 DPI). Although having more pixels packed into each physical square inch of a display can give you extremely sharp graphics and text, it can also cause problems for desktop application developers. Many desktop applications display blurry, incorrectly sized UI (too big or too small), or are unusable when using high DPI displays in combination with standard-DPI displays. Many desktop UI frameworks that developers rely on to create Windows desktop applications do not natively handle high DPI displays and work is required on the part of the developer to address resizing application UI on these displays. This can be a very expensive and time-consuming process for developers. In this post, I discuss some of the improvements introduced in the Windows 10 Anniversary Update that make it less-expensive for desktop application developers to develop applications that handle high-DPI displays properly.

Note that applications built upon the Windows Universal Platform (UWP) handle display scaling very well and that the content discussed in this post does not apply to UWP. If you’re creating a new Windows application or are in a position where migrating is possible, consider UWP to avoid the problems discussed in this post.

Some Background on DPI Scaling

Steve Wright has written on this topic extensively, but I thought I’d summarize some of the complexities around display scaling for desktop applications here. Many desktop applications (applications written in raw Win32, MFC, WPF, WinForms or other UI frameworks) can often become blurry, incorrectly sized or a combination of both, whenever the display scale factor, or DPI, of the display that they’re on is different than what it was when the Windows session was first started. This can happen under many circumstances:

  • The application window is moved to a display that has a different display scale factor
  • The user changes the display scale factor manually
  • A remote-desktop connection is established from a device with a different scale factor

When the display scale factor changes, the application may be sized incorrectly for the new scale factor and therefore, Windows often jumps in and does a bitmap stretch of the application UI. This causes the application UI to be physically sized correctly, but it can also lead to the UI being blurry.

In the past, Windows offered no support for DPI scaling to applications at the platform level. When these type of “DPI Unaware” applications are run on Windows 10, they are almost always bitmap scaled by Windows when display scaling is > 100%. Later, Windows introduced a DPI-awareness mode called “System DPI Awareness.” System DPI Awareness provides information to applications about the display scale factor, the size of the screen, information on the correct fonts to use, etc., such that developers can have their applications scaled correctly for a high DPI display. Unfortunately, System DPI Awareness was not designed for dynamic-scaling scenarios such as docking/undocking, moving an application window to a display with a different display scale factor, etc. In other words: The model for system-DPI-awareness is one that assumes that only one display will be in use during the lifecycle of the application and that the scale factor will not change.

In dynamic-scale-factor scenarios applications will be bitmap stretched by Windows when the display-scale-factor changed (this even applies to system-DPI-aware processes). Windows 8.1 introduced support for “Per-Monitor-DPI Awareness” to enable developers to write applications that could resize on a per-DPI basis. Applications that register themselves as being Per-Monitor-DPI Aware are informed when the display scale factor changes and are expected to respond accordingly.

So… everything was good, right? Not quite.

Unfortunately, there were three big gaps with our implementation of Per-Monitor-DPI Awareness in the platform:

  • There wasn’t enough platform support for desktop application developers to actually make their applications do the right thing when the display-scale-factor changed.
  • It was very expensive to update application UI to respond correctly to a display-scale factor changes, if it was even possible to do at all.
  • There was no way to directly disable Window’s bitmap-scaling of application UI. Some applications would register themselves as being Per-Monitor-DPI Aware not because they actually were DPI aware, but because they didn’t want Windows to bitmap stretch them.

These problems resulted in very few applications handling dynamic display scaling correctly. Many applications that registered themselves as being Per-Monitor-DPI Aware don’t scale at all and can render extremely large or extremely small on secondary displays.

Background on Explorer

As I mentioned in another blog post, during the development cycle for the first release of Windows 10 we decided to start improving the way Windows handled dynamic display scaling by updating some in-box UI components, such as the Windows File Explorer, to scale correctly.

This was a great learning experience for us because it taught us about the problems developers face when trying to update their applications to dynamically scale and where Windows was limited in this regard. One of the main lessons learned was that, even for simple applications, the model of registering an application as being either System DPI Aware or Per-Monitor-DPI Aware was too rigid of a requirement because it meant that if a developer decided to mark their application as conforming to one of these DPI-awareness modes, they would have had to update every top-level window in their application or live with some top-level windows being sized incorrectly. Any application that hosts third-party content, such as plugins or extensions, may not even have access to the source code for this content and therefore would not be able to validate that it handled display scaling properly. Furthermore, there were many system components (ComDlg32, for example) that didn’t scale on a per-DPI basis.

When we updated File Explorer (a codebase that’s been around and been added to for some time), we kept finding more and more UI that had to be updated to handle scaling correctly, even after we reached the point in the development process when the primary UI scaled correctly. At that point we faced the same choice other developers faced: we had to touch old code to implement dynamic scaling (which came with application-compatibility risks) or live with these UI components being sized incorrectly. This helped us feel the pain that developers face when trying to adhere to the rigid model that Windows required of them.

Mixed-Mode DPI Scaling and the DPI-Awareness Context

Lesson learned. It was clear to us that we needed to break apart this rigid, process-wide, model for display scaling that Windows required. Our goal was to make it easier for developers to update their desktop applications to handle dynamic display scaling so that more desktop applications would scale gracefully on Windows 10. The idea we came up with was to move the process-level constraint on display scaling to the top-level window level. The idea was that instead of requiring every single top-level window in a desktop application to be updated to scale using a single mode, we could instead enable developers to ease-in, so to speak, to the dynamic-DPI world by letting them choose the scaling mode for each top-level window. For an application with a main window and secondary UI, such as a CAD or illustration application, for example, developers can focus their time and energy updating the main UI while letting Windows handle scaling the less-important UI, possibly with bitmap stretching. While this would not be a perfect solution, it would enable application developers to update their UI at their own pace instead of requiring them to update every component of their UI at once, or suffer the consequences previously mentioned.

The Windows 10 Anniversary Update introduced the concept of “Mixed-Mode” DPI scaling, also known as sub-process DPI scaling, via the concept of the DPI-awareness context (DPI_AWARENESS_CONTEXT) and the SetThreadDpiAwarenessContext API. You can think of a DPI-awareness context as a mode that a thread can be in which can impact the DPI-behavior of API calls that are made by the thread (while in one of these modes). A thread’s mode, or context, can be changed via calls to SetThreadDpiAwarenessContext at any time. Here are some key points to consider:

  • A thread can have its DPI Awareness Context changed at any time.
  • Any API calls that are made after the context is changed will run in the corresponding DPI context (and may be virtualized).
  • When a thread that is running with a given context creates a new top-level window, the new top-level window will be assigned the same context that the thread that created it had, at the time of creation.

Let’s discuss the first point: With SetThreadDpiAwarenessContext the context of a thread can be switched at will. Threads can also be switched in and out of different contexts multiple times.

Many Windows API calls in Windows will return different information to applications depending on the DPI awareness mode that the calling process is running in. For example, if an application is DPI-unaware (which means that it didn’t specify a DPI-Awareness mode) and is running on a display scale factor greater than 100%, and if this application queries Windows for the display size, Windows will return the display size scaled to the coordinate space of the application. This process is referred to as virtualization. Prior to the availability of Mixed-Mode DPI, this virtualization only took place at the process level. Now it can be done at the thread level.

Mixed-Mode DPI scaling should significantly reduce the barrier to entry for DPI support for desktop applications.

Making Notepad Per-Monitor DPI Aware

Now that I’ve introduced the concept of Mixed-Mode, let’s talk about how we applied it to an actual application. While we were working on Mixed Mode we decided to try it out on some in-box Windows applications. The first application we started with was Notepad. Notepad is essentially a single-window application with a single edit control. It also has several “level 2” UI such as the font dialog, print dialog and the find/replace dialog. Before the Windows 10 Anniversary Update, Notepad was a System-DPI-Aware process (crisp on the primary display, blurry on others or if the display scale factor changed). Our goal was to make it a first-class Per-Monitor-DPI-Aware process so that it would render crisply at any scale factor.

One of the first things we did was to change the application manifest for Notepad so that it would run in per-monitor mode. Once an application is running as per-monitor and the DPI changes, the process is sent a WM_DPICHANGE message. This message contains a suggested rectangle to size the application to using SetWindowPos. Once we did this and moved Notepad to a second display (a display with a different scale factor), we saw that the non-client area of the window wasn’t scaling automatically. The non-client area can be described as all of the window chrome that is drawn by the OS such as the min/max/close button, window borders, system menu, caption bar, etc.

Here is a picture of Notepad with its non-client area properly DPI scaling next to another per-monitor application that has non-client area that isn’t scaling. Notice how the non-client area of the second application is smaller. This is because the display that its image was captured on used 200% display scaling, while the non-client area was initialized at 100% (system) display scaling.

image1

During the first Windows 10 release we developed functionality that would enable non-client area to scale dynamically, but it wasn’t ready for prime-time and wasn’t released publicly until we released the Anniversary Update.

We were able to use the EnableNonClientDpiScaling API to get Notepad’s non-client area to automatically DPI scale properly.

Using EnableNonClientDpiScaling will enable automatic DPI scaling of the non-client area for a window when the following conditions are satisfied:

  • The API is called from the WM_NCCREATE handler for the window
  • The process or window is running in per-monitor-DPI awareness
  • The window passed to the API is a top-level window (only top-level windows are supported)

Font Size & the ChooseFont Dialog

The next thing that had to be done was to resize the font on a DPI change. Notepad uses an edit control for its primary UI and it needs to have a font-size specified. After a DPI change, the previous font size was either be too large or too small for the new scale factor, so this had to be recalculated. We used GetDpiForWindow to base the calculation for the new font size:

FontStruct.lfHeight = -MulDiv(iPointSize, GetDpiForWindow(hwndNP), 720);

This gave us a font size that was appropriate for the display-scale factor of the current display, but we next ran into an interesting problem: when choosing the font we ran up against the fact that the ChooseFont dialog was not per-monitor DPI aware. This meant that this dialog could be either too large or too small, depending on the display configuration at runtime. Notice in the image below that the ChooseFont dialog is twice as large as it should be:

image2

To address this, we used mixed-mode to have the ChooseFont dialog run with a system-DPI-awareness context. This meant that this dialog would scale to the system DPI on the primary display and be bitmap stretched any time the display scale factor changed:

DPI_AWARENESS_CONTEXT previousDpiContext = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);
BOOL cfResult = ChooseFont(cf);
SetThreadDpiAwarenessContext(previousDpiContext);

This code stores the DPI_AWARENESS_CONTEXT of the thread and then temporarily changes the context while the ChooseFont dialog is created. This ensures that the ChooseFont dialog will run with a system-DPI-awareness context. Immediately after the call to create the window, the thread’s awareness context is restored because we didn’t want the thread to have its awareness changed permanently.

We knew that the ChooseFont dialog did support system-DPI awareness so we chose DPI_AWARENESS_CONTEXT_SYSTEM_AWARE, otherwise we could have used DPI_AWARENESS_CONTEXT_UNAWARE to at least ensure that this dialog would have been bitmap stretched to the correct physical size.

Now we had the ChooseFont dialog scaling properly without touching any of the ChooseFont dialog’s code but this lead to our next challenge… and this is one of the most important concepts that developers should understand about the use of mixed-mode DPI scaling: data shared across DPI-awareness contexts can be using different scaling/coordinate spaces and can have different interpretations in each context. In the case of the ChooseFont dialog, this function returns a font size based off of the user’s input, but this font size returned is relative to the scale factor that the dialog is running in. When the main Notepad window is running at a scale factor that is different than that of the system scale factor, the values from the ChooseFont dialog must be translated to be meaningful for the main window’s scale factor. Here we scaled the font point size to the DPI of the display that the Notepad window was running on, again using GetDpiForWindow:

FontStruct.lfHeight = -MulDiv(cf.iPointSize, GetDpiForWindow(hwndNP), 720);

Windows Placement

Another place where we had to deal with handling data across coordinate spaces was with the way Notepad stores and reuses its window placement (position and dimensions). When Notepad is closed, it will store its window placement. The next time it’s launched, it reads this information in an attempt to restore the previous position. Once we started running the main Notepad thread in per-monitor-DPI awareness we ran into a problem: the Notepad window was opening in strange sizes when launched.

What was happening was that in some cases we would store Notepad’s size at one scale factor and then restore it for a different scale factor. If the display configuration of the PC that Notepad was run on hadn’t changed between when the information was stored and when Notepad was launched again, theoretically this wouldn’t have been a problem. However, Windows supports changing scale factors, connecting/disconnecting and rotating display at will. This meant that we needed Notepad handle these situations more gracefully.

The solution was again to use mixed-mode scaling, but this time not leverage Window’s bitmap-stretching functionality and instead to normalize the coordinates that Notepad used to set and restore its window placement. This involved changing the thread to a DPI-unaware context when saving window placement, and to do the same when restoring. This effectively normalized the coordinate space across all displays and display scale factors that Notepad would be restored to, approximately the same placement regardless of the display-topology changes:

DPI_AWARENESS_CONTEXT previousDpiContext = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
BOOL ret = SetWindowPlacement(hwnd, wp);
SetThreadDpiAwarenessContext(previousDpiContext);

Once all of these changes were made, we had Notepad scaling nicely whenever the DPI would change and the document text rendering natively for each DPI, which was a big improvement over having Windows bitmap stretch the application on DPI change.

Useful DPI Utilities

While working on Mixed Mode display scaling, we ran into the need to have DPI-aware variants of some commonly used APIs:

A note about GetDpiForSystem: Calling GetDpiForSystem is more efficient than calling GetDC and GetDeviceCaps to obtain the system DPI.

Also, any component that could be running in an application that uses sub-process DPI awareness should not assume that the system DPI is static during the lifecycle of the process. For example, if a thread that is running under DPI_AWARENESS_CONTEXT_UNAWARE awareness context queries the system DPI, the answer will be 96. However, if that same thread switched to DPI_AWARENESS_CONTEXT_SYSTEM and queried the system DPI again, the answer could be different. To avoid the use of a cached — and possibly stale — system-DPI value, use GetDpiForSystem() to retrieve the system DPI relative to the DPI-awareness mode of the calling thread.

What We Didn’t Get To

The Windows 10 Anniversary Update delivers useful API for developers that want to update desktop applications to support dynamic DPI scaling in their applications, in particular EnableNonClientDpiScaling and SetThreadDpiAwarenessContext (also known as “mixed-mode”), but there is still some missing functionality that we weren’t able to deliver. Windows common controls (comctl32.dll) do not support per-monitor DPI scaling and non-client area DPI-scaling is only supported for top-level windows (child-window non-client area, such as child-window scroll bars do not automatically scale for DPI (even in the Anniversary Update)).

We recognize that these, and many other, platform features are going to be needed by developers before they’re fully unblocked from updating their desktop applications to handle display scaling well.

As mentioned in my other post, WPF now offers per-monitor DPI-awareness support as well.

Sample Mixed-Mode Application:

We put together a sample that shows the basics of how to use mixed-mode DPI awareness. The project linked below creates a top-level window that is per-monitor DPI aware and has its non-client area automatically scaled. From the menu you can create a secondary window that uses DPI_AWARENESS_CONTEXT_SYSTEM_AWARE context so that Windows will bitmap stretch the content when its rendered at a different DPI.

https://github.com/Microsoft/Windows-classic-samples/tree/master/Samples/DPIAwarenessPerWindow

Conclusion

Our aim was to reduce the cost for developers to update their desktop applications to be per-monitor DPI aware. We recognize that there are still gaps in the DPI-scaling functionality that Windows offers desktop application developers and the importance of fully unblocking developers in this space. Stay tuned for more goodness to come and get more information on Mixed-Mode DPI Scaling and DPI-aware APIs here.

Download Visual Studio to get started.

The Windows team would love to hear your feedback.  Please keep the feedback coming using our Windows Developer UserVoice site. If you have a direct bug, please use the Windows Feedback tool built directly into Windows 10.

Updated October 27, 2016 3:30 pm

Join the conversation

  1. Not quite sure if this has been discussed but: Why don’t you also scale an app depending on the screen it was launched from instead of depending on the DPI of the screen used to login? This would be a great patch for all the apps that will not be updated any soon.

  2. Thanks for a very informative post Peter!

    One thing I don’t understand still: even in the Anniversary Update, when the DPI changes (such as when docking/undocking) all the applications for which Windows applies bitmap stretching require the user to log out of their Windows session and log back in before they render according to the new API.

    With everything you’ve described in this post, I don’t understand why this should be necessary. I do understand if a restart of the *individual application/process* is necessary, but not a restart of the whole Windows session. It is as if Windows somehow remembers the DPI mode of an application across process restarts for the duration of the logon session, which to me seems like actively counteracting the purpose. Why can’t you simply perform this evaluation whenever a process is started, so that I as a user can simply restart the applications that are not DPI aware, instead of having to restart the whole Windows session and thereby *all* applications?

  3. Aren’t WPF supposed to fix that? I read in a lot of books that WPF would apply the right DPI to controls so it wouldn’t make the program blurry.

  4. The problem is that MS press keep telling everyone that HiDPI is all fixed and it’s just lazy software developers that are to blame. We have been battling Win32 HiDPI for years and have only had minimal success.
    UWP just isn’t ready for kind of applications we write. The list of features missing is long but the inability to create a window is unfortunate on an OS called Windows. It makes dragging off UI tabs practically impossible and having multiple windows that share the same toolbars and chrome are just not possible.
    We updated all of our OSX applications to support retina HiDPI and it only took us a few weeks.
    Come on Microsoft , sort out UWP and we might have a shot at HiDPI.

  5. That is basically what System DPI awareness does.
    System DPI scaling probably works just fine for most people, but it has problems in some situations that can occur in multi-monitor situations or docking situations.

  6. Is there a way to set the per-monitor scaling values programmatically? There doesn’t appear to be a Win32 or PowerShell interface to this feature.

  7. I recently switched to high-DPI monitors (27″ 5120×2880), and noticed quite a few problems with programs that declare themselves as DPI-aware, but actually render way too small (I’m using 225% [216DPI] for DPI settings, because I found fonts too small at 200%; previously I used 30″ 2560×1600+24″ 1920×1200, both set to 125%). While there is a compatibility setting to disable bitmap scaling for non-DPI-aware programs, what I’d really need would be the opposite – force DPI scaling (I was actually thinking about this – I’d like it the most if I could simply set the DPI the program should be rendered at and a scaling factor).

    And speaking of high-DPI problems in Explorer, just a few days ago I found this: https://eternallybored.org/imgs/compstuff/HighDPI/ACLs.png – this happens if you try changing permissions on a file for which Explorer has to elevate to display them, however it only happens if you open the elevated dialog, close it, then immediately open it again.

  8. Great post, thanks. One problem we’re facing with DPI scaling is the maxium height of 255 for CCombox control. The problem is documented here https://msdn.microsoft.com/en-us/library/d7k29sak.aspx

    We have lots of Combobox controls with images and image height can be more about 150 pixels by default. When we scale the control for high DPI monitors it will go over the maximum of 255. Will this limit be removed in the near future? Thanks.

  9. Somebody needs to put together a compatibility matrix that shows the high DPI api feature differences across Windows 7, 8, 8.1, 10, and 10 Anniversary. In general we need a lot more reference material on this stuff. Your blog is a step in the right direction but it seems a bit “light-weight”. Neither the blog nor any other google hits have helped me explain why I get obscure E_ACCESSDENIED when using certain of these high DPI calls. I sure wish these API’s came with a better error messages – in plain English.

    I’m trying to write a simple line-of-business WPF app and, in the initial phases, I don’t want to have to worry about testing DPI awareness for proper behavior. But I can’t for the life of me figure out how to disable DPI awareness in WPF on the Windows 10 anniversary update. All the API calls are rejecting me. Therefore the users with their fancy custom scaling factors are having all kinds of font display issues with the WPF app. These same users say that they don’t have any display issues in their primitive, pixel-based apps (WinForms). The problems are especially bad in some places like Citrix (possibly because of a proprietary font rendering “optimization” called “local text echo”).

    Sorry for the rant. I just think the progression of this high DPI technology has been managed poorly and should have a lot more documentation. If Citrix is screwing this stuff up too, then you need to work more closely with your partners to get them on the same page. Maybe you should set up a help desk at Microsoft 1-800-HIGH-DPI that helps programmers work thru the issues in your high DPI API.

    And I’d certainly recommend you create a managed .Net layer to wrap around this madness. In that managed layer you should consider throwing meaningful exceptions with clear messages in plain English.

  10. I read that in insider build 14986 the HighDPI support addresses system apps like mmc.exe (event viewer, device manager) to display crisp text. I cannot see a change in my setup: Laptop with 17″ FullHD set to 125% DPI scaling and external display with 24″ 1920×1200 set to 100%:

    Device Manager shows crisp on the external display with 100% DPI scaling and blurry on the internal set to 125%DPI. Only the text in the window title is crisp, not the content inside the window.

    Please give us an easy option to control when scaling is allowed for apps, even for system apps like mmc. I have no problem to look at an unscaled mmc on a 125% display. But blurry text looks really bad.

    If I had a 4K display and use >= 200% DPI I guess scaling of mmc would be important to get it at a readable size. But not when scaling is just at 125%.

    My preferred solution would be to change the scaling of window content at *runtime* from the window menu. Especially for mmc this would by useful as it hosts snap-ins which might behave differently when automatic content scaling is applied.

    Thank you very much for all your efforts in this complex part of UI presentations!

  11. Is there something I can do in my application for checkboxes and radio buttons or is the drawing of the glyph totally an issue with Windows itself or is it an issue that needs to be solved by Microsoft?

    I have a large MFC application that I have made High DPI aware.  I have all my windows and dialogs scaling based on the dpi of the monitor they are being displayed.  The windows 10 anniversary update helped with things like non client scaling, but I have some big issues with checkboxes and radio buttons.  The fonts on these controls have been scaled correctly. but the glyphs for the square or circle are either too large or too small.  If the main display is at 250% (4k) and another display is at 100% the checkbox square is WAY too big on the other display on both windows and dialogs.  If the main and other display scale factor is switched, then the checkbox square is too small.

    Reference articles used:
    https://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspx
    https://blogs.technet.microsoft.com/askcore/2015/12/08/display-scaling-in-windows-10/
    https://blogs.windows.com/buildingapps/2016/10/24/high-dpi-scaling-improvements-for-desktop-applications-and-mixed-mode-dpi-scaling-in-the-windows-10-anniversary-update/#Hra3ASt46fOszMRg.97