扩展非工作区(标题栏,菜单栏)以支持每个监视器的高DPI支持 [英] Scaling the non-client area (title bar, menu bar) for per-monitor high-DPI support

查看:155
本文介绍了扩展非工作区(标题栏,菜单栏)以支持每个监视器的高DPI支持的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Windows 8.1引入了为不同的监视器具有不同的DPI设置的功能.此功能被称为每个监视器的高DPI支持".它会持续存在,并且已得到进一步完善在Windows 10中.

如果某个应用程序未选择启用(,它不了解DPI或识别高DPI),则DWM会自动将其放大到适当的DPI.大多数应用程序属于这两类之一,包括与Windows捆绑在一起的大多数实用程序(例如 ,记事本).在我的测试系统上,高DPI监视器设置为150%标度(144 DPI),而普通监视器设置为系统DPI(100%标度,96 DPI).因此,当您在高DPI屏幕上打开这些应用程序之一(或将其拖到那里)时,虚拟化就会启动,放大所有内容,但也使其变得非常模糊.

另一方面,如果应用程序明确表明它支持每个监视器的高DPI,则不会执行任何虚拟化,并且开发人员负责扩展. Microsoft在此处 * ,但是为了解决一个独立的问题,我将进行总结.首先,通过在清单中设置<dpiAware>True/PM</dpiAware>来表示支持.这使您选择接收 WM_DPICHANGED消息,该消息告诉您新的DPI设置以及建议的窗口新大小和位置.它还允许您调用 GetDpiForMonitor 函数并获取 actual DPI,但出于兼容性方面的考虑而没有被引用.肯尼·科尔(Kenny Kerr)还撰写了全面的教程.

我已经在一个小型C ++测试应用程序中成功完成了所有这些工作.这是很多样板,主要是项目设置,因此在此处发布完整示例并没有多大意义.如果要进行测试,请按照肯尼的指示进行操作,有关MSDN的本教程,或下载官方SDK样本.现在,客户区中的文本看起来不错(因为我对WM_DPICHANGED的处理),但是由于不再执行虚拟化,因此无法缩放非客户区.结果是标题/标题栏和菜单栏为错误的大小-在高DPI屏幕上它们不会变大:

问题是,如何获得窗口的非客户区域来缩放到新的DPI?
无论创建自己的窗口类还是使用对话框都没关系-它们在这方面具有相同的行为.

它已经建议 没有答案-您唯一的选择是自定义绘制整个窗口,包括非客户区域.尽管这确实是可行的,而且确实确实可以实现UWP应用程序(以前称为Metro)的功能,例如Windows 10计算器,但对于使用许多非客户端小部件并希望看起来像本机的桌面应用程序来说,这不是一个可行的选择.

除此之外,它显然是错误的.自定义绘制的标题栏不能是获得正确行为的唯一方法,因为Windows Shell团队已经做到了.不起眼的运行"对话框的行为完全符合预期,在具有不同DPI的监视器之间拖动时,可以正确调整客户端和非客户端区域的大小:

