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 感知操作的示意图

除了在 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 可能会返回未针对调用线程的上下文缩放的值。 GetSystemMetricForDpiSystemParametersInfoForDpiAdjustWindowRectExForDpi 将执行与其 DPI 不知道的对应项相同的功能,但采用 DPI 作为参数,并从当前线程的上下文推断 dpi 感知。

  • GetSystemMetricForDpiSystemParametersInfoForDpi 将根据以下公式返回 DPI 缩放的系统指标值和系统参数值:

    GetSystemMetrics (...) @ dpi == GetSystemMetricsForDpi (..., dpi)

    因此,在具有特定系统 DPI 值的设备上运行时,调用 GetSystemMetrics (或 SystemParametersInfoForDpi) 将返回其 DPI 感知变体 (GetSystemMetricsForDpiSystemParametersInfoForDpi) 的相同值,但与输入的 DPI 值相同。

  • AdjustWindowRectExForDpi 采用 HWND,将以 DPI 敏感的方式计算窗口矩形的所需大小。

GetDpiForWindow
GetDpiForWindow 将返回与提供的 HWND 关联的 DPI。 答案取决于 HWND 的 DPI 感知模式:
HWND 的 DPI 感知模式 返回值
无法感知 96
系统 系统 DPI
Per-Monitor 关联的顶级窗口主要位于的显示器的 DPI
(如果提供了子窗口,则会返回相应顶级父窗口的 DPI)
GetDpiForSystem

调用 GetDpiForSystem 比调用 GetDCGetDeviceCaps 获取系统 DPI 更高效。

可在使用子进程 DPI 感知的应用程序中运行的任何组件不应假定系统 DPI 在进程的生命周期内是静态的。 例如,如果在 DPI_AWARENESS_CONTEXT_UNAWARE 感知上下文下运行的线程查询系统 DPI,则答案将为 96。 但是,如果同一线程切换到 DPI_AWARENESS_CONTEXT_SYSTEM 感知上下文并再次查询系统 DPI,则答案可能有所不同。 若要避免使用缓存的 (,并且) 系统 DPI 值可能过时,请使用 GetDpiForSystem 检索相对于调用线程的 DPI 感知模式的系统 DPI。