当前位置: 首页 > news >正文

支持 Windows 10 最新 PerMonitorV2 特性的 WPF 多屏高 DPI 应用开发

原文: 支持 Windows 10 最新 PerMonitorV2 特性的 WPF 多屏高 DPI 应用开发

版权声明:本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:http://blog.csdn.net/wpwalter/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系(walter.lv@qq.com)。 https://blog.csdn.net/WPwalter/article/details/83476895

Windows 10 自 1703 开始引入第二代的多屏 DPI 机制(PerMonitor V2),而 WPF 框架可以支持此第二代的多屏 DPI 机制。

本文将介绍 WPF 框架利用第二代多屏 DPI 机制进行高 DPI 适配的方法。同时,也介绍低版本的 WPF 或者低版本的操作系统下如何做兼容。


本文内容

      • 添加应用程序清单文件
        • 如果你没有 app.config,如何添加?
        • 如果你没有 app.manifest,如何添加?
      • 了解 WPF 清单文件中的 DPI 感知设置
        • DpiAware
        • DpiAwareness
      • 使 WPF 程序支持 Per-Monitor V2 级 DPI 感知
      • WPF 程序在特殊清单设置下的效果
      • 低版本 .NET Framework 和 低版本 Windows 下的 WPF DPI 缩放
        • 参考资料

添加应用程序清单文件

在你现有 WPF 项目的主项目中需要添加两个文件以支持第二代的多屏 DPI 机制。

  • app.manifest (决定性文件)
  • app.config (修复 Bug, .NET Framework 4.6.2 及以上可忽略)

项目中新增的两个文件
▲ 项目中新增的两个文件

默认情况下,app.config 在你创建 WPF 项目的时候就会存在,而 app.manifest 则不是。如果你的项目中已经存在这两个文件,就不需要添加了。

如果你没有 app.config,如何添加?

打开项目属性,然后在属性中选择 .NET Framework 的版本,无论你选择哪个,app.config 都会自动为你添加。

选择 .NET Framework 版本以便添加 app.config 文件

当然,正统的方法是跟下面的 app.manifest 的添加方法相同,你会在下面看到 Visual Studio 新建项中 app.manifest 和 app.config 文件是挨在一起的。

如果你没有 app.manifest,如何添加?

新建文件的时候选择应用程序清单文件(应用程序配置文件就在旁边)
▲ 新建文件的时候选择应用程序清单文件(应用程序配置文件就在旁边)

了解 WPF 清单文件中的 DPI 感知设置

DpiAware

在你打开了 app.manifest 文件后,找到以下代码,然后取消注释:

<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
    DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need 
    to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should 
    also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
<!--
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
    <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
-->

上面这一段代码是普通的 DPI 感知的清单设置,开启后获得系统 DPI 感知级别(System DPI Awareness)。

如果要开启 Per-Monitor DPI 感知,将上面的 true 改成 true/pm(pm 表示 per-monitor)。

不过这只是兼容性的设计而已,感谢老版本的系统使用字符串包含的方式,于是可以老版本的系统可以兼容新的 DPI 感知值:

  • 什么都不填
    • 如果你额外也没做什么 DPI 相关的操作,那么就是 Unaware。
    • 如果你在程序启动的时候调用了 SetProcessDpiAwareness 或 SetProcessDPIAware 函数,那么就会按照调用此函数的效果来感知 DPI。
  • 包含 true 字符串
    • 当前进程设置为系统级 DPI 感知(System DPI Awareness)。
  • 包含 false 字符串
    • 在 Windows Vista / 7 / 8 中,与什么都不填的效果是一样的。
    • 在 Windows 8.1 / 10 中,当前进程设置为不感知 DPI(Unaware),就算你调用了 SetProcessDpiAwareness 和 SetProcessDPIAware 也是没有用的。
  • 包含 true/pm 字符串
    • 在 Windows Vista / 7 / 8 中,当前进程设置为系统级 DPI 感知(System DPI Awareness)。
    • 在 Windows 8.1 / 10 中,当前进程设置为屏幕级 DPI 感知(Per-Monitor DPI Awareness)。
  • 包含 per monitor 字符串
    • 在 Windows Vista / 7 / 8 中,与什么都不填的效果是一样的。
    • 在 Windows 8.1 / 10 中,当前进程设置为屏幕级 DPI 感知(Per-Monitor DPI Awareness)。
  • 其他任何字符串
    • 在 Windows Vista / 7 / 8 中,与什么都不填的效果是一样的。
    • 在 Windows 8.1 / 10 中,当前进程设置为不感知 DPI(Unaware),就算你调用了 SetProcessDpiAwareness 和 SetProcessDPIAware 也是没有用的。

