Windows 缩放非客户端区域(标题栏、菜单栏)以获得每台显示器的高DPI支持

Windows 缩放非客户端区域(标题栏、菜单栏)以获得每台显示器的高DPI支持,windows,winapi,windows-10,dpi,windows-10-desktop,Windows,Winapi,Windows 10,Dpi,Windows 10 Desktop,Windows 8.1引入了为不同监视器设置不同DPI的功能。此功能称为“每监视器高DPI支持”。它在Windows 10中持续存在 如果应用程序没有选择加入(即DPI不知道或高DPI知道),DWM将自动将其放大到适当的DPI。大多数应用程序属于这两类中的一类,包括与Windows捆绑的大多数实用程序(例如记事本)。在我的测试系统上,高DPI监视器设置为150%刻度(144 DPI),而正常监视器设置为系统DPI(100%刻度,96 DPI)。因此,当您在高DPI屏幕上打开其中一个应用程序(或将

Windows 8.1引入了为不同监视器设置不同DPI的功能。此功能称为“每监视器高DPI支持”。它在Windows 10中持续存在

如果应用程序没有选择加入(即DPI不知道或高DPI知道),DWM将自动将其放大到适当的DPI。大多数应用程序属于这两类中的一类,包括与Windows捆绑的大多数实用程序(例如记事本)。在我的测试系统上,高DPI监视器设置为150%刻度(144 DPI),而正常监视器设置为系统DPI(100%刻度,96 DPI)。因此,当您在高DPI屏幕上打开其中一个应用程序(或将其拖到那里)时,虚拟化开始发挥作用,放大了所有内容,但也使其变得非常模糊

另一方面,如果应用程序明确表示支持每监视器高DPI,则不执行虚拟化,开发人员负责扩展。微软有一个相当全面的解释*,但为了一个独立的问题,我将总结一下。首先,通过在清单中设置
True/PM
来表示支持。这会选择接收,它会告诉您新的DPI设置以及建议的新窗口大小和位置。它还允许您调用函数并获取实际的DPI,而不必出于兼容性原因而撒谎。肯尼·科尔也写了一篇文章

我在一个小型C++测试应用程序中成功地完成了这一切。这是大量的样板文件,主要是项目设置,所以我不认为在这里发布完整的示例有多大意义。如果你想测试它,要么按照肯尼的指示,要么下载。现在,客户端区域中的文本看起来很好(因为我处理了
WM_DPICHANGED
),但是因为不再执行虚拟化,所以没有对非客户端区域进行缩放。结果是标题/标题栏和菜单栏的大小错误,它们在高DPI屏幕上不会变大:

所以问题是,如何将窗口的非客户端区域扩展到新的DPI?
无论您是创建自己的窗口类还是使用对话框,它们在这方面的行为都是相同的

很显然,没有答案,您唯一的选择是自定义绘制整个窗口,包括非客户端区域。虽然这当然是可能的,UWP应用程序(以前称为Metro的应用程序)也可以做到这一点,比如Windows10计算器,但对于使用许多非客户端小部件并希望看起来像本地的桌面应用程序来说,这不是一个可行的选项

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

Spy++的调查证实这只是一个bog标准的Win32对话框。所有控件都是标准的Win32 SDK控件。这不是一个UWP应用程序,他们也没有自定义绘制标题栏。它仍然具有
WS\u标题
样式。它是由explorer.exe进程启动的,该进程根据monitor high DPI aware进行标记(通过process explorer和GetProcessDPIaaWare进行验证)。确认已在Windows 10中重写运行对话框和命令提示符以正确缩放(请参阅“Command shells et al.”)运行对话框如何调整标题栏的大小?

负责新样式的“打开”和“保存”对话框的,在从每个监视器都具有高DPI感知的进程启动时也可以正确缩放,正如您在“运行”对话框中单击“浏览”按钮时所看到的。同样的道理,创造。(但是,遗留MessageBox API尚未更新,并且表现出与我的测试应用程序相同的行为。)

如果shell团队正在这样做,那么它必须是可能的。我无法想象负责设计/实施每监视器DPI支持的团队会忽视为开发人员提供一种合理的方式来生产兼容的应用程序。像这样的特性需要开发人员的支持,或者它们是现成的。即使WPF应用程序被破坏,Microsoft的项目也无法扩展非客户端区域,导致标题栏大小错误。我不太赞成阴谋论,但这似乎是一种阻止桌面应用开发的营销举措。如果是这样的话,而且没有官方的方式,我会接受那些依赖于未记录行为的答案

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

到目前为止,我的最佳想法是处理消息以调整非客户端区域的大小。通过使用带有的
SWP\u FRAMECHANGED
标志,我可以强制窗口调整大小并重新绘制其非客户端区域,以响应
WM\u DPICHANGED
。标题似乎在系统DPI确定的高度达到峰值。即使有效,这也不是理想的解决方案,因为它对系统绘制的菜单栏或