简体   繁体   English

缩放非客户区(标题栏、菜单栏)以支持每个显示器的高 DPI

[英]Scaling the non-client area (title bar, menu bar) for per-monitor high-DPI support

Windows 8.1 introduced the ability to have different DPI settings for different monitors. Windows 8.1 引入了针对不同显示器具有不同 DPI 设置的功能。 This feature is known as "per-monitor high-DPI support."此功能称为“每个显示器的高 DPI 支持”。 It persists and has been further refined in Windows 10.它仍然存在,并在 Windows 10 中得到了进一步完善

If an application does not opt in ( ie , is either DPI-unaware or high-DPI aware), it will be automatically scaled up by DWM to the proper DPI.如果应用程序没有选择加入(不知道 DPI 或知道高 DPI),DWM 会自动将其放大到适当的 DPI。 Most applications fall into one of these two categories, including most of the utilities bundled with Windows ( eg , Notepad).大多数应用程序都属于这两个类别之一,包括大多数与 Windows 捆绑在一起的实用程序(例如,记事本)。 On my test system, the high-DPI monitor is set to 150% scale (144 DPI), while the normal monitor is set to the system DPI (100% scale, 96 DPI).在我的测试系统上,高 DPI 显示器设置为 150% 比例(144 DPI),而普通显示器设置为系统 DPI(100% 比例,96 DPI)。 Therefore, when you open one of these applications on the high-DPI screen (or drag it over there), virtualization kicks in, magnifying everything, but also making it incredibly blurry.因此,当您在高 DPI 屏幕上打开其中一个应用程序(或将其拖到那里)时,虚拟化就会启动,放大所有内容,但也会使其变得非常模糊。

On the other hand, if an application explicitly indicates that it supports per-monitor high-DPI, then no virtualization is performed and the developer is responsible for scaling.另一方面,如果应用程序明确表明它支持每台显示器的高 DPI,则不会执行任何虚拟化,并且开发人员负责扩展。 Microsoft has a fairly comprehensive explanation here * , but for the benefit of a self-contained question, I'll summarize.微软在这里有一个相当全面的解释* ,但为了一个独立的问题,我会总结一下。 First, you indicate support by setting <dpiAware>True/PM</dpiAware> in the manifest.首先,您通过在清单中设置<dpiAware>True/PM</dpiAware>来表示支持。 This opts you into receiving WM_DPICHANGED messages , which tells you both the new DPI setting as well as a suggested new size and position for your window.这会让您选择接收WM_DPICHANGED消息,它会告诉您新的 DPI 设置以及建议的窗口大小和位置。 It also allows you to call the GetDpiForMonitor function and obtain the actual DPI, without being lied to for compatibility reasons.它还允许您调用GetDpiForMonitor函数并获取实际的 DPI,而不会出于兼容性原因而被欺骗。 Kenny Kerr has also written up a comprehensive tutorial . Kenny Kerr 还编写了一个综合教程

I've gotten all of this going successfully in a little C++ test app.我已经在一个小的 C++ 测试应用程序中成功完成了所有这些。 It's a lot of boilerplate and mostly project settings, so I don't see much point in posting a full example here.这是很多样板文件,主要是项目设置,所以我认为在这里发布一个完整的例子没有多大意义。 If you want to test it out, either follow Kenny's instructions, this tutorial on MSDN , or download the official SDK sample .如果您想对其进行测试,请按照 Kenny 的说明、 MSDN 上的本教程,或下载官方 SDK 示例 Now, the text in the client area looks good (because of my handling of WM_DPICHANGED ), but because virtualization is no longer performed, there is no scaling of the non-client area.现在,客户区中的文本看起来不错(因为我对WM_DPICHANGED的处理),但由于不再执行虚拟化,因此非客户区没有缩放。 The result is that the title/caption bar and the menu bar are the wrong size —they do not get larger on the high-DPI screen:结果是标题/标题栏和菜单栏的大小错误——它们在高 DPI 屏幕上没有变大:

So the question is, how do I get the non-client area of the window to scale to the new DPI?所以问题是,如何让窗口的非客户区缩放到新的 DPI?
It doesn't matter whether you create your own window class or use a dialog—they have identical behavior in this respect.无论您是创建自己的窗口类还是使用对话框都没有关系——它们在这方面具有相同的行为。

It has been suggested that there is no answer—that your only choice is to custom draw the entire window, including the non-client area. 有人建议没有答案 - 您唯一的选择是自定义绘制整个窗口,包括非客户区。 While this is certainly possible, and indeed what UWP apps (those previously known as Metro) do, like the Windows 10 Calculator, it is not a workable option for desktop applications that use many non-client widgets and hope to look native.虽然这当然是可能的,并且确实是 UWP 应用程序(以前称为 Metro)所做的,例如 Windows 10 计算器,但对于使用许多非客户端小部件并希望看起来像原生的桌面应用程序来说,它不是一个可行的选择。