说明一下,SetProcessDpiAwareness 是新 API,要求的最低系统版本是 Windows 8.1,调用这个才能指定为 Per-Monitor 的 DPI 感知。而 SetProcessDPIAware 是 Vista 开始引入的老 API,没有参数可以传。

DpiAwareness

<asmv3:application>
  <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
    <dpiAwareness>PerMonitorV2, unaware</dpiAwareness>
  </asmv3:windowsSettings>
</asmv3:application>

注意:只有 Windows 10 (1607) 及以上版本才会识别此节点的 DPI 设置。如果你设置了 dpiAwareness 节点,那么 dpiAware 就会被忽略。

建议你可以两个节点都指定,这样既可以使用到 Windows 10 1607 的新特性,又可以兼容老版本的 Windows 操作系统。

dpiAwareness 节点支持设置一个或多个 DPI 感知级别,用逗号分隔。如果你指定了多个,那么操作系统会从第一个开始识别,如果能识别就使用,否则会找第二个。用这种方式,未来的应用可以指定当前系统不支持的 DPI 感知级别。

鉴于此,在目前 Windows 7 还大行其道的今天,为了兼容,dpiAwarenessdpiAware 都设置是比较靠谱的。

dpiAwareness 节点目前支持的值有:

  • 什么都不设置
    • dpiAware 节点的结果来
  • 整个逗号分隔的序列都没有能识别的 DPI 感知级别
    • 如果你额外也没做什么 DPI 相关的操作,那么就是 Unaware。
    • 如果你在程序启动的时候调用了 SetProcessDpiAwareness 或 SetProcessDPIAware 函数,那么就会按照调用此函数的效果来感知 DPI。
  • 第一个能识别的感知级别是 system
    • 当前进程设置为系统级 DPI 感知(System DPI Awareness)。
  • 第一个能识别的感知级别是 permonitor
    • 当前进程设置为屏幕级 DPI 感知(Per-Monitor DPI Awareness)。
  • 第一个能识别的感知级别是 permonitorv2
    • 当前进程设置为第二代屏幕级 DPI 感知(Per-Monitor V2 DPI Awareness)。
    • 仅在 Windows 10 (1703) 及以上版本才可被识别
  • 第一个能识别的感知级别是 unaware
    • 当前进程设置为不感知 DPI(Unaware),就算你调用了 SetProcessDpiAwareness 和 SetProcessDPIAware 也是没有用的。

使 WPF 程序支持 Per-Monitor V2 级 DPI 感知

前面我们分析 App.Manifest 文件中 DPI 的设置后,几乎得到一个信息,dpiAwaredpiAwareness 都是要设置的,除非以后绝大多数用户的系统版本都到达 Windows 10 (1607) 及以上。

以下是推荐的 DPI 感知级别设置:

<application xmlns="urn:schemas-microsoft-com:asm.v3">
  <windowsSettings>
    <!-- The combination of below two tags have the following effect : 
         1. Per-Monitor for >= Windows 10 Anniversary Update
         2. System < Windows 10 Anniversary Update -->
    <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
    <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
  </windowsSettings>
</application>

需要注意:

  1. 你的 .NET Framework 版本必须在 4.6.2 以上才建议这么设置,否则不建议开启 Per-Monitor 的 DPI 感知;
  2. 系统版本在 Windows 10 (1703) 或以上,V2 的感知级别才会生效,否则就是第一个版本。

第一代和第二代的 Per-Monitor 感知之间的差异,可以参考:Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32) - walterlv

额外的,如果你的 .NET Framework 版本在 .NET Framework 4.6.2 以下,但操作系统在 Windows 10 及以上,你还需要修改 App.config 文件(在 <configuration /> 节点)。

<runtime>
  <AppContextSwitchOverrides value = "Switch.System.Windows.DoNotScaleForDpiChanges=false"/>
</runtime>

注意:

  1. 这个值要设为 false。(微软官方吐槽:Yes, set it to false. Double negative FTW!)
  2. AppContextSwitchOverrides 不能被设置两次,如果一已经设置了其他值,需要用分号分隔多个值。

特别说明,当面向 .NET Framework 4.6.2 以下版本编译,但运行于 Windows 10 (1607) 和以上版本时,只需要添加 Switch.System.Windows.DoNotScaleForDpiChanges=false 即可让 WPF 程序处理 Dpi Change 消息,此时 WPF 程序就像高版本的 .NET Framework 中一样能够正常处理多屏下的 DPI 缩放。

以上,划重点 你并不需要编译为高版本的 .NET Framework 即可获得 Per-Monitor 的 DPI 缩放支持

WPF 程序在特殊清单设置下的效果

dpiAwareness 不设置,dpiAware 节点设置为 true/pm

100% DPI -->
100% DPI
▲ 100% DPI

150% DPI
▲ 150% DPI

注意到标题栏(非客户区)没有缩放,而 WPF 区域(客户区)清晰地缩放了。

