支援 Windows 10 最新 PerMonitorV2 特性的 WPF 多屏高 DPI 應用開發
- 2020 年 2 月 10 日
- 筆記
支援 Windows 10 最新 PerMonitorV2 特性的 WPF 多屏高 DPI 應用開發
發佈於 2018-10-22 18:04 更新於 2018-12-14 01:54
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。
- 如果你在程式啟動的時候調用了 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 還大行其道的今天,為了兼容,dpiAwareness
和 dpiAware
都設置是比較靠譜的。
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 的設置後,幾乎得到一個資訊,dpiAware
和 dpiAwareness
都是要設置的,除非以後絕大多數用戶的系統版本都到達 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</dpiAware> </windowsSettings> </application>
需要注意:系統版本在 Windows 10 (1703) 或以上,V2 的感知級別才會生效,否則就直接使用系統級 DPI 感知。
第一代和第二代的 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>
注意:
- 這個值要設為
false
。(微軟官方吐槽:Yes, set it to false. Double negative FTW!) 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 中的源碼。
參考資料
- 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
本文會經常更新,請閱讀原文: https://blog.walterlv.com/post/windows-high-dpi-development-for-wpf.html ,以避免陳舊錯誤知識的誤導,同時有更好的閱讀體驗。
本作品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名 呂毅 (包含鏈接: https://blog.walterlv.com ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發布。如有任何疑問,請 與我聯繫 ([email protected]) 。