Aside from that, it is demonstrably false.除此之外,它显然是错误的。 Custom-drawn title bars cannot be the only way of getting the correct behavior, since the Windows shell team has done it.自定义绘制的标题栏不是获得正确行为的唯一方法,因为 Windows shell 团队已经做到了。 The humble Run dialog behaves exactly as expected, properly resizing both the client and non-client areas as you drag it between monitors with different DPIs:不起眼的“运行”对话框的行为与预期完全一样,当您在具有不同 DPI 的显示器之间拖动它时,会正确调整客户端和非客户端区域的大小:

Investigation with Spy++ confirms this is just a bog-standard Win32 dialog—nothing fancy.使用 Spy++ 进行的调查证实这只是一个标准的 Win32 对话框——没什么特别的。 All of the controls are standard Win32 SDK controls.所有的控件都是标准的 Win32 SDK 控件。 It is not a UWP app, nor have they custom-drawn the title bar—it still has the WS_CAPTION style.它不是 UWP 应用程序,也没有自定义绘制标题栏——它仍然具有WS_CAPTION样式。 It is launched by the explorer.exe process, which is marked as per-monitor high-DPI aware (verified with Process Explorer and GetProcessDpiAwareness).它由 explorer.exe 进程启动,该进程标记为 per-monitor 高 DPI 感知(通过 Process Explorer 和 GetProcessDpiAwareness 验证)。 This blog post confirms that both the Run dialog and the Command Prompt have been rewritten in Windows 10 to scale correctly (see " Command shells et al. "). 这篇博文确认运行对话框和命令提示符都已在 Windows 10 中重写以正确扩展(请参阅“命令 shell 等”)。 What is the Run dialog doing to resize its title bar?运行对话框在做什么来调整其标题栏的大小?

The Common Item Dialog API , responsible for new-style Open and Save dialogs, also scales correctly when launched from a process that is per-monitor high-DPI aware, as you can see when clicking the "Browse" button from the Run dialog. Common Item Dialog API负责新型打开和保存对话框,当从每个监视器高 DPI 感知的进程启动时,它也可以正确缩放,正如您在单击“运行”对话框中的“浏览”按钮时所见。 Same thing for the Task Dialog API , creating the odd situation where an app launches a dialog box with a different-size title bar . Task Dialog API也是如此,创造了一个奇怪的情况,即应用程序启动一个带有不同大小标题栏的对话框 (The legacy MessageBox API has not been updated, however, and exhibits the same behavior as my test app.) (但是,旧的 MessageBox API 尚未更新,并且表现出与我的测试应用程序相同的行为。)

If the shell team is doing it, it has to be possible.如果外壳团队正在这样做,它必须是可能的。 I just cannot imagine that the team responsible for designing/implementing per-monitor DPI support neglected to provide a reasonable way for developers to produce compatible applications.我只是无法想象负责设计/实现每个监视器 DPI 支持的团队忽略了为开发人员提供一种合理的方式来生成兼容的应用程序。 Features like this require developer support, or they are broken out-of-the-box.像这样的功能需要开发人员的支持,或者它们是开箱即用的。 Even WPF applications are broken—Microsoft's Per-Monitor Aware WPF Sample project fails to scale the non-client area, resulting in a title bar that is the wrong size.甚至 WPF 应用程序都损坏了——Microsoft 的Per-Monitor Aware WPF 示例项目无法缩放非客户区,导致标题栏的大小错误。 I'm not much for conspiracy theories, but this smells of a marketing move to discourage desktop app development.我不太喜欢阴谋论,但这闻起来像是一种阻止桌面应用程序开发的营销举措。 If so, and there is no official way, I'll accept answers that rely on undocumented behavior.如果是这样,并且没有官方方式,我将接受依赖于无证行为的答案。

Speaking of undocumented behavior, logging window messages when the Run dialog is dragged between monitors with different DPI settings shows that it receives an undocumented message, 0x02E1 .说到未记录的行为,当在具有不同 DPI 设置的监视器之间拖动“运行”对话框时记录窗口消息表明它收到了未记录的消息0x02E1 This is somewhat interesting because this message ID is exactly one greater than the documented WM_DPICHANGED message ( 0x02E0 ).这有点有趣,因为此消息 ID 正好比记录的WM_DPICHANGED消息 ( 0x02E0 ) 大一。 My test app never gets this message, though, regardless of its DPI-awareness settings.但是,无论其 DPI 感知设置如何,我的测试应用程序都不会收到此消息。 (Curiously, close inspection does reveal that Windows slightly increases the size of the minimize/maximize/close glyphs on the title bar as the window moves onto the high-DPI monitor. They're still not as big as they are when they are virtualized, but they're slightly bigger than the glyphs that it uses for unscaled system-DPI applications.) (奇怪的是,仔细检查确实显示,当窗口移动到高 DPI 显示器上时,Windows略微增加了标题栏上最小化/最大化/关闭字形的大小。它们仍然没有虚拟化时那么大,但它们比它用于未缩放的系统 DPI 应用程序的字形略大。)

