UI 自动化和屏幕缩放

注释

本文档适用于想要使用 System.Windows.Automation 命名空间中定义的托管 UI 自动化类的 .NET Framework 开发人员。 有关 UI 自动化的最新信息,请参阅 Windows 自动化 API:UI 自动化

从 Windows Vista 开始,Windows 允许用户更改每英寸(dpi)设置的点数,以便屏幕上的大多数用户界面(UI)元素显示更大。 尽管此功能在 Windows 中一直可用,但在早期版本中,缩放必须由应用程序实现。 从 Windows Vista 开始,桌面窗口管理器对所有不处理自身缩放的应用程序执行默认缩放。 UI 自动化客户端应用程序必须考虑到此功能。

在 Windows Vista 中缩放

默认 dpi 设置为 96,这意味着 96 像素占用一个表示法英寸的宽度或高度。 “英寸”的确切度量取决于监视器的大小和物理分辨率。 例如,在 12 英寸宽的监视器上,水平分辨率为 1280 像素,96 像素的水平线延长约 9/10 英寸。

更改 dpi 设置与更改屏幕分辨率不同。 使用 dpi 缩放时,屏幕上的物理像素数保持不变。 但是,缩放应用于 UI 元素的大小和位置。 此缩放可由桌面窗口管理器(DWM)自动执行,适用于不显式要求不缩放的应用程序。

实际上,当用户将比例系数设置为 120 dpi 时,屏幕上的垂直或水平英寸将增大 25%。 所有维度都会相应地缩放。 应用窗口相对于屏幕左边缘和上边缘的偏移量增加了 25%。 如果启用了应用程序缩放且应用程序不支持 DPI,窗口的大小将按相同比例增加,并且其中所有 UI 元素的偏移量和大小也同样按比例增加。

注释

默认情况下,当用户将 dpi 设置为 120 时,DWM 不会对非支持 dpi 感知的应用程序执行缩放,但在 dpi 设置为 144 或更高时会执行缩放。 但是,用户可以替代默认行为。

屏幕缩放给任何涉及屏幕坐标的应用程序带来了新的挑战。 屏幕现在包含两个坐标系:物理和逻辑系统。 点的物理坐标是原点左上角的实际偏移量(以像素为单位)。 逻辑坐标是偏移量,就像像素缩放时的坐标变动一样。

假设你设计了一个对话框,其中包含一个位于坐标处的按钮(100,48)。 当此对话框以默认的 96 dpi 显示时,按钮位于物理坐标 (100, 48) 处。 在 120 dpi 处,它位于物理坐标 (125, 60) 处。 但逻辑坐标在任何 dpi 设置上都是相同的:(100,48)。

逻辑坐标很重要,因为它们使操作系统和应用程序的行为保持一致,无论 dpi 设置如何。 例如, Cursor.Position 通常返回逻辑坐标。 如果将光标移到对话框中的元素上,则无论 dpi 设置如何,都会返回相同的坐标。 在 (100, 100) 处绘制控件时,它将被绘制到这些逻辑上的坐标,并且在任何 DPI 设置下都会占据相同的相对位置。

在 UI 自动化客户端中缩放

UI 自动化 API 不使用逻辑坐标。 以下方法和属性返回物理坐标或将其作为参数。

默认情况下,非 96 dpi 环境中运行的 UI 自动化客户端应用程序将无法从这些方法和属性获取正确的结果。 例如,由于游标位置在逻辑坐标中,客户端不能简单地传递这些坐标来 FromPoint 获取游标下的元素。 此外,应用程序将无法将窗口正确放置在其工作区之外。

解决方案分为两个部分。

  1. 首先,使客户端应用程序成为 dpi 感知的应用程序。 为此,请在启动时调用 Win32 函数 SetProcessDPIAware 。 在托管代码中,以下声明使此函数可用。

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    internal static extern bool SetProcessDPIAware();
    
    <System.Runtime.InteropServices.DllImport("user32.dll")> _
    Friend Shared Function SetProcessDPIAware() As Boolean
    End Function
    

    此函数使整个进程支持 DPI 感知,这意味着进程中的所有窗口均未进行缩放。 例如,在 荧光器示例中,构成突出显示矩形的四个窗口位于从 UI 自动化获取的物理坐标,而不是逻辑坐标。 如果该示例不是 dpi 感知的,将在桌面上的逻辑坐标处绘制突出显示,这将导致在非 96 dpi 环境中错误放置。

  2. 若要获取游标坐标,请调用 Win32 函数 GetPhysicalCursorPos。 以下示例演示如何声明和使用此函数。

    public struct CursorPoint
    {
        public int X;
        public int Y;
    }
    
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    internal static extern bool GetPhysicalCursorPos(ref CursorPoint lpPoint);
    
    private bool ShowUsage()
    {
        CursorPoint cursorPos = new CursorPoint();
        try
        {
            return GetPhysicalCursorPos(ref cursorPos);
        }
        catch (EntryPointNotFoundException) // Not Windows Vista
        {
            return false;
        }
    }
    
    Structure CursorPoint
        Public X As Integer
        Public Y As Integer
    End Structure
    
    <System.Runtime.InteropServices.DllImport("user32.dll")> _
    Friend Shared Function GetPhysicalCursorPos(ByRef lpPoint As CursorPoint) As Boolean
    End Function
    
    Private Function ShowUsage() As Boolean
    
        Dim cursorPos As New CursorPoint()
        Try
            Return GetPhysicalCursorPos(cursorPos)
        Catch e As EntryPointNotFoundException ' Not Windows Vista
            Return False
        End Try
    
    End Function
    

谨慎

请勿使用 Cursor.Position。 未定义在缩放环境中客户端窗口外部此属性的行为。

如果应用程序与非 dpi 感知应用程序执行直接的跨进程通信,则可以使用 Win32 函数PhysicalToLogicalPointLogicalToPhysicalPoint在逻辑坐标和物理坐标之间进行转换。

另请参阅