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

本文将介绍在 windows 系统中进行高 dpi 开发的基础知识。开发过程中,坐标转换往往在不知不觉中进行,无论你使用何种 windows ui 框架进行开发,都需要掌握这些知识,以避免频繁遇到问题。

Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32)微软主推的 Windows 桌面 UI 框架包括:

UWPWPFWindows FormsWin32 与 C++DirectX

后两者实际上不是 UI 框架,而是 UI 框架的底层实现。当然,如果你仅使用 Win32 和 DirectX 来开发 GUI 应用,没有人会阻止你,但如果你不进行 UI 组件的封装,最终会非常困难。

UWP 仅支持 Windows 10(当然也分不同的小版本,兼容性上有些小麻烦)。

WPF 和 Windows Forms 的最新版本仅支持 Windows 7 SP1 及以上系统。如果要支持 Windows 7 及更早版本的系统,需要降低 .NET Framework 版本至 4.5.2 及以下;如果要支持 XP,还需要降至 4.0 及以下。

对于普通用户,DPI 级别有两种:系统 DPI(System DPI)和屏幕 DPI(Monitor DPI)。从 Windows Vista 开始引入了系统 DPI 的概念,而从 Windows 8.1 开始引入了屏幕 DPI 的概念。

在 Windows Vista / 7 / 8 中,操作系统提供了真正的 DPI 设置:

Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32) ▲ Windows 7 的 DPI 设置(控制面板 -> 外观与个性化 -> 显示

这里的设置会改变系统的 DPI 值。

Windows 7 中还提供了传统 Windows XP 风格 DPI 缩放比例的选项(此选项在 Windows 8 之后被删除),这也是在修改 DPI 值,只是可以选择非 1/4 整数倍的 DPI 值。

Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32) ▲ 自定义 DPI 设置

自 Windows 8.1 开始,操作系统开始可以设置不同屏幕的 DPI 值:

Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32) ▲ Windows 10 中的多个屏幕选择

Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32) ▲ Windows 10 中针对每个屏幕的 DPI 设置

如果用户在设置中更改了系统 DPI 值或屏幕 DPI 值,Windows 系统会提示需要注销才能应用修改。

对于 Windows 8.1 以下的系统,注销是必要的。因为如果不注销,系统 DPI 值不会改变,应用需要在系统重新登录后获得新的 DPI 值时才能正常根据新的系统 DPI 值进行渲染。否则系统会进行位图缩放。

对于 Windows 8.1 及以上的系统,注销通常也是必要的。虽然屏幕 DPI 值已经更新,并且已向应用窗口发送了 DPI 变化消息,但系统 DPI 值依然没变。应用必须处理 DPI 变化消息才能正常渲染。如果应用不支持屏幕 DPI 感知,那么使用的就是系统 DPI 值,也会被系统进行位图缩放。

但在 Windows 10 (1803) 之后,情况有了转机。现在,你可以通过在设置中打开一个开关,使得无需注销,只要重新打开应用即可让此应用获取到最新的系统 DPI 的值。

Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32)方法是:打开“设置” -> “系统” -> “显示器” -> “高级缩放设置”,在“高级缩放设置”上,打开“允许 Windows 尝试修复应用,使其不模糊”。

此外,对于 Windows 8.1 及以上的系统,系统 DPI 值等于主屏在系统启动时的屏幕 DPI 值。

Windows 应用的 DPI 感知级别(Dpi Awareness)经过历代升级,已经有四种:

无感知 (Unaware):DPI 值为常量 96。如果在系统中设置缩放,就会采用位图拉伸(会模糊)。更多信息请看本文末尾的故事。系统级感知 (System DPI Awareness):Vista 系统引入。所有显示器上的应用共用这一个 DPI 值。每个用户会话固定一个 DPI 值,修改 DPI 后不需要重启系统而只需要注销当前用户重新登录即可。如果在设置中修改了 DPI,就会采用位图拉伸(会模糊)。屏幕级感知 (Per-Monitor DPI Awareness):随 Windows 8.1 引入。应用的 DPI 值会随着所在屏幕的不同而改变。当多个屏幕 DPI 不一样,而应用从一个屏幕切换到另一个屏幕的时候,应用会收到 DPI 改变的消息。只有应用的顶层 HWND 会收到 DPI 改变消息。屏幕级感知第二代 (Per-Monitor V2 DPI Awareness):随 Windows 10 (1607) 引入。应用的 DPI 值会随着所在屏幕的不同而改变。当多个屏幕 DPI 不一样,而应用从一个屏幕切换到另一个屏幕的时候,应用会收到 DPI 改变的消息。应用的顶层和子 HWND 都会收到 DPI 改变消息。以下 UI 元素也会在 DPI 改变时缩放:非客户区(Non-client Area)、系统通用控件中的位图(comctl32V6)、对话框(CreateDialog)。

在 Windows 10 19H1 中,可以直接在任务管理器中查看进程的 DPI Awareness:

Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32) ▲ 在任务管理器中查看 DPI Awareness

方法是在任务管理器中 Details 的标题栏右键,选择列,然后找到 DPI Awareness。

可以看到,目前仅文件资源管理器是 Per-Monitor V2 的。

关于在任务管理器中查看 DPI,可以阅读我的另一篇博客:

Windows 系统上使用任务管理器查看进程的各项属性(命令行、DPI、管理员权限等) – 吕毅任务管理器上关于 DPI 的中文翻译也是蛮有意思的。

AppMall应用商店 AppMall应用商店

AI应用商店,提供即时交付、按需付费的人工智能应用服务

AppMall应用商店 56 查看详情 AppMall应用商店

不同 UI 框架对 DPI 的支持情况:

UWP:UWP 当然支持最新的各种 DPI 感知级别,而且是完全支持的。WPF:WPF 的最新版支持最新的 DPI 感知级别,不过依然有限制:即原生 WPF 应用支持 DPI 缩放,在其他 UI 框架中的 WPF 也支持 DPI 缩放;但是 WPF 中嵌入的其他 UI 框架不支持自动 DPI 缩放。WPF 第一个版本(随 .NET Framework 3.5 发布)就已支持系统级 DPI 感知。.NET Framework 4.6.2 开始的 WPF 才开始支持屏幕级 DPI 感知。而 Per-Monitor V1 和 Per-Monitor V2 的支持在操作系统级别是兼容的,所以只需要修改 WPF 中的应用程序清单即可兼容第二代屏幕级 DPI 感知。Windows Forms:Windows Forms 也是在 .NET Framework 4.7 才开始支持屏幕级 DPI 感知的。不过部分控件不支持自动随屏幕 DPI 切换。其他 UI 框架:原生 Win32 是支持最新 DPI 感知的,其他如 GDI/GDI+/MFC 等都不支持,除非开发者手工编写。

当项目足够大的时候,一个或几个项目成员可能很难了解所有的窗口逻辑。让一个进程的所有窗口开启 DPI 缩放对应用的高 DPI 迁移来说比较困难。不过好在我们可以开启混合 DPI 缩放。

Windows 10 (1604) 开始引入顶级窗口(Top-level Window)级别的 DPI 感知,而 Windows 10 (1703) 开始引入每一个 HWND 的 DPI 感知,包括顶级窗口和非顶级窗口。这里的顶级窗口指的是没有父级的窗口,指的是 Parent,而不是 Owner。(实际上 API 在更早版本就引入了,这里有故事,详见本文末尾。)

在创建一个窗口的前后分别调用 SetThreadDpiAwarenessContext 函数可以让创建的这个窗口具有单独的 DPI 感知级别。前一次是为了让窗口在创建时有一个对此线程的新的 DPI 感知级别,而后一次调用是恢复此线程的 DPI 感知级别。

关于混合 DPI 感知级别的其他内容,可以阅读官网:Mixed-Mode DPI Scaling and DPI-aware APIs – Microsoft Docs。

微软的 Office 系列就是典型的使用了混合 DPI 感知级别的应用。在以下实验中,我组成了一个 96 DPI 的主屏和 144 DPI 的副屏,先在 96 DPI 的屏幕上截一张图,再将窗口移动到 144 DPI 的屏幕中再截一张图。

