Scaling non-client area for per-monitor high-DPI support on application supporting Windows 8.1

Viewed 552

I'm developing an application that runs on Windows 10 and 8.1 and have run against the problem of scaling the non-client area (menu bar, title bar) when moving between monitors with different DPI. The client area is handled but the non-client area goes out of proportions. The DPI Awareness is set to PerMonitorAware (v1 since v2 is not available on Windows 8.1).

The function EnableNonClientDpiScaling does exactly what I would need (it is the accepted answer to all similar questions) - alas it is only part of the API from Windows 10 on.

Is there a way to manually handle this without the before mentioned function - in order to keep support for Windows 8.1? Or does supporting Windows 8.1 means it's not possible to resize the non-client area when moving between screens with different DPI?

2 Answers

DPI support is a moving target, you just have to decide what your minimum supported platform is and accept that multi-monitor scaling will not be perfect on these older platforms.

Call EnableNonClientDpiScaling on the versions where it is available (GetProcAddress or the equivalent in whatever you language you are using).

The way the new awareness manifest element works in Windows 10 means that you can be Per-Monitor v2 where it is supported (1703 and later) and either PMv1, System or Unaware on older builds. PMv2 gives you automatic scaling of DialogBox based dialogs.

Thanks to Anders, GetProcAddress was just the thing I was looking for. Although it does not solve non-client area resizing on Win 8.1 (it seems there really is no way apart from drawing everything oneself), it makes possible setting the latest DPI_AWARENESS_CONTEXT on Win 10:

// the following sets PROCESS_PER_MONITOR_AWARE_V2 on Win10 and
// reverts to PROCESS_PER_MONITOR_AWARE on Win 8.1

typedef BOOL(__stdcall *SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT);
SetProcessDpiAwarenessContext dpi_call = nullptr;
dpi_call = reinterpret_cast<SetProcessDpiAwarenessContext>(GetProcAddress(
    GetModuleHandle(TEXT("User32.dll")),
    "SetProcessDpiAwarenessContext"));
if (dpi_call != nullptr) {
    if (!(*dpi_call)((DPI_AWARENESS_CONTEXT) - 4))
        throw std::runtime_error("Unable to set DPI aware app.");
} else {
    if (SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE) != S_OK)
        throw std::runtime_error("Unable to set DPI aware app.");
}
Related