How do you make a desktop window DPI-aware? Last time I showed you a few approaches to creating a top-level window on the desktop. If you care at all about the appearance of your app then one of the first things you ought to do is make the window DPI-aware. I covered this at length in an article for MSDN Magazine. Here I want to just summarize the most essential ingredients. The first thing to do is tell Windows that your app is per-monitor DPI-aware. The best way is to use a manifest and you can get the scoop on that from my article in MSDN Magazine. Here I’m interested in the code. I’m going to start with the Window class template that I introduced last time as well as the SampleWindow class that derived from it.
The earliest reasonable opportunity to determine the effective DPI for a given window is in its WM_CREATE handler. Once created the window must also respond to the WM_DPICHANGED message so that it can be notified of any changes to the DPI values over time. Here’s an update to the SampleWindow’s MessageHandler to intercept both of these additional messages.
LRESULT MessageHandler(UINT message,
WPARAM const wparam,
LPARAM const lparam)
if (WM_PAINT == message)
else if (WM_DPICHANGED == message)
else if (WM_CREATE == message)
The original version only responded to the WM_PAINT message. The WM_DPICHANGED message handler receives the new DPI values through its WPARAM while the WM_CREATE does not require any additional information at this point. Let’s start with the CreateHandler method. The first thing it needs to do is determine the monitor nearest the window. Even though the WM_CREATE message is received before the window is visible on the desktop, the window already has its position and size so this is a fine time to call the MonitorFromWindow function to retrieve the monitor that has the largest intersection with my window. Given the window handle, I can call the GetDpiForMonitor function to retrieve the monitor-specific DPI values.
float m_dpiX = 0.0f;
float m_dpiY = 0.0f;
HMONITOR const monitor = MonitorFromWindow(m_window,
unsigned x = 0;
unsigned y = 0;
m_dpiX = static_cast<float>(x);
m_dpiY = static_cast<float>(y);
I’m just using two member variables to keep track of the DPI values for the window and I’m storing the DPI values using floating point since most DPI-aware rendering uses floating point coordinates. So the window begins life with an awareness of the DPI values it ought to use for scaling and rendering but it’s also responsible for keeping those values up to date. That’s the job of the DpiHandler method. It simply extracts the updated DPI values from the message’s WPARAM and updates the same member variables.
void DpiHandler(WPARAM const wparam)
m_dpiX = LOWORD(wparam);
m_dpiY = HIWORD(wparam);
This WM_DPICHANGED handler could technically do the exact same thing as the WM_CREATE handler and look up the monitor and DPI values directly but this is certainly more convenient. And that’s all there is to it. Assuming you’ve configured your manifest correctly you window will always have the correct DPI values to rely on. Until next time…
For more practical advice and examples for writing apps with Visual C++ please check out 10 Practical Techniques to Power Your Visual C++ Apps.
Read the previous installment in this series: Classy Windows