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

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


添加应用程序清单文件

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

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


▲ 项目中新增的两个文件

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

如果你没有 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。
    • 如果你在程序启动的时候调用了 SetProcessDpiAwarenessSetProcessDPIAware 函数,那么就会按照调用此函数的效果来感知 DPI。
  • 包含 true 字符串
    • 当前进程设置为系统级 DPI 感知(System DPI Awareness)。
  • 包含 false 字符串
    • 在 Windows Vista / 7 / 8 中,与什么都不填的效果是一样的。
    • 在 Windows 8.1 / 10 中,当前进程设置为不感知 DPI(Unaware),就算你调用了 SetProcessDpiAwarenessSetProcessDPIAware 也是没有用的。
  • 包含 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),就算你调用了 SetProcessDpiAwarenessSetProcessDPIAware 也是没有用的。

说明一下,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。
    • 如果你在程序启动的时候调用了 SetProcessDpiAwarenessSetProcessDPIAware 函数,那么就会按照调用此函数的效果来感知 DPI。
  • 第一个能识别的感知级别是 system
    • 当前进程设置为系统级 DPI 感知(System DPI Awareness)。
  • 第一个能识别的感知级别是 permonitor
    • 当前进程设置为屏幕级 DPI 感知(Per-Monitor DPI Awareness)。
  • 第一个能识别的感知级别是 permonitorv2
    • 当前进程设置为第二代屏幕级 DPI 感知(Per-Monitor V2 DPI Awareness)。
    • 仅在 Windows 10 (1703) 及以上版本才可被识别
  • 第一个能识别的感知级别是 unaware

使 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


▲ 150% DPI

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

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

-->

▲ 100% DPI


▲ 150% DPI

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

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

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

dpiAwareness 设置为 PerMonitorV2


▲ 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 中的源码。


参考资料

支持 Windows 10 最新 PerMonitorV2 特性的 WPF 多屏高 DPI 应用开发的更多相关文章

  1. Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32)

    本文将介绍 Windows 系统中高 DPI 开发的基础知识.由于涉及到坐标转换,这种转换经常发生在计算的不知不觉中:所以无论你使用哪种 Windows 下的 UI 框架进行开发,你都需要了解这些内容 ...

  2. 《连载 | 物联网框架ServerSuperIO教程》-4.如开发一套设备驱动,同时支持串口和网络通讯。附:将来支持Windows 10 IOT

    1.C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍 <连载 | 物联网框架ServerSuperIO教程>1.4种通讯模式机制. <连载 | 物联网框架Serve ...

  3. 最新更新的Windows 10切换蓝牙连接开关会导致蓝屏死机

    目前已经升级到 Windows 10 Version 1809 版的用户们正在努力发掘新版本还没有被公开发现的错误问题. 比如此前的.ZIP 格式的压缩包解压时无法正常替换,微软已承认该问题并称将在后 ...

  4. easybcd 支持 windows 10 和 ubuntu 14.04 双系统启动

    家里计算机系统 windows 10 全新安装. 原本是双系统的,还有一个ubuntu. windows 10 安装以后,恢复ubuntu就是问题了. (事后经验:请不要立刻安装bcd修改工具) 最初 ...

  5. DevExpress Windows 10 v19.1新版亮点:UWP控件新功能全面解析

    行业领先的.NET界面控件DevExpress 日前正式发布v19.1版本,本站将以连载的形式介绍各版本新增内容.在本系列文章中将为大家介绍DevExpress WPF v19.1中新增的一些控件及部 ...

  6. Windows 10 版本 1507 中的新 AppLocker 功能

    要查看 Windows 10 版本信息,使用[运行]> dxdiag  回车 下表包含 Windows 10 的初始版本(版本 1507)中包括的一些新的和更新的功能以及对版本 1511 的 W ...

  7. Build 2015 Beijing & Windows 10 China Geek Challenge

    6月5日,借着Build 2015在北京召开分会的东风,参加了这次由微软中国举办的Windows 10中国开发者极客挑战赛. 白天是Build 2015课程,学习了Windows 10最新的技术,包括 ...

  8. Windows 10 TH2

    Windows 10 TH2到底更新了啥? 15年11月,微软正式向Windows 10用户推送了Threshold 2(简称TH2)更新, 也就是传说中的November Update.更新后系统版 ...

  9. [转*译]Networking API Improvements in Windows 10

        在当今,以云优先,移动优先技术为宗旨的时代下,大多数Apps都至少有一些与web服务或网络上其他设备的集成.这些包括应用程序,它获取天气在线内容,新闻或体育比赛的分数,媒体或下载的播客,甚至对 ...

随机推荐

  1. #C++初学记录(初识汉诺塔)

    汉诺塔 题目 用1,2,...,n表示n个盘子,称为1号盘,2号盘,....号数大盘子就大.经典的汉诺塔问 题经常作为一个递归的经典例题存在.可能有人并不知道汉诺塔问题的典故.汉诺塔来源于 印度传说的 ...

  2. hdu 5185 动态规划 分析降低复杂度

    这题说的是 x[1]+x[2]+x[3]+…+x[n]=n, 这里 0 <= x[i] <= n && 1 <= i <= n x[i] <= x[i+1 ...

  3. DB开发之oracle存储过程

    1. 存储过程格式 /* Formatted on 2011/1/17 13:20:44 (QP5 v5.115.810.9015) */ CREATE OR REPLACE procedure pr ...

  4. extern--C#调用C++等其他非托管代码

    例如: https://www.cnblogs.com/sosoft/p/extern.html

  5. PHP设计模式_单例模式

    了解 单例设计模式用于限制特定对象只能被实例化创建一次,有且只有一个此类型的资源.例如,通过数据库句柄到数据库的连接是独占的.您希望在应用程序中共享数据库句柄,因为在保持连接打开或关闭时,它是一种开销 ...

  6. Java集合转有类型的数组之toArray(T[] a)

    在java变成中慎用强制类型转换,尽量使用类自带的转换函数或泛型.先看一行代码 错误方法: String[] array= (String[]) list.toArray(); 如果list中存放的是 ...

  7. ngular6开发不完全笔记(三)-- 报错指南

    router Uncaught Error: Template parse errors: 'router-outlet' is not a known element: If 'router-out ...

  8. HDU 2457 DNA repair(AC自动机+DP)题解

    题意:给你几个模式串,问你主串最少改几个字符能够使主串不包含模式串 思路:从昨天中午开始研究,研究到现在终于看懂了.既然是多模匹配,我们是要用到AC自动机的.我们把主串放到AC自动机上跑,并保证不出现 ...

  9. mysql Alter 的问题

    转自:https://blog.csdn.net/c_enhui/article/details/50903351 -- 设置或删除列的默认值.该操作会直接修改.frm文件而不涉及表数据. 此操作很快 ...

  10. Codeforces Round #319 (Div. 2) B. Modulo Sum 抽屉原理+01背包

    B. Modulo Sum time limit per test 2 seconds memory limit per test 256 megabytes input standard input ...