使用Spy ++进行的调查确认,这只是标准的Win32对话框-没什么.所有控件都是标准的Win32 SDK控件.它不是UWP应用程序,也不是他们自定义的标题栏,它仍然具有WS_CAPTION样式.它由explorer.exe进程启动,该进程 被标记为针对每个监视器的高DPI感知能力(已通过Process Explorer和GetProcessDpiAwareness进行验证). 此博客文章确认Windows 10中已重新编写了运行"对话框和命令提示符"以正确缩放(请参见"命令外壳等"."). 运行"对话框如何调整其标题栏的大小?

公用项目对话框API 负责新型的打开"和保存"对话框,在从每个监视器的高级DPI意识的进程中启动时,也可以正确缩放,如在运行"对话框中单击浏览"按钮时所看到的. 任务对话框API ,从而创建应用程序启动带有不同大小标题栏的对话框的奇怪情况. (但是,旧版MessageBox API尚未更新,并且表现出与我的测试应用相同的行为.)

如果Shell团队正在这样做,就必须做到这一点.我简直无法想象负责设计/实现每个监视器DPI支持的团队被忽视了,无法为开发人员提供合理的方法来实现此目标.产生兼容的应用程序.像这样的功能需要开发人员支持,否则它们是开箱即用的.甚至WPF应用程序都已损坏-Microsoft的每个监视器都知道WPF示例项目无法缩放非客户区域,导致标题栏大小错误.我对阴谋论的看法不多,但这是一种行销动机,以阻止台式机应用程序的开发.如果是这样,并且没有正式的方法,我将接受依赖于未记录的行为的答案.

说到未记录的行为,当在具有不同DPI设置的监视器之间拖动运行"对话框时,记录窗口消息表明它接收到未记录的消息0x02E1.这有点有趣,因为此消息ID比记录的 WM_DPICHANGED 消息(0x02E0).但是,无论其DPI感知设置如何,我的测试应用程序都不会收到此消息. (奇怪的是,仔细检查确实发现,当窗口移至高DPI监视器时,Windows 稍微会增加标题栏上的最小化/最大化/关闭字形的大小.它们仍然没有那么大就像它们在虚拟化时一样,但是它们比它在未缩放的系统DPI应用程序中使用的字形稍大.)

到目前为止,我最好的主意是处理 WM_NCCALCSIZE 消息来调整非客户区的大小.通过将SWP_FRAMECHANGED标志与 SetWindowPos函数,我可以强制窗口调整大小并重新绘制其非工作区以响应WM_DPICHANGED. 此方法可以很好地减小标题栏的高度,甚至完全将其删除,但它永远不会更高.字幕似乎在系统DPI确定的高度达到峰值.即使可行,这也不是理想的解决方案,因为它对系统绘制的菜单栏或滚动条和hellip;毫无帮助,但至少这是一个开始.还有其他想法吗?

**我知道这篇文章说 请参见上文,以了解为什么这是(1)错误和(2)不令人满意的原因.我正在寻找一种解决方法,而不是自定义绘制非客户区域.

解决方案

在任何最新的Windows Insider内部版本中(内部版本> = 14342,SDK版本号> = 14332),都有一个EnableNonClientDpiScaling API(采用HWND)作为其参数)将为顶级HWND启用非客户端DPI缩放.此功能要求顶层窗口在每个监视器的DPI感知模式下运行.应该从窗口的WM_NCCREATE处理程序中调用此API.当在顶层窗口上调用此API时,其标题栏,顶层滚动条,系统菜单和菜单栏将在应用程序的DPI更改时进行DPI缩放(当应用程序移动到具有不同显示比例的显示时,可能会发生这种情况值或由于其他原因(例如用户进行设置更改或RDP连接更改比例因子)而导致DPI更改时).

此API不支持缩放子窗口的非工作区,例如子窗口中的滚动条.

据我所知,没有此API,就无法自动进行非客户区域DPI缩放.

请注意,此API尚未完成,在Windows 10周年更新中发布之前,它可能会更改. MSDN最终定稿时,请密切关注MSDN上的官方文档.

Windows 8.1 introduced the ability to have different DPI settings for different monitors. This feature is known as "per-monitor high-DPI support." It persists and has been further refined in Windows 10.

If an application does not opt in (i.e., is either DPI-unaware or high-DPI aware), it will be automatically scaled up by DWM to the proper DPI. Most applications fall into one of these two categories, including most of the utilities bundled with Windows (e.g., Notepad). 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). 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.

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. 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. 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. It also allows you to call the GetDpiForMonitor function and obtain the actual DPI, without being lied to for compatibility reasons. Kenny Kerr has also written up a comprehensive tutorial.

I've gotten all of this going successfully in a little C++ test app. 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. 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. 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:

So the question is, how do I get the non-client area of the window to scale to the new 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.

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. 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:

Investigation with Spy++ confirms this is just a bog-standard Win32 dialog—nothing fancy. All of the controls are standard Win32 SDK controls. It is not a UWP app, nor have they custom-drawn the title bar—it still has the WS_CAPTION style. It is launched by the explorer.exe process, which is marked as per-monitor high-DPI aware (verified with Process Explorer and 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."). 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. Same thing for the Task Dialog API, creating the odd situation where an app launches a dialog box with a different-size title bar. (The legacy MessageBox API has not been updated, however, and exhibits the same behavior as my test app.)

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. 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. 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. This is somewhat interesting because this message ID is exactly one greater than the documented WM_DPICHANGED message (0x02E0). My test app never gets this message, though, regardless of its DPI-awareness settings. (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.)

So far, my best idea has been to handle the WM_NCCALCSIZE message to adjust the size of the non-client area. 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. 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. 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." See above for why that is (1) wrong and (2) unsatisfactory. 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. This functionality requires that the top-level window be running in per-monitor DPI-awareness mode. This API should be called from the WM_NCCREATE handler for the window. 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).

This API does not support scaling non-client area for child windows, such as scroll bars in a child window.

To my knowledge there is no way to have non-client area DPI scale automatically without this API.

Note that this API has not yet been finalized, and it may change prior to being released in the Windows 10 Anniversary update. Keep your eye on MSDN for official documentation when it becomes final.

这篇关于扩展非工作区(标题栏,菜单栏)以支持每个监视器的高DPI支持的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