So far, my best idea has been to handle the WM_NCCALCSIZE message to adjust the size of the non-client area.到目前为止,我最好的想法是处理WM_NCCALCSIZE消息来调整非客户区的大小。 By using the SWP_FRAMECHANGED flag with the SetWindowPos function , I can force the window to resize and redraw its non-client area in response to WM_DPICHANGED .通过使用带有SetWindowPos函数SWP_FRAMECHANGED标志,我可以强制窗口调整大小并重新绘制其非客户区以响应WM_DPICHANGED This works fine to reduce the height of the title bar, or even remove it altogether, but it will never make it any taller .这可以很好地降低标题栏的高度,甚至完全删除它,但它永远不会让它变得更高 The caption seems to peak out at the height determined by the system DPI.标题似乎在系统 DPI 确定的高度达到峰值。 Even if it worked, this wouldn't be the ideal solution, because it wouldn't help with the system-drawn menu bar or scroll bars…but at least it would be a start.即使它有效,这也不是理想的解决方案,因为它对系统绘制的菜单栏或滚动条没有帮助……但至少这是一个开始。 Other ideas?其他想法?

* I know that this article 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." * 我知道这篇文章说“请注意,每台显示器的非客户区 - DPI 感知应用程序不会被 Windows 缩放,并且在高 DPI 显示器上会按比例缩小。” See above for why that is (1) wrong and (2) unsatisfactory.请参阅上文,了解为什么 (1) 错误和 (2) 不令人满意。 I'm looking for a workaround other than custom-drawing the non-client area.我正在寻找一种解决方法,而不是自定义绘制非客户区域。

In any up-to-date Windows Insider builds (build >= 14342, SDK version# >= 14332) there is an EnableNonClientDpiScaling API (which takes an HWND as its argument) that will enable non-client DPI scaling for top-level HWNDs.在任何最新的 Windows Insider 版本(版本 >= 14342,SDK 版本# >= 14332)中都有一个 EnableNonClientDpiScaling API(它以 HWND 作为其参数),它将启用顶级 HWND 的非客户端 DPI 缩放. This functionality requires that the top-level window be running in per-monitor DPI-awareness mode.此功能要求顶级窗口以每显示器 DPI 感知模式运行。 This API should be called from the WM_NCCREATE handler for the window.应该从窗口的 WM_NCCREATE 处理程序调用此 API。 When this API is called on a top-level window, its caption bar, top-level scrollbars, system menu and menubar will DPI scale when the application's DPI changes (this can happen when the app is moved to a display with a different display scaling value or when the DPI changes for other reasons such as the user making a settings change or when an RDP connection changes the scale factor).在顶级窗口调用此 API 时,其标题栏、顶级滚动条、系统菜单和菜单栏将在应用程序的 DPI 更改时进行 DPI 缩放(当应用程序移动到具有不同显示缩放的显示器时会发生这种情况值或 DPI 因其他原因而更改时,例如用户更改设置或 RDP 连接更改比例因子时)。

This API does not support scaling non-client area for child windows, such as scroll bars in a child window.此 API 不支持缩放子窗口的非客户区,例如子窗口中的滚动条。

To my knowledge there is no way to have non-client area DPI scale automatically without this API.据我所知,如果没有此 API,就无法自动调整非客户区 DPI。

Note that this API has not yet been finalized, and it may change prior to being released in the Windows 10 Anniversary update.请注意,此 API 尚未最终确定,它可能会在 Windows 10 周年更新中发布之前发生变化。 Keep your eye on MSDN for official documentation when it becomes final.当它成为最终版本时,请密切关注 MSDN 以获取官方文档。

With Per Monitor V2 DPI awareness in Windows 10 Creators Update (build 15063) you can resolve this easily without the EnableNonClientDpiScaling .借助 Windows 10 Creators Update(内部版本 15063)中的Per Monitor V2 DPI 意识,您可以在没有EnableNonClientDpiScaling的情况下轻松解决此问题。

To enable Per Monitor V2 DPI awareness , while still supporting old Per Monitor DPI awareness on older Windows 10 builds and Windows 8.1 and DPI awareness on yet older versions of Windows, make your application manifest like this:要启用Per Monitor V2 DPI 感知,同时仍支持旧 Windows 10 版本和 Windows 8.1 上的旧Per Monitor DPI 感知以及旧版本 Windows 上的DPI 感知,请使您的应用程序清单如下所示:

<assembly ...>
    <!-- ... --->
    <asmv3:application>
        <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
            <dpiAware>True/PM</dpiAware>
            <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
        </asmv3:windowsSettings>
    </asmv3:application>
</assembly>

References:参考:


Note that in WinForms targeting .NET 4.7 and later, you can achieve the same by adding请注意,在面向 .NET 4.7 及更高版本的 WinForms 中,您可以通过添加

<add key="DpiAwareness" value="PerMonitorV2" />

to <System.Windows.Forms.ApplicationConfigurationSection> tag in app.config .app.config中的<System.Windows.Forms.ApplicationConfigurationSection>标记。 I assume that in the end, this change modifies the manifest in the target binary as described above.我假设最后,此更改会修改目标二进制文件中的清单,如上所述。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM