Skip to content
WindowsDeep Dive Published Updated 2 min readViews unavailable

Per-Monitor DPI Awareness in Win32: Reflowing Windows Across Displays

Handle mixed-DPI monitor changes in Win32 with Per-Monitor v2 awareness, WM_DPICHANGED, suggested bounds, and DPI-specific layout and assets.

A desktop window can move between displays with different scale factors, change DPI after a display-setting update, or cross a Remote Desktop boundary. A system-DPI-aware app lays itself out for one session DPI; Windows may bitmap-scale it after the DPI changes, which can blur text and controls. Per-monitor awareness lets the app react to the effective DPI for each window instead of asking Windows to stretch an already-rendered surface.

For modern Win32 applications, Per-Monitor v2 (PMv2) is generally the preferred context when the full UI stack supports it. Declare awareness in the application manifest or set the process context before creating UI. PMv2 extends change notifications through the child window tree and improves scaling for non-client areas, dialogs, menus, and common controls. It does not automatically fix custom drawing or every framework control.

Handle WM_DPICHANGED as a layout event

When a top-level PM-aware window changes DPI, WM_DPICHANGED carries the new horizontal and vertical DPI in wParam and a suggested window rectangle in lParam. Apply the suggested bounds, then recompute DPI-dependent layout, fonts, and raster assets. Avoid blindly multiplying already-scaled dimensions on every transition; keep a logical baseline and derive physical pixels from the current DPI.

case WM_DPICHANGED: {
    const auto dpiX = LOWORD(wParam);
    const auto dpiY = HIWORD(wParam);
    const auto* suggested = reinterpret_cast<const RECT*>(lParam);
    SetWindowPos(hwnd, nullptr, suggested->left, suggested->top,
        suggested->right - suggested->left,
        suggested->bottom - suggested->top,
        SWP_NOZORDER | SWP_NOACTIVATE);
    RebuildLayoutForDpi(hwnd, dpiX, dpiY);
    return 0;
}

This is a message-handler sketch, not a complete multi-window strategy. GetDpiForWindow(hwnd) provides the effective DPI for a window after its awareness context is established; DPI APIs can return virtualized values depending on the calling thread/window context.

Make mixed-DPI behavior testable

Audit cached metrics, icon selection, owner-drawn controls, popup menus, drag images, and child-window hosting. A DLL or plugin with a different awareness context can create mixed-mode behavior that looks like a layout bug. Use DPI-aware APIs such as GetSystemMetricsForDpi where available rather than caching system metrics once at startup.

Test dragging every top-level window between monitors at unequal scale factors, changing the scale while the app runs, reconnecting through Remote Desktop, opening menus/dialogs, and resizing at each scale. Test a monitor arrangement whose origin is not (0, 0); virtual-screen coordinates can be negative. Include screenshot review and interaction checks, not just a successful compile.

Related:

Sources:

Comments