dpiAwareness 不设置,dpiAware 节点设置为 true

100% DPI -->
100% DPI
▲ 100% DPI

150% DPI
▲ 150% DPI

注意到标题栏(非客户区)被缩放了,而 WPF 区域(客户区)被 DPI 虚拟化进行了位图拉伸(模糊)。

dpiAwareness 不设置,dpiAware 节点设置为 true/pm12345

此时,WPF 程序无法启动!!!而你只需要减少一位数字,例如写成 true/pm1234 即可成功启动,效果跟 true 是一样的,注意效果 不是 true/pm。也就是说,/pm 并没有显示出它的含义来。额外的,如果设为 false 但后面跟随那么长的字符串,WPF 程序是可以启动的。

dpiAwareness 设置为 PerMonitorV2

150% DPI
▲ 150% DPI

注意到标题栏(非客户区)被缩放了,而 WPF 区域(客户区)也能清晰地缩放(仅 Windows 10 1703 及以上系统才是这个效果)。

低版本 .NET Framework 和 低版本 Windows 下的 WPF DPI 缩放

由于 Windows 8.1 操作系统用户存量不多,主要是 Windows 7 和 Windows 10。所以我们要么兼容完全不支持 Per-Monitor 的 Windows 7,要么使用具有新特性的 Windows 10 即可获得最佳的开发成本平衡。使用以上的 DPI 缩放方法足以让你的 WPF 应用在任何一个 .NET Framework 版本下获得针对屏幕的 DPI 清晰缩放(Per-Monitor DPI Awareness)。

所以仅针对 Windows 8.1 做特殊的 DPI 缩放是不值得的,把 Windows 8.1 当做 Windows 7 来做那种不支持 Per-Monitor 的处理就好了。当然你硬要支持也有相关文档可以看:Developing a Per-Monitor DPI-Aware WPF Application - Microsoft Docs 了解实现方法。具体是使用 DisableDpiAwareness 特性和 Windows Per-Monitor Aware WPF Sample 中的源码。


参考资料

  • Developing a Per-Monitor DPI-Aware WPF Application - Microsoft Docs
  • WPF-Samples/Developer Guide - Per Monitor DPI - WPF Preview.docx at master · Microsoft/WPF-Samples
  • Application Manifests - Microsoft Docs

相关文章:

  • 如何使用 Quagga BGP(边界网关协议)路由器来过滤 BGP 路由
  • 常用正则表达式(高亮,markdown)
  • 一些资料
  • Selenium库简介
  • shell 相关操作
  • Android 内存分析
  • ASP.NET的几个试题(《C#与.NET程序员面试宝典》)
  • 可以使你成为更优秀程序员的5个好习惯
  • HBase生产环境配置与使用优化不完全指南
  • co模块的前端实现
  • 【转】【支付 . 技术控】最全最强解析:支付宝系统架构内部剖析(架构图)...
  • 写给正在入坑linux系统的伙伴
  • 关于Activity销毁,而绘制UI的子线程未销毁出现的问题
  • BootStrap框架
  • Django3_Url控制系统和View视图
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • 【407天】跃迁之路——程序员高效学习方法论探索系列(实验阶段164-2018.03.19)...
  • CentOS 7 修改主机名
  • EventListener原理
  • GraphQL学习过程应该是这样的
  • Javascript基础之Array数组API
  • markdown编辑器简评
  • oschina
  • 马上搞懂 GeoJSON
  • 前端工程化(Gulp、Webpack)-webpack
  • 为什么要用IPython/Jupyter?
  • 自定义函数
  • Semaphore
  • 不要一棍子打翻所有黑盒模型,其实可以让它们发挥作用 ...
  • 如何在招聘中考核.NET架构师
  • #{}和${}的区别?
  • #每天一道面试题# 什么是MySQL的回表查询
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .
  • (转)PlayerPrefs在Windows下存到哪里去了?
  • (转)关于pipe()的详细解析
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .gitignore
  • .jks文件(JAVA KeyStore)
  • .NET BackgroundWorker
  • .Net CF下精确的计时器
  • .NET Core Web APi类库如何内嵌运行?
  • .net core使用RPC方式进行高效的HTTP服务访问
  • .Net IE10 _doPostBack 未定义
  • .net 流——流的类型体系简单介绍
  • .Net 中Partitioner static与dynamic的性能对比
  • .NET6 开发一个检查某些状态持续多长时间的类
  • .NetCore Flurl.Http 升级到4.0后 https 无法建立SSL连接
  • .NET设计模式(7):创建型模式专题总结(Creational Pattern)
  • .NET序列化 serializable,反序列化
  • .sh文件怎么运行_创建优化的Go镜像文件以及踩过的坑
  • [20160902]rm -rf的惨案.txt