18 msdn magazine Windows with C++
Traditionally, Direct2D applications simply retrieved the DPI
values from the Direct2D factory, which in turn simply called
GetDeviceCaps, as I illustrated earlier. But this is no longer suffi -
cient. As I’ve shown, the DPI values may now change on the fl y, and
the value provided by GetDeviceCaps is only useful if you’re trying
to fi t in with the rendering of a system DPI-aware application.
Instead, right aft er you’ve created your Direct2D render target, you
need to query the DPI value for the monitor nearest your window:
auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONEAREST);
Given this monitor handle, you can call the GetDpiForMonitor
function to retrieve the DPI values for this specifi c monitor:
auto x = unsigned {};
auto y = unsigned {};
VERIFY_(S_OK, GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &x, &y));
Th e eff ective DPI value for a given monitor won’t necessarily
correspond exactly to options presented in Figure 4. It again
depends on a number of factors, including resolution, the phys-
ical DPI of the display and the assumed distance to the display
surface. Finally, you can call the Direct2D render target’s SetDpi
method and Direct2D will take care of properly
scaling your content as needed.
I did mention this can all happen dynamically.
Your application may be running and suddenly the
user changes the scale with the window shown in
Figure 3 or drags your window to another monitor
with a diff erent DPI scaling factor. In those cases, the
Windows shell will send per-monitor DPI-aware
application windows a new window message called
WM_DPICHANGED.
Inside your message handler, you can once again call
the MonitorFromWindow and GetDpiForMonitor
functions to get the eff ective DPI values for the current
monitor and then simply update your Direct2D ren-
der target with its SetDpi method again. Alternatively,
the message’s WPARAM packs both the x and y DPI
values so this becomes a simple matter of extracting
the low and high order words:
auto x = LOWORD(wparam);
auto y = HIWORD(wparam);
Th e WM_DPICHANGED message’s LPARAM is,
however, indispensable. It provides a new suggested
position and size of your window based on the new
scale factor that’s now in eff ect. Th is ensures that while
Direct2D takes care of scaling your content, you can
also scale your window’s actual size on the desktop
and position it appropriately. Th e message’s LPARAM
is just a pointer to a RECT structure:
auto rect = *reinterpret_cast<RECT *>(lparam);
You can then simply call the SetWindowPos function to update
your window’s position and size:
VERIFY(SetWindowPos(window,
0, // No relative window
rect.left,
rect.top,
rect.right - rect.left,
rect.bottom - rect.top,
SWP_NOACTIVATE | SWP_NOZORDER));
And that’s all it takes. If you’ve invested in Direct2D, you’re just
a few steps away from being per-monitor DPI-aware. Few appli-
cations have made the leap, so your application is bound to stand
out! If you’ve got an existing Windows Presentation Foundation
(WPF) application, there’s also a sample available at bit.ly/IPDN3p that
shows you how to integrate these same principles outlined in this
article into your WPF code base.
Figure 9 provides an example of a per-monitor DPI-aware applica-
tion, automatically scaling both its content and its window size based
on WM_DPICHANGED messages. Th e emoticon indicates which
monitor the window is currently positioned on. For a more interactive
example, please check out my Pluralsight course at bit.ly/1fgTifi , where
you can also get the sample code for this application. Q
KENNY KERR is a computer programmer based in Canada, as well as an author for
Pluralsight and a Microsoft MVP. He blogs at kennykerr.ca and you can follow
him on Twitter at twitter.com/kennykerr.
THANKS to the following technical expert for reviewing this article:
James Clarke (Microsoft )
Figure 9 Per-Monitor DPI-Aware Application
If you’ve invested in Direct2D,
you’re just a few steps away from
being per-monitor DPI-aware.