2
votes

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
Is it worth supporting 8.1? Surely most people would have taken advantage of the free upgrade to 10.Jonathan Potter
@JonathanPotter You mean downgrade? On a small tablet, 8.1 is better than 10.Anders
Non-client area per monitor scaling introduced in Windows 10 Aniversary Update High-DPI Scaling Improvements.Daniel Sęk
Scaling of non-client area. Prior to the Anniversary Update there was no way to have the Windows-drawn non-client area DPI scale (caption bar, system menus, top-level scroll bars, menu bars, etc.). This meant that if you created a per-monitor application you’d be left with incorrectly sized (too big or too small) non-client area after the DPI change without any recourse other than drawing all that stuff yourself. In the Anniversary Update we added an API that you could call to turn on non-client scaling, EnableNonClientDpiScaling but now with PMv2 you get this automaticallyDaniel Sęk

2 Answers

3
votes

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.

1
votes

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.");
}