DPI 缩放是一种由操作系统或应用程序执行的功能, 用于根据显示器的 "每英寸点数" 设置以增加内容的可视大小. 一般来说, 它允许内容在具有不同显示分辨率的系统上以相同的物理大小显示, 或者至少可以在非常高分辨率的显示器上使用. 有时, 用户可能会增加 DPI 设置, 只是为了使内容更大, 阅读更舒服.
A_ScreenDPI 通常返回脚本启动时主显示器的 DPI 设置. 这就是所谓的 "系统 DPI", 尽管在不同时间启动的进程可以有不同的值.
有两种类型的 DPI 缩放与 AutoHotkey 相关: Gui DPI 缩放和系统 DPI 缩放.
默认情况下, 自动缩放由 Gui 和 GuiControl 方法/属性执行, 因此具有硬编码位置, 大小和边距的 Gui 脚本将在高 DPI 屏幕上根据需要进行缩放. 如果这干扰了脚本, 或者脚本将自己进行缩放, 那么可以禁用自动缩放. 有关详情, 请参阅 -DPIScale 选项.
对于不支持 dpi 的应用程序, 操作系统会自动缩放传递给某些系统函数和从某些系统函数返回的坐标. 这种类型的缩放通常在两种情况下影响 AutoHotkey:
所完成的精确缩放取决于正在调用的系统函数, 脚本的 DPI 感知以及目标窗口的潜在 DPI 感知.
在 Windows 8.1 和更高版本中, 副屏幕可以有不同的 DPI 设置, "每监视器 DPI-感知" 应用程序可以根据它们当前所处屏幕的 DPI 来缩放它们的窗口, 当窗口在屏幕之间移动时动态调整.
对于不支持感知每个监视器 DPI 的应用程序, 系统执行位图缩放, 以允许窗口在屏幕之间移动时改变大小. 并通过报告缩放到应用程序预期的全局 DPI 设置的坐标和大小来向应用程序隐藏这一点. 例如, 在一个 11 英寸的 4K 屏幕上, 一个设计成 96 dpi(100 %) 显示的 GUI 几乎不可能使用,而将其放大 200 % 就可以使用了.
AutoHotkey v2.0 不是设计来执行每监视器缩放的, 因此没有被标记为每监视器 DPI 感知的. 这是一个好处, 例如, 当在一个 100 % DPI 的外部大屏幕和一个 200 % DPI 的小屏幕之间移动一个 GUI 窗口时. 然而, 自动缩放确实有负面影响.
为了使系统的自动缩放工作, 系统函数如 MoveWindow 和 GetWindowRect 会自动缩放它们接受或返回的坐标. 当 AutoHotkey 使用这些函数处理外部窗口时, 如果坐标不在主屏幕上, 这通常会产生意外的结果. 更加令人困惑的是, 一些函数根据脚本最后一个活动窗口显示在哪个屏幕上来缩放坐标.
在 Windows 10 1607 及更高版本中, 可以使用 SetThreadDpiAwarenessContext 系统函数在运行时更改程序的 DPI 感知设置. 例如, 启用每监视器 DPI 感知将禁用系统执行的缩放, 因此诸如 WinMove 和 WinGetPos 之类的命令将接受或返回像素坐标, 不受 DPI 缩放的影响. 然而, 如果一个 GUI 的大小适合于 100 % DPI 的屏幕, 然后移动到 200 % DPI 的屏幕, 它将不会自动调整, 并且可能会非常难以使用.
要启用每个监视器的 DPI 感知, 请在使用通常受 DPI 缩放影响的函数之前调用以下函数:
DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr")
在 Windows 10 1703 及更高版本中, 可以使用 -4 替换 -3 以启用 "Per Monitor v2" 模式. 样就可以缩放对话框, 菜单, 工具提示和其他一些东西. 然而, 它也会导致非客户端区域(标题栏) 的缩放, 这可能会导致窗口的客户端区域太小, 除非脚本被设计为对此进行调整(例如通过响应 WM_DPICHANGED 消息). 可以在创建 GUI 之前将上下文设置为 -3, 而在创建任何工具提示, 菜单或对话框之前将上下文设置为 -4 来避免这种情况.
当系统调用窗口的窗口过程时, 它会自动将当前DPI感知上下文设置为创建窗口时正在使用的上下文. 因此, 新脚本线程的上下文取决于它是直接从 AutoHotkey 的消息循环启动还是通过窗口过程启动.
当接收到 WM_DPICHANGED 消息时, 每个监视器的 DPI 感知 GUI 窗口会按预期自动调整. 默认情况下, AutoHotkey v2.0 GUI 窗口不会响应此消息. 如果正确实现这种类型的动态缩放太困难, 一个更简单的替代方法是在创建 GUI 之前立即暂时禁用每个监视器的 DPI 感知. 例如:
; Set the "system DPI aware" mode which is the default in AutoHotkey v2.0: try dac := DllCall("SetThreadDpiAwarenessContext", 'ptr', -2, 'ptr') ; Create the GUI, which will permanently be "system DPI aware": MyGui := Gui() ; Restore the previous mode for any subsequent function calls: IsSet(dac) && DllCall("SetThreadDpiAwarenessContext", 'ptr', dac, 'ptr')
如果操作系统不支持 SetThreadDpiAwarenessContext 或程序已经处于系统 DPI 感知模式, 则额外的行没有作用.
如果只有一些 GUI 控件不能很好地缩放, 系统 DPI 感知(或不支持 DPI 感知) 控件可以托管在每个监视器的 DPI 感知窗口上. 在创建窗口之前必须启用混合托管(需要 Windows 10 1803 版本或更高版本):
; Create a GUI window which can host less-aware child windows: try dhb := DllCall("SetThreadDpiHostingBehavior", 'int', 1) MyGui := Gui() IsSet(dhb) && DllCall("SetThreadDpiHostingBehavior", 'int', dhb) ; Add a "system DPI aware" control: try dac := DllCall("SetThreadDpiAwarenessContext", 'ptr', -2, 'ptr') MyListView := MyGui.AddListView() IsSet(dac) && DllCall("SetThreadDpiAwarenessContext", 'ptr', dac, 'ptr')
通过在编译脚本的清单(一个嵌入式 XML 资源) 中设置 "dpiAware" 和 "dpiAwareness" 元素, 可以在整个流程范围内启用每个监视器的 DPI 感知. 有关这些设置的正确使用和效果的详细信息, 请参阅使用应用程序清单设置默认感知. 例如, AutoHotkey v2.0.19 中的清单包括以下内容:
<v3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings" xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings"> <dpiAware>true</dpiAware> <ws2:longPathAware>true</ws2:longPathAware> </v3:windowsSettings>
正如 Microsoft 文档中所解释的, 可能需要同时包含 "dpiAware" 和 "dpiAwareness", 它们属于不同的 XML 名称空间. 因为 "longPathAware" 和 "dpiAwareness" 属于同一个名称空间, 所以可以通过移动一些东西来优化 XML. 以下启用每个监视器的 DPI 感知(如果可用, 则为 v2, 否则为 v1):
<v3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings"> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <dpiAwareness>PerMonitorV2</dpiAwareness> <longPathAware>true</longPathAware> </v3:windowsSettings>
程序的默认 DPI 感知可以通过兼容性设置来覆盖, 这可以在 AutoHotkey 可执行文件的属性中设置, 在快捷文件的属性中设置, 或者通过设置 __COMPAT_LAYER
环境变量来包含关键字 DpiUnaware
或关键字 HighDpiAware
. 使用这种方法启用 DPI 感知可能会产生不必要的影响; 特别是, MsgBox 窗口在屏幕之间移动时可能无法自动调整.