Microsoft PowerPoint 使用的是系统 DPI 感知级别:

Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32) ▲ 96 DPI 下的主界面

Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32) ▲ 144 DPI 下的主界面

你可以通过点开图片查看原图来比较这两幅图在原图尺寸下的模糊程度。

Microsoft PowerPoint 的演示页面使用的是屏幕 DPI 感知级别:

Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32) ▲ 96 DPI 下的演示页面

Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32) ▲ 144 DPI 下的演示页面

可以看到,演示页面在多屏 DPI 下是没有产生缩放的模糊,即采用了屏幕 DPI 感知级别。

而以上的主界面和演示页面属于同一个进程。

Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32) ▲ 只有一个 PowerPoint 进程

DPI 相关的 Windows API 的迁移:

GetSystemMetrics -> GetSystemMetricsForDpiAdjustWindowRectEx -> AdjustWindowRectExForDpiSystemParametersInfo -> SystemParametersInfoForDpiGetDpiForMonitor -> GetDpiForWindow

关于 DPI 相关 API 变化的故事,感谢 Mouri_Naruto(毛利)提供的故事,API 的具体使用也可参考他的文章:【原创】实现每显示器高DPI识别(Per-Monitor DPI Aware)的注意事项。

关于 Windows 10,前文提到 Per-Monitor V2 是 Windows 10 (1703) 引入的,微软官方文档 High DPI Desktop Application Development on Windows – Win32 apps 也是这么写的。但实际上更早的 Windows 10 (1607) 就引入了相关 API,包括 SetThreadDpiAwarenessContext 和 PerMonitorV2 应用程序清单。并且更早的,V2 带来的非客户区缩放和子窗口 DPI 变更消息的 API 在 1507 和 1511(分别是 Windows 10 的第一和第二个正式版本)就已经有了,不过是未公开的(可参阅 【原创】实现每显示器高DPI识别(Per-Monitor DPI Aware)的注意事项)。1607 开始这两个非公开 API 不能使用了,因为换成了新的 API,参见 Setting the default DPI awareness for a process (Windows) – Win32 apps。

可以发现微软实际上宣称 1607 已经支持 Per-Monitor V2 了,而完整支持是在 1703。所谓的“完整”体现在这些地方:

comctl32 从 1703 开始完整支持缩放(参见 High DPI Scaling Improvements for Desktop Applications and “Mixed Mode” DPI Scaling in the Windows 10 Anniversary Update (1607) – Windows Developer Blog)如果指定了 PerMonitor 但没指定 PerMonitorV2,那么 1607 默认是 PerMonitor,1703 默认是 PerMonitorV2。

关于 Windows Vista 之前的系统,感谢 Mouri_Naruto(毛利)提供的历史:

参考资料:

High DPI Desktop Application Development on Windows – Microsoft DocsWPF-Samples/Developer Guide – Per Monitor DPI – WPF Preview.docx at master · Microsoft/WPF-Samples在 Windows 10 中修复显示模糊的应用 – Windows HelpFix apps that appear blurry in Windows 10 – Windows Help

本文会经常更新,请阅读原文: https://www.php.cn/link/ce737c887b558e8467dede8ca59029eb ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://www.php.cn/link/8c3af53f6554ac306d481a872a47fb83 ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 ([email protected]) 。

以上就是Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32)的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/396547.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月6日 17:49:49
下一篇 2025年11月6日 17:50:29

