点击上方蓝字关注“汪宇杰博客”
导语
WinForms 是运行在Windows上的传统.NET桌面应用技术框架。由于历史原因,它对高DPI以及跨不同DPI屏幕的支持有些问题,本文将探索尽可能的解决方案。
Windows 的“黑历史”
Windows 系统的默认DPI(更确切的说法叫 PPI)是96。PPI 的意思是 Pixels per inch,也就是每英寸屏幕显示多少像素的意思。这个值越高,表示屏幕的显示能力越细腻。
但也意味着,要显示物理尺寸和低PPI屏幕相同的画面,高PPI屏幕需要更多的像素来填充。对于一张非矢量图来说,这个问题不好解决,由于向高像素拉伸,这个图片会被“拉模糊”。类似的问题也发生在Windows中,特别是老程序,设计的时候只考虑了96 PPI。而这个历史原因可以在微软的这篇博客里找到详解:
https://blogs.msdn.microsoft.com/fontblog/2005/11/08/where-does-96-dpi-come-from-in-windows/
例:在150% DPI的屏幕上,Windows管理控制台(MMC)均会发生模糊。(可能在微信或网页里不明显)
而在100% DPI 的屏幕上,图像是清晰的。
努力的 Windows 10
由于现在的电脑高分屏(HDPI)越来越多,Windows 10 每半年一次的 Feature Update 一直在努力解决 DPI 的问题。我们可以通过下图的设置搭配,解决很多老程序的DPI适配。但是很难做到跨屏幕DPI自适应。
所谓跨屏幕DPI自适应(Per Monitor-DPI aware),意思就是当你的电脑有外接屏幕时,Windows会选择适配该屏幕的DPI来显示外接屏幕的图像。这个DPI很可能和你电脑的主屏幕是不一样的。例如,用 Surface Pro 外接一个 1920x1080 的22寸显示器,那么Surface的主屏幕通常是 150%以上DPI,而外接显示器是100%。
如果程序自己不支持 Per Monitor-DPI aware,那么你用Windows自带的兼容模式调整完,会发现虽然两个屏幕都是清晰的图像,但是应用界面在低DPI屏幕上会被放大。并不完美。所以最地道的解决方式,是开发支持 Per Monitor-DPI aware 的程序。
微软自己的应用也有这方面的改进。例如 Visual Studio 2019 开始已经天然支持 Per Monitor-DPI aware。(要求 Windows 10 v1803及.NET Framework 4.8)
WinForms 能抢救吗
Windows桌面开发最native的三种技术分别是:WinForms、WPF、UWP。因为UWP诞生于现代,所以天生没有DPI适配问题。而WPF的XAML界面也可以轻松适配DPI。唯独 WinForms 历史包袱太重,不改是不行的。我们来试试能否抢救。
首先,我在VS2019中使用150% DPI的主屏幕,设计器视图不模糊,但按钮尺寸有问题,控件位置如下:
留意红色箭头位置。在VS里一切正常。然而运行起来,在150% DPI的主屏幕是会模糊,并且控件错位。
将窗口拖动到100% DPI的屏幕上,UI不模糊,但控件依旧错位。
按照微软官网文档 https://docs.microsoft.com/en-us/dotnet/framework/winforms/high-dpi-support-in-windows-forms 的描述,.NET Framework 从4.7开始,改善WinForms的DPI支持。因此第一步,我将该程序的运行时改为4.7.2(Windows 10 1803以上版本自带)
在应用根目录加入一个 app.manifest 文件。
取消注释其中的 assembly/compatibility/application 下的Windows 10 GUID。
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
然后在 App.config 底下加入:
<System.Windows.Forms.ApplicationConfigurationSection>
<add key="DpiAwareness" value="PerMonitorV2" />
</System.Windows.Forms.ApplicationConfigurationSection>
现在发现控件位置在150% DPI的主屏幕上正确显示,整个UI不模糊。
但是在 100% DPI 的屏幕上,虽然UI不模糊,但是控件位置依然不正确,并且TextBox变的巨大无比。
微软文档里没提别的方法。但是我发现将运行时改成.NET Framework 4.8 可以修复这个TextBox的爆,但是控件位置依然不正确。
经过仔细观察,发生问题的并不是 TextBox、Label、Checkbox 这几个控件,而是 MonthCalendar 在100% DPI的屏幕上比 150% 的主屏宽。并且 Panel、TableLayoutPanel和Dock的组合拳都没法办法解决这个问题。
.NET Core 3.0 能解决吗?
.NET Core 3.0 目前还在preview 6阶段。从我实验的结果来看,它的DPI适配不需要App.config,而是在Program.cs里加入:
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
但最终效果和以上的.NET Framework 4.8的效果是一样的,虽然跨DPI屏幕界面不会模糊,但是 MonthCalendar 的宽度问题依旧。
结论
在 Windows 10 v1903 上(其他版本我没试过),通过 .NET Framework 4.8 + app.manifest + app.config 的配置,可以一定程度上让 WinForms 具有 Per Monitor-DPI aware 的能力,但是部分控件的尺寸还是会不一样,因此发布程序之前需要仔细测试,保证UI可用性,再向用户提供跨屏幕DPI自适应支持。
我的样例程序代码:https://github.com/EdiWang/DotNet-Samples/tree/master/WinForms-DPI-PMA/PerMonitorAwareDPIForms