Mixed-Mode DPI 缩放和 DPI 感知 API
Sub-Process DPI 感知支持
SetThreadDpiAwarenessContext 支持在单个进程中使用不同的 DPI 缩放模式。 在Windows 10周年更新之前,窗口的 DPI 感知绑定到进程范围的 DPI 感知模式, (DPI 感知、系统 DPI 感知或Per-Monitor DPI 感知) 。 但现在,使用 SetThreadDpiAwarenessContext,顶级窗口可以具有不同于进程范围的 DPI 感知模式的 DPI 感知模式。 这也会影响子窗口,因为它们始终具有与其父窗口相同的 DPI 感知模式。
使用 SetThreadDpiAwarenessContext 可让开发人员决定在为桌面应用程序定义 DPI 特定行为时,他们希望将开发工作集中在何处。 例如,应用程序的主顶级窗口可以按监视器缩放,而辅助顶级窗口可以通过操作系统的位图缩放进行缩放。
DPI 感知上下文
在 SetThreadDpiAwarenessContext 可用之前,进程的 DPI 感知是在应用程序二进制文件的清单中定义的,或者在进程初始化期间通过调用 SetProcessDpiAwareness 来定义。 使用 SetThreadDpiAwarenessContext,每个线程可以具有单独的 DPI 感知上下文,该上下文可能与进程范围的 DPI 感知模式不同。 线程的 DPI 感知上下文以 DPI_AWARENESS_CONTEXT 类型表示,其行为方式如下:
- 线程可以随时更改其 DPI 感知上下文。
- 更改上下文后进行的任何 API 调用都将在相应的 DPI 上下文 (中运行,并可能) 虚拟化。
- 创建窗口时,其 DPI 感知定义为当时调用线程的 DPI 感知。
- 调用窗口的窗口过程时,线程会自动切换到创建窗口时使用的 DPI 感知上下文。
使用 SetThreadDpiAwarenessContext 的常见方案如下:从使用一个上下文 ((如 DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)运行的线程开始,) 暂时切换到其他上下文 (DPI_AWARENESS_CONTEXT_UNAWARE) 、创建窗口,然后立即将线程上下文切换回其以前的状态。 创建的窗口的 DPI 上下文为 DPI_AWARENESS_CONTEXT_UNAWARE,而调用线程上下文将还原到 DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ,并随后调用 SetThreadDpiAwarenessContext。 在此方案中,与调用线程关联的窗口将使用每个监视器上下文 (运行,因此操作系统) 不会对新创建的窗口进行位图拉伸,而新创建的窗口不会 (DPI 感知,因此会在设置为 >100% 缩放) 的显示器上自动进行位图拉伸。
图 1 演示了如何使用 DPI_AWARENESS_CONTEXT_PER_MONITOR 执行main进程线程,将其上下文切换到DPI_AWARENESS_CONTEXT_UNAWARE,并创建新窗口。 然后,每当向新创建的窗口发送消息或从其进行 API 调用时,就会使用 dpi 感知上下文 DPI_AWARENESS_CONTEXT_UNAWARE 执行。 创建新窗口后,main线程立即还原到其以前的DPI_AWARENESS_CONTEXT_PER_MONITOR上下文。
与 DPI 相关的新 API
除了在 SetThreadDpiAwarenessContext 提供的单个进程中支持不同的 DPI 感知模式外,还为桌面应用程序添加了以下特定于 DPI 的功能:
-
EnableNonClientDpiScaling
-
注意
Per Monitor V2 DPI 感知模式会自动启用此功能,因此,在使用它的应用程序中不需要调用 EnableNonClientDpiScaling。
从窗口WM_NCCREATE处理程序内调用 EnableNonClientDpiScaling 将导致顶级窗口的非工作区自动缩放 DPI。 如果顶级窗口是按监视器 DPI 感知 (是因为进程本身是按监视器 DPI 感知的,还是因为窗口是在每监视器 DPI 感知线程) 中创建的,则每当窗口 DPI 发生更改时,这些窗口的描述文字栏、滚动条、菜单和菜单栏都将按 DPI 缩放。
-
请注意,使用此 API 时,子窗口的非工作区(例如子编辑控件的非客户端滚动条)不会自动 DPI 缩放。
-
注意
EnableNonClientDpiScaling 必须从 WM_NCCREATE 处理程序调用。
-
-
*ForDpi API
几个常用的 API(如 GetSystemMetrics )没有任何 HWND 上下文,因此无法为其返回值提供正确的 DPI 感知。 从在不同 DPI 感知模式或上下文中运行的线程调用这些 API 可能会返回未针对调用线程的上下文缩放的值。 GetSystemMetricForDpi、 SystemParametersInfoForDpi 和 AdjustWindowRectExForDpi 将执行与其 DPI 不知道的对应项相同的功能,但采用 DPI 作为参数,并从当前线程的上下文推断 dpi 感知。
GetSystemMetricForDpi 和 SystemParametersInfoForDpi 将根据以下公式返回 DPI 缩放的系统指标值和系统参数值:
GetSystemMetrics (...) @ dpi == GetSystemMetricsForDpi (..., dpi)
因此,在具有特定系统 DPI 值的设备上运行时,调用 GetSystemMetrics (或 SystemParametersInfoForDpi) 将返回其 DPI 感知变体 (GetSystemMetricsForDpi 和 SystemParametersInfoForDpi) 的相同值,但与输入的 DPI 值相同。
AdjustWindowRectExForDpi 采用 HWND,将以 DPI 敏感的方式计算窗口矩形的所需大小。
-
GetDpiForWindow
-
GetDpiForWindow 将返回与提供的 HWND 关联的 DPI。 答案取决于 HWND 的 DPI 感知模式:
HWND 的 DPI 感知模式 返回值 无法感知 96 系统 系统 DPI Per-Monitor 关联的顶级窗口主要位于的显示器的 DPI
(如果提供了子窗口,则会返回相应顶级父窗口的 DPI)
-
GetDpiForWindow 将返回与提供的 HWND 关联的 DPI。 答案取决于 HWND 的 DPI 感知模式:
-
GetDpiForSystem
-
调用 GetDpiForSystem 比调用 GetDC 和 GetDeviceCaps 获取系统 DPI 更高效。
-
可在使用子进程 DPI 感知的应用程序中运行的任何组件不应假定系统 DPI 在进程的生命周期内是静态的。 例如,如果在 DPI_AWARENESS_CONTEXT_UNAWARE 感知上下文下运行的线程查询系统 DPI,则答案将为 96。 但是,如果同一线程切换到 DPI_AWARENESS_CONTEXT_SYSTEM 感知上下文并再次查询系统 DPI,则答案可能有所不同。 若要避免使用缓存的 (,并且) 系统 DPI 值可能过时,请使用 GetDpiForSystem 检索相对于调用线程的 DPI 感知模式的系统 DPI。
-