相关推荐

  • Uniapp 中如何不拉伸不裁剪地展示图片?

    灵活展示图片:如何不拉伸不裁剪 在界面设计中,常常需要以原尺寸展示用户上传的图片。本文将介绍一种在 uniapp 框架中实现该功能的简单方法。 对于不同尺寸的图片,可以采用以下处理方式: 极端宽高比:撑满屏幕宽度或高度,再等比缩放居中。非极端宽高比:居中显示,若能撑满则撑满。 然而,如果需要不拉伸不…

    2025年12月24日
    400
  • 如何让小说网站控制台显示乱码,同时网页内容正常显示?

    如何在不影响用户界面的情况下实现控制台乱码? 当在小说网站上下载小说时,大家可能会遇到一个问题:网站上的文本在网页内正常显示,但是在控制台中却是乱码。如何实现此类操作,从而在不影响用户界面(UI)的情况下保持控制台乱码呢? 答案在于使用自定义字体。网站可以通过在服务器端配置自定义字体,并通过在客户端…

    2025年12月24日
    800
  • 如何在地图上轻松创建气泡信息框?

    地图上气泡信息框的巧妙生成 地图上气泡信息框是一种常用的交互功能,它简便易用,能够为用户提供额外信息。本文将探讨如何借助地图库的功能轻松创建这一功能。 利用地图库的原生功能 大多数地图库,如高德地图,都提供了现成的信息窗体和右键菜单功能。这些功能可以通过以下途径实现: 高德地图 JS API 参考文…

    2025年12月24日
    400
  • 如何使用 scroll-behavior 属性实现元素scrollLeft变化时的平滑动画?

    如何实现元素scrollleft变化时的平滑动画效果? 在许多网页应用中,滚动容器的水平滚动条(scrollleft)需要频繁使用。为了让滚动动作更加自然,你希望给scrollleft的变化添加动画效果。 解决方案:scroll-behavior 属性 要实现scrollleft变化时的平滑动画效果…

    2025年12月24日
    000
  • 如何为滚动元素添加平滑过渡,使滚动条滑动时更自然流畅?

    给滚动元素平滑过渡 如何在滚动条属性(scrollleft)发生改变时为元素添加平滑的过渡效果? 解决方案:scroll-behavior 属性 为滚动容器设置 scroll-behavior 属性可以实现平滑滚动。 html 代码: click the button to slide right!…

    2025年12月24日
    500
  • 如何选择元素个数不固定的指定类名子元素?

    灵活选择元素个数不固定的指定类名子元素 在网页布局中,有时需要选择特定类名的子元素,但这些元素的数量并不固定。例如,下面这段 html 代码中,activebar 和 item 元素的数量均不固定: *n *n 如果需要选择第一个 item元素,可以使用 css 选择器 :nth-child()。该…

    2025年12月24日
    200
  • 使用 SVG 如何实现自定义宽度、间距和半径的虚线边框?

    使用 svg 实现自定义虚线边框 如何实现一个具有自定义宽度、间距和半径的虚线边框是一个常见的前端开发问题。传统的解决方案通常涉及使用 border-image 引入切片图片,但是这种方法存在引入外部资源、性能低下的缺点。 为了避免上述问题,可以使用 svg(可缩放矢量图形)来创建纯代码实现。一种方…

    2025年12月24日
    100
  • 如何让“元素跟随文本高度,而不是撑高父容器?

    如何让 元素跟随文本高度,而不是撑高父容器 在页面布局中,经常遇到父容器高度被子元素撑开的问题。在图例所示的案例中,父容器被较高的图片撑开,而文本的高度没有被考虑。本问答将提供纯css解决方案,让图片跟随文本高度,确保父容器的高度不会被图片影响。 解决方法 为了解决这个问题,需要将图片从文档流中脱离…

    2025年12月24日
    000
  • CSS元素设置em和transition后,为何载入页面无放大效果?

    css元素设置em和transition后,为何载入无放大效果 很多开发者在设置了em和transition后,却发现元素载入页面时无放大效果。本文将解答这一问题。 原问题:在视频演示中,将元素设置如下,载入页面会有放大效果。然而,在个人尝试中,并未出现该效果。这是由于macos和windows系统…

    2025年12月24日
    200
  • 为什么 CSS mask 属性未请求指定图片?

    解决 css mask 属性未请求图片的问题 在使用 css mask 属性时,指定了图片地址,但网络面板显示未请求获取该图片,这可能是由于浏览器兼容性问题造成的。 问题 如下代码所示: 立即学习“前端免费学习笔记(深入)”; icon [data-icon=”cloud”] { –icon-cl…

    2025年12月24日
    200
  • 如何利用 CSS 选中激活标签并影响相邻元素的样式?

    如何利用 css 选中激活标签并影响相邻元素? 为了实现激活标签影响相邻元素的样式需求,可以通过 :has 选择器来实现。以下是如何具体操作: 对于激活标签相邻后的元素,可以在 css 中使用以下代码进行设置: li:has(+li.active) { border-radius: 0 0 10px…

    2025年12月24日
    100
  • 如何模拟Windows 10 设置界面中的鼠标悬浮放大效果?

    win10设置界面的鼠标移动显示周边的样式(探照灯效果)的实现方式 在windows设置界面的鼠标悬浮效果中,光标周围会显示一个放大区域。在前端开发中,可以通过多种方式实现类似的效果。 使用css 使用css的transform和box-shadow属性。通过将transform: scale(1.…

    2025年12月24日
    200
  • 如何用HTML/JS实现Windows 10设置界面鼠标移动探照灯效果?

    Win10设置界面中的鼠标移动探照灯效果实现指南 想要在前端开发中实现类似于Windows 10设置界面的鼠标移动探照灯效果,有两种解决方案:CSS 和 HTML/JS 组合。 CSS 实现 不幸的是,仅使用CSS无法完全实现该效果。 立即学习“前端免费学习笔记(深入)”; HTML/JS 实现 要…

    2025年12月24日
    000
  • 为什么我的 Safari 自定义样式表在百度页面上失效了?

    为什么在 Safari 中自定义样式表未能正常工作? 在 Safari 的偏好设置中设置自定义样式表后,您对其进行测试却发现效果不同。在您自己的网页中,样式有效,而在百度页面中却失效。 造成这种情况的原因是,第一个访问的项目使用了文件协议,可以访问本地目录中的图片文件。而第二个访问的百度使用了 ht…

    2025年12月24日
    000
  • 如何用前端实现 Windows 10 设置界面的鼠标移动探照灯效果?

    如何在前端实现 Windows 10 设置界面中的鼠标移动探照灯效果 想要在前端开发中实现 Windows 10 设置界面中类似的鼠标移动探照灯效果,可以通过以下途径: CSS 解决方案 DEMO 1: Windows 10 网格悬停效果:https://codepen.io/tr4553r7/pe…

    2025年12月24日
    000
  • 如何用前端技术实现Windows 10 设置界面鼠标移动时的探照灯效果?

    探索在前端中实现 Windows 10 设置界面鼠标移动时的探照灯效果 在前端开发中,鼠标悬停在元素上时需要呈现类似于 Windows 10 设置界面所展示的探照灯效果,这其中涉及到了元素外围显示光圈效果的技术实现。 CSS 实现 虽然 CSS 无法直接实现探照灯效果,但可以通过以下技巧营造出类似效…

    2025年12月24日
    000
  • 使用CSS mask属性指定图片URL时,为什么浏览器无法加载图片?

    css mask属性未能加载图片的解决方法 使用css mask属性指定图片url时,如示例中所示: mask: url(“https://api.iconify.design/mdi:apple-icloud.svg”) center / contain no-repeat; 但是,在网络面板中却…

    2025年12月24日
    000
  • 如何用CSS Paint API为网页元素添加时尚的斑马线边框?

    为元素添加时尚的斑马线边框 在网页设计中,有时我们需要添加时尚的边框来提升元素的视觉效果。其中,斑马线边框是一种既醒目又别致的设计元素。 实现斜向斑马线边框 要实现斜向斑马线间隔圆环,我们可以使用css paint api。该api提供了强大的功能,可以让我们在元素上绘制复杂的图形。 立即学习“前端…

    2025年12月24日
    000
  • 图片如何不撑高父容器?

    如何让图片不撑高父容器? 当父容器包含不同高度的子元素时,父容器的高度通常会被最高元素撑开。如果你希望父容器的高度由文本内容撑开,避免图片对其产生影响,可以通过以下 css 解决方法: 绝对定位元素: .child-image { position: absolute; top: 0; left: …

    2025年12月24日
    000
  • CSS 帮助

    我正在尝试将文本附加到棕色框的左侧。我不能。我不知道代码有什么问题。请帮助我。 css .hero { position: relative; bottom: 80px; display: flex; justify-content: left; align-items: start; color:…

    2025年12月24日 好文分享
    200

发表回复

登录后才能评论
关注微信