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

相关推荐

  • c语言smgduan什么意思

    在 C 语言中,smgduan 宏用于定义全局变量,使之可以在其他编译单元中访问,例如 DLL 或共享库。它将展开为不同的定义,具体取决于编译器,例如 GCC 中的 “_GLOBAL_OFFSET_TABLE_” 和 MSVC 中的 “_declspec(dlle…

    2025年12月17日
    000
  • c语言rand什么意思

    rand 函数是一个 C 语言标准库函数,它生成伪随机数,范围在 0 到 RAND_MAX(最大可返回整数)之间。该函数通过种子值(一个整数)生成随机数,每次调用 rand 时,它使用之前的数字作为种子值生成新的伪随机数。使用 srand 函数可以设置种子值。 C 语言中的 rand 函数 问题:r…

    2025年12月17日
    000
  • c#随机数random怎么用

    C# 中的 System.Random 类用于生成伪随机数,可通过以下步骤使用:创建 Random 对象。使用不同的方法生成不同类型的随机数,如整数、小数和布尔值。可指定范围生成指定范围内的随机数。 如何使用 C# 中的 Random 类 C# 中的 System.Random 类是一个用于生成伪随…

    2025年12月17日
    000
  • c#怎么定义数组

    C# 数组是存储同类型元素的有序集合,使用连续内存块并通过索引值访问元素。它支持多维数组,元素按多个索引值组织。 C# 定义数组 在 C# 中,数组是用于存储相同数据类型的元素的有序集合。它使用连续的内存块来存储数据,可以使用一个索引值来访问每个元素。 语法 定义一个数组的语法如下: dataTyp…

    2025年12月17日
    000
  • c#怎么生成exe

    在 C# 中生成 EXE 可执⾏文件步骤:创建新的 C# 控制台应用程序。在 Program.cs 中编写代码。构建项目。EXE 文件将位于项目的 “binDebug” 子目录中。 如何在 C# 中生成 EXE 在 C# 中生成 EXE 可执行文件很简单,可以通过以下步骤实现…

    2025年12月17日
    000
  • C#怎么引用命名空间

    引用 C# 命名空间的步骤如下:使用 using 指令在代码顶部引用命名空间,以自动生成完全限定名称。如果不使用 using 指令,则必须使用命名空间的完全限定名称来访问其中的类型。可以使用 as 关键字指定别名,以避免冗长的完全限定名称。 如何引用 C# 命名空间 C# 中的命名空间用于将相关的类…

    2025年12月17日
    000
  • c语言怎么编译运行

    C语言编译运行:一、编译:使用GCC编译命令:gcc -o .c编译成功后生成可执行文件。二、运行:导航到可执行文件目录。输入可执行文件名并按回车运行。三、常见问题:编译时错误:检查语法或逻辑错误。编译后程序无法运行:检查链接或逻辑错误。 C 语言编译运行指南 一、编译 编译是将 C 语言源代码转换…

    2025年12月17日
    000
  • c语言怎么删除数组

    删除 C 语言数组:使用 free() 函数释放内存。使用 delete[] 运算符(仅适用于 C++)。设置数组元素为 NULL。对于动态分配的数组,设置数组长度为 0。 如何删除 C 语言数组 在 C 语言中,数组是一种连续存储的数据结构,其中元素按顺序排列。以下是如何删除 C 语言数组: 1.…

    2025年12月17日
    000
  • c语言如何解析xml

    解析 XML 的 C 语言方法:直接解析:使用手动编写的代码或第三方库(如 expat、libxml2)。利用库:使用预先编写的库,如 expat、libxml2 或 TinyXML。使用 DOM(文档对象模型):使用对象模型以树状结构访问和修改 XML 文档。使用 SAX(简单 API for X…

    2025年12月17日
    000
  • c语言编辑器哪个比较好

    最佳 C 语言编辑器:Visual Studio Code:功能强大,免费开源,界面友好。Sublime Text:商业编辑器,速度快,界面可定制。Atom:免费开源,可跨平台运行,可扩展性强。Eclipse:Java IDE,也支持 C 语言,功能丰富。CLion:专为 C 和 C++ 设计,功能…

    2025年12月17日
    000
  • c语言如何读取像素

    要读取像素,需要使用图像处理库,如 GDAL 或 OpenCV。使用 OpenCV,可以通过加载图像文件并遍历像素,获取像素的 RGB 值。使用 GDAL,需要打开图像数据集、获取图像大小和波段,再遍历像素获取像素值。 C语言读取像素 如何读取像素? 在 C 语言中,读取像素涉及使用图像处理库,例如…

    2025年12月17日
    000
  • c语言如何输出整个数组

    在 C 语言中,可通过以下方法输出整个数组:1. 使用 for 循环;2. 使用指针;3. 使用 range-based for 循环(C++11 及更高版本)。 如何用 C 语言输出整个数组 在 C 语言中,可以通过几种方法输出整个数组: 方法 1:使用 for 循环 int main() { i…

    2025年12月17日
    000
  • c语言编程软件推荐

    最佳整体选择:Visual Studio Code,一款跨平台编辑器,具有高级功能。初学者:Code::Blocks,界面友好、提供内置工具。专业开发:CLion,一款商用 IDE,专为 C/C++ 开发设计,提供高级功能。其他推荐:Dev-C++、Eclipse with CDT、JetBrain…

    2025年12月17日
    000
  • c语言用什么编程软件

    适合 C 语言编程的软件推荐:Code::Blocks:跨平台免费 IDE,提供语法高亮、调试和代码自动完成功能。Dev-C++:专为 C 和 C++ 编程设计的免费 IDE。Visual Studio:微软开发的商业 IDE,提供高级功能,如智能感知和重构。CLion:JetBrains 开发的商…

    2025年12月17日
    000
  • c语言如何实现开机自动启动

    在 C 语言中实现开机自动启动,主要步骤包括:创建 Windows 服务,包括编写代码和编译可执行文件;安装服务,以管理员身份执行命令 sc create 并指定服务名称和可执行文件路径;设置服务自动启动,在服务管理器中将其启动类型设置为“自动”;测试服务,验证其是否已成功启动。 如何在 C 语言中…

    2025年12月17日
    000
  • c#中this的用法

    this 关键字引用当前类的实例,用于:访问实例字段和方法。将对象作为参数传递给其他方法。从嵌套类中访问外部类的成员。作为扩展方法的参数。 c# 中 this 的用法 什么是 this? this 是一个关键字,它引用当前正在执行代码的类的实例。 this 的作用 访问类的实例字段和方法。将对象作为…

    2025年12月17日
    000
  • c#用什么软件编程

    常用的 C# 编程软件包括:Visual Studio:由 Microsoft 提供的全面 IDE,提供丰富的工具和功能。Visual Studio Code:Microsoft 的轻量级开源 IDE,提供核心功能和扩展支持。JetBrains Rider:专门的 C# IDE,提供高级代码分析和重…

    2025年12月17日
    000
  • c#和c++有什么区别

    C# 和 C++ 的主要区别在于:1. 语法:C# 语法简洁,C++ 语法复杂;2. 类型系统:C# 强类型,C++ 弱类型;3. 内存管理:C# 自动,C++ 手动;4. 应用:C# 适用于 Windows 和移动应用,C++ 适用于系统软件和游戏引擎;5. 运行时环境:C# 在 CLR 上运行,…

    2025年12月17日
    000
  • c#用什么软件

    C# 可使用的软件包括:集成开发环境(IDE):Visual Studio、JetBrains Rider、MonoDevelop、SharpDevelop文本编辑器:Visual Studio Code、Sublime Text、Atom其他工具:.NET SDK、MSBuild、NuGet C#…

    2025年12月17日
    000
  • c#如何获取当前时间

    C# 中获取当前时间的方法:DateTime.Now:返回当前日期和时间,格式为 yyyy-MM-dd HH:mm:ss。DateTime.UtcNow:返回当前协调世界时 (UTC),格式为 yyyy-MM-dd HH:mm:ss。System.currentTimeMillis:返回自纪元(19…

    2025年12月17日
    000

发表回复

登录后才能评论
关注微信