为进程设置默认 DPI 感知
前言
最近写程序的时候需要获取屏幕分辨率创建窗口,却发现创建的窗口贼奇怪。
昨晚翻资料的时候无意间找到了解决方案,遂记录如下。
概要
使用较旧 Windows 编程技术的桌面应用程序(原始 Win32 编程、Windows 窗体、Windows Presentation Framework (WPF) 等)在没有其他开发人员工作的情况下,无法自动处理 DPI 缩放。如果没有此类工作,许多常见使用方案中,应用程序看起来模糊或大小不正确。
显示比例因子 & DPI
随着显示技术的发展,显示面板制造商已经将越来越多的像素打包到面板上的每个物理空间单元中。这导致现代显示面板的点数比历史上高得多。过去,大多数显示器每线性英寸物理空间有 96 像素,即 96 DPI(Dots Per Inch,每英寸点数);在 2017 年,具有近 300 DPI 或更高版本的显示器随时可用。
大多数旧式桌面 UI 框架都有内置的假设,即在过程的生存期内,显示 DPI 不会更改。此假设不再适用,显示 DPIs 通常会在整个应用程序进程的生存期内多次更改。显示比例因子(DPI)更改的一些常见方案如下:
- 多监视器设置,其中每个显示器具有不同的比例系数,应用程序将从一个显示器移动到另一个(例如 4K 和 1080p 显示器)
- 使用低 DPI 外部显示器对接和取消锁定高 DPI 笔记本电脑(反之亦然)
- 通过远程桌面从高 DPI 笔记本电脑/平板电脑连接到低 DPI 设备(反之亦然)
- 在应用程序运行时,使显示缩放因子设置发生更改
在这些方案中,UWP 应用程序会自动为新的 DPI 重新绘制。然而在默认情况、没有其他开发人员工作下,桌面应用程序不会自动为新的 DPI 重新绘制。没有执行额外工作以响应 DPI 更改的桌面应用程序可能会向用户显示模糊或大小错误。
在没有为应用程序设置 DPI 感知时,Windows 不知道应用程序呈现为固定 DPI 值 96 (100%) 。每当这些应用程序在显示比例大于 96 DPI 的屏幕上运行时,Windows 会将应用程序位图拉伸到预期的物理大小。这会导致应用程序显得模糊不清。
DPI 感知模式
下表显示了应用程序在不同方案中的呈现方式:
DPI 感知模式 | 引入的 Windows 版本 | 应用程序的 DPI 视图 | DPI 更改行为 |
---|---|---|---|
无法感知 | 不适用 | 所有显示器均为 96 DPI | 位图拉伸 (模糊) |
系统 | Vista | 在启动当前用户会话时,所有显示器的 DPI (主显示器的 DPI) | 位图拉伸 (模糊) |
Per-Monitor | 8.1 | 应用程序窗口主要位于的显示器的 DPI | 顶级 HWND 收到 DPI 更改通知 没有任何 UI 元素的 DPI 缩放。 |
Per-Monitor V2 | Windows 10 创意者更新 (1703) | 应用程序窗口主要位于的显示器的 DPI | 顶级 和 子 HWND 会收到 DPI 更改通知 自动 DPI 缩放: 1. 非工作区 2. 常见控件中的主题绘制位图 (comctl32 V6) 3. Dialogs (CreateDialog) |
解决方案
01 使用应用程序清单设置默认感知(推荐)
有两个清单设置可用于指定进程默认 DPI 感知模式: <dpiAwareness>
和 <dpiAware>
。
<dpiAware>
在 Windows Vista 中引入,仅允许将进程默认设置设置为系统感知。
<dpiAwareness>
是在 Windows 10 版本 1607 中引入的,可用于指定进程默认 DPI 感知模式的有序列表。这样,便可以设置备份 DPI 感知模式,使应用程序在不支持指定的第一个感知模式的 Windows 版本上运行。在较旧版本的 Windows 上,将忽略较新的 <dpiAwareness>
标记。这意味着,可以使用这两个清单设置来启用一种方案,在较旧版本的 Windows 默认为系统感知,在大于 Windows 10 版本 1607 使用 Per-Monitor 。在 Windows 10 版本 1607 以上,如果 <dpiAwareness>
元素存在,则忽略 <dpiAware>
设置。
Process default DPI awareness mode | <dpiAware> setting |
<dpiAwareness> setting (Windows 10, version 1607 and later) |
---|---|---|
unaware | N/A (no dpiAware setting in manifest) or <dpiAware>false</dpiAware> |
<dpiAwareness>unaware</dpiAwareness> |
System aware | <dpiAware>true</dpiAware> |
<dpiAwareness>system</dpiAwareness> |
Per Monitor | <dpiAware>true/pm<dpiAware> |
<dpiAwareness>PerMonitor</dpiAwareness> |
Per Monitor V2 | Not supported | <dpiAwareness>PerMonitorV2</dpiAwareness> |
下面的示例显示了在同一清单文件中使用 <dpiAware>
和 <dpiAware>
设置,以便为不同版本的 Windows 配置进程默认 DPI 感知行为。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>
02 以编程方式设置默认感知
尽管不建议这样做,但可以通过编程方式设置默认 DPI 感知。在进程中创建 HWND 窗口后,不再支持更改 DPI 感知模式。如果要以编程方式设置进程默认 DPI 感知模式,则必须在创建任何 HWND 之前调用相应的 API 。
API:
API | 最低版本的Windows | DPI Unaware | 系统 DPI 感知 | 每个监视器 DPI 感知 |
---|---|---|---|---|
SetProcessDPIAware |
Windows Vista | 不适用 | SetProcessDPIAware() |
不可用 |
SetProcessDpiAwareness |
Windows 8.1 | SetProcessDpiAwareness(PROCESS_DPI_UNAWARE) |
SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE) |
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE) |
SetProcessDpiAwarenessContext |
Windows 10 版本 1607 | SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE) |
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE) |
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE) SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) |
下面以 SetProcessDPIAware
为例介绍使用方法。
在 Visual Studio 等大部分软件都支持直接使用上述函数,但仍有部分软件内置的编译器不支持上述函数,因此,便需要手动引入 User32.dll
文件以提供支持。示例代码如下:
//为进程设置默认 DPI 感知
HINSTANCE hUser32 = LoadLibrary("User32.dll");
if (hUser32)
{
typedef BOOL (WINAPI* LPSetProcessDPIAware)(void);
LPSetProcessDPIAware SetProcessDPIAware = (LPSetProcessDPIAware)GetProcAddress(hUser32, "SetProcessDPIAware");
if (SetProcessDPIAware)
{
SetProcessDPIAware();
}
FreeLibrary(hUser32);
}
参考资料:
- 为进程设置默认 DPI 感知 https://learn.microsoft.com/en-us/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process
- Windows上的高 DPI 桌面应用程序开发 https://learn.microsoft.com/zh-cn/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
评论区