Finally, it is necessary to change the code that is executed with the
WM_PAINT message in the
WndProc function with the following code that calls the
case WM_PAINT: RenderWindow(hWnd); break;
The result of all this code is simple a window that scales its size and its fonts to display crisp and clear text for the different DPI levels (see Figure 1).
Figure 1: The sample application window scaled to 192 DPI and displaying crisp text.
Creating a Per-Monitor DPI Aware Win32 Application
If you move the window to a monitor with a different DPI setting, DWM will scale up or down the application and you will see blurry text on the new display. If I want this simple application to adapt itself to a different display or to dynamic changes in the DPI settings, it is necessary to change its manifest file to convert it to a per-monitor DPI aware application and make a few small changes in the code to listen for changes in the DPI settings or scale factor. The use of a scale helper class will make it easy to perform the conversion.
The following lines add the code that has to be executed with the
WM_DPICHANGED message in the
WndProc function. This message tells the program that most of its window is on a monitor with a new DPI setting. The
wParam contains the new DPI setting and the
lParam contains a window rectangle scaled for the new DPI. It is necessary to ask the scale helper to recalculate the scale based on the new DPI setting, resize the window, create scaled fonts, and render again the window content.
case WM_DPICHANGED: // wParam = New DPI setting // lParam = Window rectangle scaled for the new DPI // Use the new DPI retrieved from the wParam to calculate the new scale factor g_ScaleHelper.SetScaleFactor(LOWORD(wParam)); // Get the window rectangle scaled for the new DPI, retrieved from the lParam LPRECT lprcNewScale; lprcNewScale = (LPRECT)lParam; // Resize the window for the new DPI setting SetWindowPos(hWnd, HWND_TOP, lprcNewScale->left, lprcNewScale->top, RectWidth(*lprcNewScale), RectHeight(*lprcNewScale), SWP_NOZORDER | SWP_NOACTIVATE); // Create new scaled fonts for the new DPI setting CreateFonts(hWnd); // Render the window contents again RedrawWindow(hWnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE); break;
Right-click on the project name (
DPIAware) within Solution Explorer and select Properties. Visual Studio will show the Property Pages dialog box for the project. Then, select Configuration Properties | Manifest Tool | Input and Output | DPI Awareness, select Per Monitor High DPI Aware from the dropdown menu, and click OK. Execute the application, and perform one of the following actions:
- Change the DPI settings for the display in which the application is showing the window.
- Move the application window to another display with a different DPI setting than the display in which the application started.
You will see how the application changes its window, button, and text sizes and keeps displaying crisp text, taking full advantage of the new DPI setting (see Figure 2).
Figure 2: The sample application window scaled to 240 DPI and displaying crisp text.
However, there is a small problem with the value generated for the manifest file. Now, the application is per-monitor DPI-aware, but in Windows versions prior to 8.1, that value isn't recognized. Thus, Windows Vista, Windows 7, and Windows 8 will consider the application as a DPI-unaware application and the DWM will scale it. Unless you only target Windows 8.1, you won't want this to happen.
You can solve this problem creating an XML manifest file with a special value for
True/PM. This value sets the application to per-monitor DPI-aware on Windows 8.1, but sets the application to system DPI-aware on Windows Vista through Windows 8. This way, your application will behave as a system DPI-aware one on Windows versions lower than 8.1. The following lines show the XML code for the manifest you have to create:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware> </windowsSettings> </application> </assembly>
You need to save the XML manifest and tell Visual Studio to merge it. Right-click on the project name (
DPIAware) within Solution Explorer and select Properties. Visual Studio will show the Property Pages dialog box for the project. Then, select Configuration Properties | Manifest Tool | Input and Output | DPI Awareness, and select None. You don't want Visual Studio to specify and DPI awareness configuration in the manifest because you provide it with the manifest file. Then, specify the full path for the XML manifest file you saved in Additional Manifest Files and click OK. Now, the application will work as a per-monitor DPI aware application in Windows 8.1, and as a system DPI aware application in previous Windows versions.
As you can see from this simple example, if you code a scale helper class, it is pretty easy for you to convert a system DPI-aware application into a per-monitor DPI aware one. You can use either the retrieved DPI value or the calculated scale factor to load the necessary resources that have been prepared for the different scale factors you want to support. End users will definitely welcome your investment in providing crisp and clear text and images for their different high-DPI displays.
Gastón Hillar is a senior contributing editor at Dr. Dobb's.