Coding for High-DPI Displays in Windows

作者: admin 分类: win32 发布时间: 2015-02-03 22:43 ė6,207 浏览数 6没有评论
文章转自王牌软件
站长推荐:NSetup一键部署软件
一键式完成美化安装包制作,自动增量升级,数据统计,数字签名。应对各种复杂场景,脚本模块化拆分,常规复杂的脚本代码,图形化设置。无需专业的研发经验,轻松完成项目部署。(www.nsetup.cn)
In the first article in this two-part series on coding for high-DPI displays on the Windows desktop, I explained high-DPI configurations in Windows 8.1 and their effect on the three different kinds of applications: DPI-unaware, system-DPI aware, and per-monitor DPI aware. In this article, I provide a complete example of a system DPI-aware Win32 application and then I’ll convert it to a per-monitor DPI-aware application that always provides crisp text and images to Windows 8.1 users.

 

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 SetProcessDpiAwarenessfunction. 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>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    </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;
}

 

If you add the following lines to the _tWinMain function, you will notice the isSystemDPIAwarevariable 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 SetScaleFactormethod 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 the x and y components of a POINT.
  • ScaleRectangle: Scales the leftrighttop, and bottom components of a RECT.
  • 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:

 

 

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).

WindowsDisplay
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.

 

 

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).

WindowsDisplay
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:

 

 

 

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

Programming for High-Res Displays in Windows

http://www.drdobbs.com/windows/coding-for-high-dpi-displays-in-windows/240168736?pgno=3



只回答业务咨询点击这里给我发消息 点击这里给我发消息

学习日记,兼职软件设计,软件修改,毕业设计。

本文出自 学习日记,转载时请注明出处及相应链接。

本文永久链接: https://www.softwareace.cn/?p=1179

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">


Ɣ回顶部

无觅相关文章插件,快速提升流量