WPF Per Monitor DPI 完整适配指南:3 步解决多显示器缩放模糊
【免费下载链接】WPF-SamplesRepository for WPF related samples项目地址: https://gitcode.com/gh_mirrors/wp/WPF-Samples
本文带你过一遍 WPF-Samples 的 WPF Per Monitor DPI 适配流程:app.manifest 开 dpiAwareness、App.Config 配开关、接 DpiChanged 事件、备好多尺寸图片。照做就能修掉窗口跨屏后文字发虚、图片变糊的老毛病 ✅
问题场景:多屏 DPI 不一致,界面为什么会糊
两块显示器,左 100%、右 150% 缩放。窗口拖到右边那块,图标发虚、文字毛糙。
原因:WPF 默认只有 System DPI 感知——窗口按主屏缩放整体绘制,跨到别的屏幕时,系统只能拉伸或压缩整张位图。拉大就糊,压缩就浪费分辨率。
原理速览:Per Monitor DPI 到底是什么
Per Monitor DPI(每显示器 DPI 感知)随 .NET 4.6.2 和 Windows 10 周年更新引入。意思很直白:系统把窗口所在屏幕的真实缩放比例告诉应用,应用按这个比例自己重绘内容。
启用后窗口跨屏会自动切到目标缩放下原生渲染,不再拉伸位图。纯 WPF 内容配好两个文件即可;图片、宿主控件、底层文本 API 还要补几行代码。
上手三步:配置 dpiAwareness 并跑通跨屏验证
第 1 步:app.manifest 开启 PerMonitor 感知 ✅
项目里新建 app.manifest(右键项目 -> 添加 -> 新建项 -> 应用程序清单文件),补上这两个标签:新标签在 Windows 10 周年更新及以上生效,老标签给旧系统兜底。
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> </windowsSettings> </application>第 2 步:App.Config 加 WPF 侧开关 ✅
目标框架低于 .NET 4.6.2 才需要这一步,加在 App.Config 的 configuration 节里:
<runtime> <AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false"/> </runtime>两个坑:它叫 DoNotScale 却要设成 false,双重否定才算开;AppContextSwitchOverrides 一个文件只能出现一次,已有就分号分隔追加。
第 3 步:拖窗口做第一次跨屏验证 🚀
编译运行,把窗口在两块不同缩放的显示器间来回拖。文字锐利、控件自动跟随新屏缩放,基础配置就通了。
运行时响应:窗口跨屏时监听 DpiChanged 事件
布局会自动跟随缩放,但换图片、重缩放宿主控件得你自己接事件 ⚡ 速查表如下:
| API | 什么时候用 |
|---|---|
Window.DpiChanged | 窗口移到不同缩放显示器时触发;作为整窗内容重排或重渲染的主入口 |
Image.DpiChanged | 路由事件,窗口内每个 Image 都收到;换入对应缩放图片,可在 XAML 根上集中监听 |
WindowsFormsHost.DpiChanged | 每个宿主控件都会触发;算出新旧缩放比后对子控件调Control.Scale |
VisualTreeHelper.GetDpi(visual) | 静态方法返回DpiScale;随时查某个视觉当前的渲染 DPI |
VisualTreeHelper.SetRootDpi(visual, dpi) | 仅无父级的根视觉可调;RenderTargetBitmap渲染树外视觉前声明目标 DPI |
查当前 DPI 一行搞定:
DpiScale dpi = VisualTreeHelper.GetDpi(myImage);WinForms 宿主的完整做法参考 PerMonitorDPI/WinFormsHost/ 示例:在 DpiChanged 里算 NewDpi 与 OldDpi 的 PixelsPerDip 之比,传给托管控件的 Scale,字体同步放大。
资源准备:图片按 100/200/400 三档备齐 🖼️
同一张图备 100%、200%、400% 三档,命名统一成 scale-100.png 这种格式。运行时读当前 PixelsPerDip,取最接近且不小于它的那档加载——微软的建议是宁缩勿放,所以留三档而不是两档。
PerMonitorDPI/ImageScaling/ 示例做了个 DpiAwareImage 控件:首次加载用 GetDpi 选图,重写 OnDpiChanged 后每次缩放变化再换一次 Source,思路可以直接抄。
文本侧,用 FormattedText、TextSource 这类 API 自绘文字时,要把目标 DPI 经新增的 PixelsPerDip 传进去,DPI 变化时强制重排重绘,对照 PerMonitorDPI/TextFormatting/ 示例即可。
验收清单:测试环境与自检查项
测试环境要求:
- Windows 10 周年更新或更高版本
- 两块不同缩放的显示器(如 100% 与 150%)
自检查项:
- 拖窗跨屏:目标缩放下文字锐利,无整体拉伸感
- 含图片:跨屏后自动换到对应 scale 资源
- 含 WinForms 宿主:跨屏后子控件尺寸、字体按比例跟随
- 用 RenderTargetBitmap 的:渲染前调用 SetRootDpi 声明目标 DPI
- App.Config 里 AppContextSwitchOverrides 仅出现一次,多开关分号分隔
- 用到新 API 的项目已把目标框架升到 .NET 4.6.2
已知限制:WinForms 应用经 ElementHost 托管 WPF、Win32 窗口经 HwndSource 托管 WPF,均不支持 Per Monitor DPI——WM_DPICHANGED 只发给顶层窗口,WPF 收不到。
🎯 想动手,直接打开 PerMonitorDPI/ 下的四个示例:FormattedTextExample、ImageScaling、TextFormatting、WinFormsHost,各对应本文一个场景。边跑边拖屏,验证最快。
【免费下载链接】WPF-SamplesRepository for WPF related samples项目地址: https://gitcode.com/gh_mirrors/wp/WPF-Samples
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考