19

I am making my app dpi-aware per monitor by setting <dpiAware>True/PM</dpiAware> in the manifest file. I can verify with process explorer that this is indeed working or by calling GetProcessDpiAwareness.

This is all working fine and I can scale anything in the client area fine in my code. However, my only problem is that if I drag my app from a system-dpi monitor to a non-system dpi monitor, the title bar and any system menu would either become too big or too small. This isn't the case for most built-in apps (e.g. calc, edge browser, etc..) so there must be away to scale it properly. Does anyone how the devs at MS did this?

The screenshot below should explain my problem better. Also notice, that the padding between the close, min, and max button is different when it's scaled (96dpi).

Screenshot

Sample app I'm attaching a very simple app that is per-monitor dpi aware.

Dealdiane
  • 3,984
  • 1
  • 24
  • 35
  • 1
    What does your per-monitor code looks like? Handler for WM_DPICHANGED? – David Heffernan Aug 03 '15 at 07:42
  • Is your app a universal application? Both calculator and Edge are – melak47 Aug 03 '15 at 07:47
  • @melak47 It isn't a universal app. This is an old app - just wanted to make it scale properly on all my monitors. Do you think windows would scale the title bar if it is a universal app? – Dealdiane Aug 03 '15 at 07:55
  • @David Heffernan Yes I'm handling WM_DPICHANGED and there's nothing fancy happening there just scaling fonts and button sizes. – Dealdiane Aug 03 '15 at 07:59
  • It usually helps to show code rather than describe it. How about an MCVE? – David Heffernan Aug 03 '15 at 08:03
  • The app is a fork of a popular open source project and it's a bit hard to get a working snippet of the code. Anyway, part of the main idea about making it dpi-aware was taken from [here](https://emoacht.wordpress.com/2013/10/30/per-monitor-dpi-aware-in-windows-forms/). At the bottom, there is a [zip file](http://sourceforge.net/projects/nasherder/files/samplecode/DpiChangeDemo.zip/download) that you can download that contains the compiled binaries. The compiled app in the zip file has the same exact problem I'm having. – Dealdiane Aug 03 '15 at 08:12
  • Just to reiterate, I'm not having troubles scaling the content. It's the non-client area that I'm having trouble getting windows to scale it. @melak47 just quickly created a universal app and can confirm it scales nicely and is per-monitor dpi aware as well. Only thing to find out now is whether I can replicate that behavior in a non-universal app. – Dealdiane Aug 03 '15 at 08:17
  • OK. If you can't show an MCVE then I'm voting to close as off topic. – David Heffernan Aug 03 '15 at 09:15
  • 1
    Hmm.. ok. I'm not sure how you can call it off-topic but here's an [off-the-shelf scaffold with the dpi-awareness code](https://dl.dropboxusercontent.com/u/3209117/DpiAwareness.zip) that I'm using. The code that sets the dpi-awareness is in wWinMain (not in manifest). WndProc handles WM_DPICHANGED as well but doesn't do anything. You can verify the dpi-awareness of the app using process explorer (the app runs its own verifications too) – Dealdiane Aug 03 '15 at 10:07
  • Questions and answers on stackoverflow should be self-contained. Don't link to off-site content, when that content is relevant. Include the relevant part in your question instead. – IInspectable Aug 03 '15 at 10:54
  • 1
    ok done. afaik, the sample app is pretty much standard but I hope people would find that relevant. – Dealdiane Aug 03 '15 at 11:30

4 Answers4

10

The Windows 10 Anniversary Update (v1607) has added a new API you must call to enable DPI scaling of the non-client areas: EnableNonClientDpiScaling. This function should be called, when WM_NCCREATE is received. The message is sent to the window's procedure callback during window creation.

Example:

case WM_NCCREATE:
{
    if (!EnableNonClientDpiScaling(hWnd))
    {
        // Error handling
        return FALSE;
    }

    return DefWindowProcW(...);
}

If the application's DPI-awareness context is DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, then calling EnableNonClientDpiScaling should be omitted, as it won't have any effect, although the function will still return successfully.

From the documentation:

Non-client scaling for top-level windows is not enabled by default. You must call this API to enable it for each individual top-level window for which you wish to have the non-client area scale automatically. Once you do, there is no way to disable it. Enabling non-client scaling means that all the areas drawn by the system for the window will automatically scale in response to DPI changes on the window. That includes areas like the caption bar, the scrollbars, and the menu bar. You want to call EnableNonClientDpiScaling when you want the operating system to be responsible for rendering these areas automatically at the correct size based on the API of the monitor.

See this blog post for additional information about DPI scaling changes in Windows 10 AU.

tambre
  • 4,625
  • 4
  • 42
  • 55
josh3736
  • 139,160
  • 33
  • 216
  • 263
  • You don't need to call that EnableNonClientDpiScaling. Just add proper `` : https://stackoverflow.com/questions/31781767/how-do-you-scale-the-title-bar-on-a-dpi-aware-win-application/38729344#38729344 – c-smile Jun 29 '18 at 15:18
  • Not sure if this is true. I have PerMonitorV2 in my manifest. When I call GetAwarenessFromDpiAwarenessContext it returns DPI_AWARENESS_PER_MONITOR_AWARE. EnableNonClientDpiScaling is required in WM_NCCREATE in this case, though it doesn't really work properly. Things only seem to be correctly scaled when I nudge the window to resize, having dragged it from one monitor to the other, higher DPI monitor. Without the call Windows makes no attempt to scale my non-client areas. All very strange. – Robinson Apr 20 '23 at 14:41
7

Does anyone how the devs at MS did this?

This has a pretty disappointing answer. Using Alin Constantin's WinCheat and inspecting the top-level window of Calculator, I see a window size of 320x576, and a client size that is also 320x576.

In other words, Microsoft entirely avoids the problem by suppressing the non-client area of the window, putting everything in the client area instead. Making this work well for you may involve custom drawing of the title bar.

Something worth noting is that Calculator and e.g. Windows Explorer don't use the same colour for the title bars. Calculator doing custom drawing of the title bar would explain that perfectly.

  • Thanks. That's exactly what I was worried about though. I actually don't get the reasoning behind this. If they fixed this the proper way in win10, everyone would've benefited from it. – Dealdiane Aug 03 '15 at 22:35
  • I did more research on this and it seems that UWP apps will not have this problem. This is because of the new [ApplicationViewTitleBar](https://msdn.microsoft.com/library/windows/apps/windows.ui.viewmanagement.applicationviewtitlebar.aspx) class in Windows 10. Like @hvd said, in a UWP app there is no non-client area. The tile bar is drawn in the client area but will still function as it should without extra coding. I wish we could get the same behavior out of the box or at least call some apis to achieve the same thing in a _native_ app though. – Dealdiane Aug 04 '15 at 08:22
  • I don't think this explains it entirely. For example cmd.exe or the Win-R dialog have different sizes for window and client size but MAGICALLY redraw their window chrome, window menus, etc. – Jabe Sep 18 '15 at 14:34
  • @Jabe For non-DPI-aware applications, Windows performs scaling of the complete window, including non-client area. I cannot check right now (not on a Win10 system) whether either of the windows you describe are created by a DPI-aware application, but my first guess is that they are not. –  Sep 18 '15 at 14:41
  • 1
    @hvd "cmd.exe" is unaware but spawns "conhost.exe" which shows up as per-monitor aware in procexp. It scales perfectly (on 100% and 200% DPI). The run-dialog also scales perfectly, but run an invalid program from it and it brings up an unscaled message box. Press the Find button and it'll bring up a perfectly scaled open file dialog. WTF. I realize cmd and run-dialog are probably very special windows, but the behaviour isn't easily explained by rendering everything on my own in the client area. – Jabe Sep 18 '15 at 14:52
  • 1
    @jabe is right -- conhost is the only app I've seen that actually does its per-monitor DPI scaling 100% correctly and with a regular non-client area. Even the console options dialog scales correctly. – josh3736 Apr 22 '16 at 18:18
  • @Jabe I just noticed the exact same thing about the Run dialog. I think that's the most interesting example, considering it's just a normal old Win32 dialog that is launched by explorer.exe. Looking at it with Spy++, there is absolutely nothing unusual. It's not a weirdo app like Calculator, and it's not even using a proprietary control like DirectUIHWND---all bog standard stuff. The fact that it displays an unscaled message box isn't unusual, that's the fault of the MessageBox API that hasn't been recoded to handle per-monitor DPI. More interesting is the Browse dialog *is* properly scaled... – Cody Gray - on strike Apr 24 '16 at 14:11
  • 2
    Trying to decide whether I should bounty this question or write a new one of my own. hvd, I like your answer, and I think it's correct as far as it goes, but it doesn't go quite far enough. As pointed out in other comments (see in particular mine above), the bog-standard Win32 Run dialog gets this right. As far as I can tell with Spy++, it is *not* owner-drawing its non-client area, and I very much doubt that the shell team has subclassed the Browse dialog that Run launches to owner-draw *its* title bar. So there *must* be some reasonable way of doing it. They can't have been total idiots. – Cody Gray - on strike Apr 24 '16 at 14:15
  • 1
    @CodyGray I would like to see a better answer myself, and I think if you bounty this question, there's a reasonable chance of not only not getting that, but also the system awarding the bounty to me when the whole point for the bounty was to get something better than my answer. I think creating a new question is reasonable. Include a link to this question and explain in your question that for your question, you're specifically looking to avoid owner-drawing title bars, and I think it shouldn't be mistakenly closed as a duplicate. –  Apr 24 '16 at 19:39
  • [Done](http://stackoverflow.com/questions/36864894/scaling-the-non-client-area-title-bar-menu-bar-with-per-monitor-high-dpi-supp). Turned out to be ridiculously long, oh well. I wanted to give some impression of all the research I've done, even if only for my own reference later. – Cody Gray - on strike Apr 26 '16 at 12:33
2

UPDATE:

It is enough to add new <dpiAwarness> declaration to manifest to solve all this mess. Example is here.

Remnants of former investigations (obsolete):

More investigations on the subject.

System setup: two monitors, one is 96 dpi another one is 267 dpi (Microsoft Surface 4).

Testing window is moved to secondary 96 dpi monitor:

Here is rendering (wrong, IMO) with <dpiAware>true/pm</dpiAware> in manifest:

enter image description here

Note huge size of caption bar and wrong sizes of window icons.

And here is correct rendering using <dpiAware>true</dpiAware>

enter image description here

And I suspect that MSDN documentation is plainly misleading about values of PROCESS_DPI_AWARENESS. I do not see any differences in messages and styles between <dpiAware>true</dpiAware> and <dpiAware>true/pm</dpiAware>. The later one just makes caption larger. In both case application receives WM_DPICHANGED message while moving between monitors with different DPI.

Sigh.

c-smile
  • 26,734
  • 7
  • 59
  • 86
  • As was mentioned, on the Anniversary Update (and newer versions of Windows 10) you can get the non-client area to DPI scale by calling EnableNonClientDpiScaling from WM_NCCREATE. Check out this sample: https://github.com/Microsoft/Windows-classic-samples/tree/master/Samples/DPIAwarenessPerWindow – peterfelts Feb 05 '17 at 15:56
  • 1
    In the Windows 10 Creators Update there will be a new DPI awareness context DPI_AWARENESS_PER_MONITOR_AWARE_V2 which will enable non-client scaling by default for a top-level window. – peterfelts Feb 05 '17 at 15:58
0

The documentation says:

Note that the non-client area of a per monitor–DPI aware application is not scaled by Windows, and will appear proportionately smaller on a high DPI display.

The Microsoft apps that you link to deal with this by removing the non-client area and making the client area cover the entire window.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490