Checking the DPI Awareness of Each Process
The latest versions of Process Explorer allow you to easily check the DPI awareness of each process. The default view doesn’t include the DPI Awareness column. You just need to right-click on the column headers, select Select Columns…, check the DPI Awareness checkbox, and click OK. This way, Process Explorer will add the DPI Awareness column and will display the following three possible values for each running process:
- Unaware
- System Aware
- Per-Monitor Aware
I’ll use Process Explorer to make sure that the changes in the DPI Awareness configuration for each new build are reflected in the process.
Creating a System DPI Aware Win32 Application in Visual Studio 2013
You can use the following two mechanisms to set the DPI-awareness level for a process:
- Declaratively: Specify the desired level of awareness in the
dpiAware
entry of the application manifest. This way, DWM sets the specified DPI awareness level when the user launches the application. Visual Studio 2013 enables you to set the DPI-awareness level without having to edit the manifest tool. - Procedurally: Call the
SetProcessDpiAwareness
function with the appropriate value from thePROCESS_DPI_AWARENESS
enumeration. The function must be called before any Win32 API call that makes DWM begin virtualization.
It is a good practice to use the application manifest instead of calling the SetProcessDpiAwareness
function. However, there are specific cases in which you might find it more convenient to make the API call. For the system DPI-aware sample application, I use the recommended process for declaring the DPI awareness level of a sample application and I take advantage of the features included in Visual Studio 2013 to do so. However, it will be necessary to merge a manifest file for compatibility reasons when converting the application to per-monitor DPI aware.
Create a Visual C++ Win32 Project in Visual Studio 2013. I’ll use DPIAware
for the solution name. Make sure “Windows application” is selected in Application type in the Win32 Application Wizard. This way, you will have the basic structure for a C++ Win32 Windows application, and I can focus on the necessary changes in the code for the example. By default, Visual Studio 2013 creates a simple Win32 application with some menu items and an About dialog box. These elements are going to be useful to test how the app works with different DPI aware settings, even before I make any changes to the code.
To follow along, make sure your display settings in the Display Control Panel item use a scaling level higher than 100% (96 DPI) —you need at least 125% (120 DPI). If your settings specify a scaling level equal to 100% (96 DPI), you won’t notice the changes I’ll describe later. However, don’t forget that any changes in the scaling level configuration will have an impact on all your applications.
By default, the generated Win32 application is DPI-unaware. Execute the application and you will notice the text in the menu bar is blurry because the DWM has virtualized the application window and is scaling it. Select Help | About… and you will also notice the text displayed within the About dialog box and the text for the OK button is blurry. If you use Process Explorer to check the DPI Awareness value for the generated executable (DPIAware.exe), you will see the Unaware
value.
Now, stop the execution and 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 High DPI Aware from the dropdown menu and click OK. You can achieve the same effect by merging in a manifest file with the following XML to the application manifest. Notice that the true
value fordpiAware
indicates that the application is system DPI-aware.
1
2
3
4
5
6
7
8
9
|
<? 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 > </ windowsSettings > </ application > </ assembly > |
As I explained earlier, in this case, it is easier to use the setting in the dialog box. I’ll explain how to merge in a manifest file shortly.
Now, execute the application and you will notice the text in the menu bar is crisp and clear. Now, the application is DPI-aware and DWM doesn’t virtualize it. There is no code to scale the fonts, and therefore, the text in the About… dialog box is probably going to be too small. If you use Process Explorer to check the DPI Awareness value for the generated executable (DPIAware.exe), you will see the System Aware
value.
Open the stdafx.h
header file and add a line that includes the ShellScalingAPI.h
header. It is necessary to make a call to the GetDpiForMonitor
API function. The following lines show the resulting code for stdafx.h.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#pragma once #include "targetver.h" #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers // Windows Header Files: #include <windows.h> // C RunTime Header Files #include <stdlib.h> #include <malloc.h> #include <memory.h> #include <tchar.h> // Additional header required to call GetDpiForMonitor, use enumerations and other API calls #include <ShellScalingApi.h> |
It is necessary to add shcore.lib
in the linker dependencies. You just need to right-click on the project name, select Properties, and then Configuration Properties | Linker | Input | Additional Dependencies. Add shcore.lib;
as a prefix to the existing additional dependencies and click OK.
The following lines show the initial code for the DPIAware.cpp source file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
// DPIAware.cpp : Defines the entry point for the application. // #include "stdafx.h" #include "DPIAware.h" #define MAX_LOADSTRING 100 // Global Variables: HINSTANCE hInst; // current instance TCHAR szTitle[MAX_LOADSTRING]; // The title bar text TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name // Forward declarations of functions included in this code module: ATOM MyRegisterClass( HINSTANCE hInstance); BOOL InitInstance( HINSTANCE , int ); LRESULT CALLBACK WndProc( HWND , UINT , WPARAM , LPARAM ); INT_PTR CALLBACK About( HWND , UINT , WPARAM , LPARAM ); int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // TODO: Place code here. MSG msg; HACCEL hAccelTable; // Initialize global strings LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_DPIAWARE, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance); // Perform application initialization: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_DPIAWARE)); // Main message loop: while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return ( int ) msg.wParam; } // // FUNCTION: MyRegisterClass() // // PURPOSE: Registers the window class. // ATOM MyRegisterClass( HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize = sizeof (WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_DPIAWARE)); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = ( HBRUSH )(COLOR_WINDOW+1); wcex.lpszMenuName = MAKEINTRESOURCE(IDC_DPIAWARE); wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassEx(&wcex); } // // FUNCTION: InitInstance(HINSTANCE, int) // // PURPOSE: Saves instance handle and creates main window // // COMMENTS: // // In this function, we save the instance handle in a global variable and // create and display the main program window. // BOOL InitInstance( HINSTANCE hInstance, int nCmdShow) { HWND hWnd; hInst = hInstance; // Store instance handle in our global variable hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; } // // FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM) // // PURPOSE: Processes messages for the main window. // // WM_COMMAND - process the application menu // WM_PAINT - Paint the main window // WM_DESTROY - post a quit message and return // // LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; switch (message) { case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // Parse the menu selections: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break ; case IDM_EXIT: DestroyWindow(hWnd); break ; default : return DefWindowProc(hWnd, message, wParam, lParam); } break ; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // TODO: Add any drawing code here... EndPaint(hWnd, &ps); break ; case WM_DESTROY: PostQuitMessage(0); break ; default : return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // Message handler for about box. INT_PTR CALLBACK About( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); switch (message) { case WM_INITDIALOG: return ( INT_PTR )TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return ( INT_PTR )TRUE; } break ; } return ( INT_PTR )FALSE; } |
_tWinMain
function, you will notice the isSystemDPIAware
variable holds the true
value because the call to GetProcessDpiAwareness
returnsPROCESS_SYSTEM_DPI_AWARE
in the awareness
variable.
1
2
3
4
5
6
7
8
9
|
PROCESS_DPI_AWARENESS awareness; auto hProcess = OpenProcess(PROCESS_ALL_ACCESS, false , GetCurrentProcessId()); auto hr = GetProcessDpiAwareness(hProcess, &awareness); if (hr != S_OK) { MessageBox(NULL, ( LPCWSTR )L "GetProcessDpiAwareness failed" , ( LPCWSTR )L "Notification" , MB_OK); return FALSE; } auto isSystemDPIAware = (awareness == PROCESS_SYSTEM_DPI_AWARE); |
The following lines make a call to GetDpiForMonitor
to retrieve the DPI for the monitor in which the application window is running. If you add these lines to the InitInstance
function, you will be able to check that the dpiX
and dpiY
values are different from 96 because the system DPI aware process isn’t running on a virtualized 96 DPI environment. For example, if your monitor configuration has an extra large scaling of 200% or 192 DPI, the value for dpiX
will be 192 and you can use this value to scale your content. The MDT_EFFECTIVE_DPI
value specifies that you want the effective DPI that incorporates accessibility overrides and matches what DWM uses to scale desktop applications.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
POINT point; UINT dpiX = 0, dpiY = 0; point.x = 1; point.y = 1; auto hMonitor = MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST); auto hr = GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); if (hr != S_OK) { MessageBox(NULL, ( LPCWSTR )L "GetDpiForMonitor failed" , ( LPCWSTR )L "Notification" , MB_OK); return FALSE; } |
It is a good idea to use a scale helper class with all the necessary methods to simplify the process of scaling the different values and elements. The following lines show the code for a sampleScaleHelper
class. You need to provide the DPI retrieved from the monitor to the SetScaleFactor
method in order to allow the class to calculate the appropriate scale factor and store it inm_nScaleFactor
. For example, if the DPI is equal to 192
, the value for m_nScaleFactor
will be 200
. Then, you can call different methods to scale from raw pixels to relative pixels by using the calculated scale factor:
ScaleValue
: Scales a single value.ScalePoint
: Scales thex
andy
components of aPOINT
.ScaleRectangle
: Scales theleft
,right
,top
, andbottom
components of aRECT
.CreateScaledFont
: Receives a raw height value and scales it to create a logical font close to this scaled height.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
class ScaleHelper { public : ScaleHelper() { m_nScaleFactor = 0; } UINT GetScaleFactor() { return m_nScaleFactor; } void SetScaleFactor(__in UINT iDPI) { m_nScaleFactor = MulDiv(iDPI, 100, 96); } int ScaleValue( int value) { return MulDiv(value, m_nScaleFactor, 100); } // Scale rectangle from raw pixels to relative pixels. void ScaleRectangle(__inout RECT *pRectangle) { pRectangle->left = ScaleValue(pRectangle->left); pRectangle->right = ScaleValue(pRectangle->right); pRectangle->top = ScaleValue(pRectangle->top); pRectangle->bottom = ScaleValue(pRectangle->bottom); } // Scale Point from raw pixels to relative pixels. void ScalePoint(__inout POINT *pPoint) { pPoint->x = ScaleValue(pPoint->x); pPoint->y = ScaleValue(pPoint->y); } HFONT CreateScaledFont( int nFontHeight) { int nScaledFontHeight = ScaleValue(nFontHeight); LOGFONT lf; memset (&lf, 0, sizeof (lf)); lf.lfQuality = CLEARTYPE_QUALITY; lf.lfHeight = -nScaledFontHeight; auto hFont = CreateFontIndirect(&lf); return hFont; } private : UINT m_nScaleFactor; }; |
The following lines define the baseline padding and sizes for the window, the button, and the fonts. These are the default values for 96 DPI or 100% scaling. In addition, I define a global variable to call the ScaleHelper
class methods within different functions and store two HFONT
for the window text and the button text.
1
2
3
4
5
6
7
8
9
10
|
#define BASELINE_PADDING 15 #define BASELINE_WINDOW_WIDTH 800 #define BASELINE_WINDOW_HEIGHT 500 #define BASELINE_BUTTON_WIDTH 280 #define BASELINE_BUTTON_HEIGHT 60 #define BASELINE_FONT_HEIGHT 30 UINT g_nBaselineFontHeight = BASELINE_FONT_HEIGHT; HFONT g_hTextFont, g_hButtonFont; ScaleHelper g_ScaleHelper; |
The CreateFonts
function calls the ScaleHelper.CreateScaledFont
method twice to create fonts for the window text (g_hTextFont
) and button text (g_hButtonFont
). The function can be called many times if there are changes in the DPI settings (and when it is necessary to cleanup the existing fonts and create new fonts based on the new scale). In this case, the application is system DPI-aware, but the code for this function is ready to be reused when I convert this application to a per-monitor DPI aware one.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
void CreateFonts( HWND hWnd) { if (g_hTextFont != NULL) { DeleteObject(g_hTextFont); g_hTextFont = NULL; } g_hTextFont = g_ScaleHelper.CreateScaledFont(g_nBaselineFontHeight); if (g_hTextFont == NULL) { MessageBox(hWnd, ( LPCWSTR )L "CreateScaledFont failed" , ( LPCWSTR )L "Notification" , MB_OK); } if (g_hButtonFont != NULL) { DeleteObject(g_hButtonFont); g_hButtonFont = NULL; } g_hButtonFont = g_ScaleHelper.CreateScaledFont(BASELINE_FONT_HEIGHT); if (g_hButtonFont == NULL) { MessageBox(hWnd, ( LPCWSTR )L "CreateScaledFont failed" , ( LPCWSTR )L "Notification" , MB_OK); } } |
The RenderWindow
function draws different elements of the window: the background color, two lines of text, and a button. The function calls the ScaleHelper.ScaleValue
method to scale the different baseline values based on the DPI settings and uses the fonts for the window text (g_hTextFont
) and button text (g_hButtonFont
) created by the CreateFonts
function. If the DPI settings changes, it is possible to call RenderWindow
to update the elements based on the new DPI settings. This way, the RenderWindow
function can be reused in the conversion to a per monitor DPI aware application.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
void RenderWindow( HWND hWnd) { PAINTSTRUCT ps; RECT rcText, rcWindow, rcClient; LPCWSTR text1 = L "Line #1: Sample scaled text" ; LPCWSTR text2 = L "Line #2: Sample scaled text" ; // Cornflower blue background color auto color = RGB(100, 149, 237); GetWindowRect(hWnd, &rcWindow); GetClientRect(hWnd, &rcClient); // Scale the baseline padding value auto nScaledPadding = g_ScaleHelper.ScaleValue(BASELINE_PADDING); auto hdc = BeginPaint(hWnd, &ps); SetBkMode(hdc, TRANSPARENT); auto hBrush = CreateSolidBrush(color); FillRect(hdc, &rcClient, hBrush); // Render a button with the scaled text SelectObject(hdc, g_hButtonFont); auto hWndButton = GetWindow(hWnd, GW_CHILD); SetWindowPos(hWndButton, HWND_TOP, rcClient.left + nScaledPadding, rcClient.bottom - g_ScaleHelper.ScaleValue(BASELINE_BUTTON_HEIGHT) - nScaledPadding, g_ScaleHelper.ScaleValue(BASELINE_BUTTON_WIDTH), g_ScaleHelper.ScaleValue(BASELINE_BUTTON_HEIGHT), SWP_SHOWWINDOW); SendMessage(hWndButton, WM_SETFONT, ( WPARAM )g_hButtonFont, TRUE); UpdateWindow(hWndButton); // Render two lines of text within the Window SelectObject(hdc, g_hTextFont); rcText.left = rcClient.left + nScaledPadding; rcText.top = rcClient.top + nScaledPadding; DrawText(hdc, text1, -1, &rcText, DT_CALCRECT | DT_LEFT | DT_TOP); DrawText(hdc, text1, -1, &rcText, DT_LEFT | DT_TOP); rcText.top = rcText.bottom + nScaledPadding; DrawText(hdc, text2, -1, &rcText, DT_CALCRECT | DT_LEFT | DT_TOP); DrawText(hdc, text2, -1, &rcText, DT_LEFT | DT_TOP); // Cleanup code EndPaint(hWnd, &ps); DeleteObject(hBrush); DeleteDC(hdc); } |
It is necessary to change the code for the InitInstance
function. First, I add the previously shown lines that retrieve the DPI for the monitor in which the application window is going to be displayed. Then, I use the scale helper class to calculate the appropriate scale factor based on the retrieved DPI value, and I replace the code that creates the main window with new code that scales the baseline width (BASELINE_WINDOW_WIDTH
) and height (BASELINE_WINDOW_HEIGHT
) to create the window with the appropriate scaled size. In addition, the new code calls the previously explained CreateFonts
function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
BOOL InitInstance( HINSTANCE hInstance, int nCmdShow) { HWND hWnd; POINT point; UINT dpiX = 0, dpiY = 0; // Get the DPI for the main monitor, and set the scaling factor point.x = 1; point.y = 1; auto hMonitor = MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST); auto hr = GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); if (hr != S_OK) { MessageBox(NULL, ( LPCWSTR )L "GetDpiForMonitor failed" , ( LPCWSTR )L "Notification" , MB_OK); return FALSE; } // Use the scale helper class to calculate the appropriate scale factor based on the retrieved DPI value g_ScaleHelper.SetScaleFactor(dpiX); // Store instance handle in our global variable hInst = hInstance; // Create main window and pushbutton window hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, g_ScaleHelper.ScaleValue(100), g_ScaleHelper.ScaleValue(100), g_ScaleHelper.ScaleValue(BASELINE_WINDOW_WIDTH), g_ScaleHelper.ScaleValue(BASELINE_WINDOW_HEIGHT), NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } auto hWndButton = CreateWindow(L "BUTTON" , L "Scaled button text" , WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, 0, 0, 0, 0, hWnd, NULL, hInstance, NULL); // Create the scaled fonts for the window text and the button text CreateFonts(hWnd); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; } |
Finally, it is necessary to change the code that is executed with the WM_PAINT
message in theWndProc
function with the following code that calls the RenderWindow
function:
1 2 3 |
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 theWndProc
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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 dpiAware
: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:
1 2 3 4 5 6 7 8 9 |
<?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.
Related Article
http://www.drdobbs.com/windows/coding-for-high-dpi-displays-in-windows/240168736?pgno=3