WPF 自定义控件的 UI 自动化

UI 自动化提供了一个通用接口,自动化客户端可使用该接口来检查或操作各种平台和框架的用户界面。 UI 自动化使质量保证(测试)代码和具有辅助功能的应用程序(如屏幕阅读器)能够检查用户界面元素,以及能够模拟与其他代码中的用户元素进行的用户交互。 有关跨所有平台的 UI 自动化的信息,请参阅“辅助功能”。

本主题介绍如何实现在 WPF 应用程序中运行的自定义控件的服务器端 UI 自动化提供程序。 WPF 通过等同于用户界面元素树的对等自动化对象树来支持 UI 自动化。 测试代码和提供辅助功能的应用程序可以直接使用自动化对等对象(适用于进程内代码),也可以通过 UI 自动化提供的通用接口使用自动化对等对象。

自动化对等类

WPF 控件通过派生自 AutomationPeer 的对等类树支持 UI 自动化。 按照约定,对等类名以控件类名开头,以“AutomationPeer”结尾。 例如,ButtonAutomationPeerButton 控制类的对等类。 对等类大致相当于 UI 自动化控件类型,但特定于 WPF 元素。 通过 UI 自动化接口访问 WPF 应用程序的自动化代码不直接使用自动化对等,但同一进程空间中的自动化代码可以直接使用自动化对等。

内置的自动化对等类

如果元素接受用户的接口活动,或者如果元素包含屏幕阅读器应用程序的用户所需的信息,则这些元素会实现一个自动化对等类。 并非所有 WPF 视觉元素都具有自动化对等。 实现自动化对等的类的示例包括 ButtonTextBoxLabel。 不实现自动化对等的类的示例是从 Decorator 派生的类(例如 Border)以及基于 Panel 的类(例如 GridCanvas)。

基本 Control 类没有相应的对等类。 如果需要一个对等类来对应从 Control 派生的自定义控件,应从 FrameworkElementAutomationPeer 派生自定义对等类。

派生对等的安全注意事项

自动化对等必须在部分信任环境中运行。 UIAutomationClient 程序集中的代码未配置为在部分信任环境中运行,自动化对等代码不应引用该程序集。 应改用 UIAutomationTypes 程序集中的类。 例如,应使用 UIAutomationTypes 程序集中的 AutomationElementIdentifiers 类,该类对应于 UIAutomationClient 程序集中的 AutomationElement 类。 可以安全地在自动化对等代码中引用 UIAutomationTypes 程序集。

对等导航

定位自动化对等点后,进程内代码可通过调用对象的 GetChildrenGetParent 方法导航对等点树。 可通过在对等中实现 GetChildrenCore 方法来支持在控件中的各个 WPF 元素之间进行导航。 UI 自动化系统调用此方法生成控件内包含的子元素树(例如,列表框中的列表项)。 默认的 UIElementAutomationPeer.GetChildrenCore 方法遍历元素的可视化树以构建自动化对等树。 自定义控件重写此方法以向自动化客户端公开子元素,这会返回传达信息或允许用户交互的元素自动化对等。

派生对等中的自定义项

UIElementContentElement 派生的所有类都包含受保护的虚拟方法 OnCreateAutomationPeer。 WPF 调用 OnCreateAutomationPeer 以获取每个控件的自动化对等对象。 自动化代码可使用对等来获取有关控件特征和功能的信息,并模拟交互使用。 支持自动化的自定义控件必须替代 OnCreateAutomationPeer 并返回从AutomationPeer 派生的类的实例。 例如,如果自定义控件派生自 ButtonBase 类,则 OnCreateAutomationPeer 返回的对象应派生自 ButtonBaseAutomationPeer

实现自定义控件时,必须重写自动化对等基类中的“Core”方法,这些方法描述了特定于自定义控件的唯一行为。

重写 OnCreateAutomationPeer

替代自定义控件的 OnCreateAutomationPeer 方法,使其返回提供程序对象,该对象必须直接或间接从 AutomationPeer 派生。

重写 GetPattern

自动化对等简化了服务器端 UI 自动化提供程序的一部分实现,但自定义控件自动化对等仍必须处理模式接口。 与非 WPF 提供程序一样,对等点通过在 System.Windows.Automation.Provider 命名空间(例如 IInvokeProvider)中提供接口实现来支持控制模式。 控件模式接口可以由对等本身或其他对象实现。 对等的 GetPattern 实现返回支持指定模式的对象。 UI 自动化代码调用 GetPattern 方法并指定 PatternInterface 枚举值。 重写 GetPattern 后,应返回实现指定模式的对象。 如果控件没有模式的自定义实现,可以调用基类型的 GetPattern 实现来检索其实现或返回 null(如果此控件类型不支持该模式)。 例如,NumericUpDown 自定义控件可设置为某个范围内的值,以便该控件的 UI 自动化对等实现 IRangeValueProvider 接口。 下面的示例演示了如何替代对等的 GetPattern 方法以响应 PatternInterface.RangeValue 值。

public override object GetPattern(PatternInterface patternInterface)
{
    if (patternInterface == PatternInterface.RangeValue)
    {
        return this;
    }
    return base.GetPattern(patternInterface);
}
Public Overrides Function GetPattern(ByVal patternInterface As PatternInterface) As Object
    If patternInterface = PatternInterface.RangeValue Then
        Return Me
    End If
    Return MyBase.GetPattern(patternInterface)
End Function

GetPattern 方法还可以将子元素指定为模式提供程序。 下面的代码显示 ItemsControl 如何将滚动模式处理转移到其内部 ScrollViewer 控件的对等。

public override object GetPattern(PatternInterface patternInterface)  
{  
    if (patternInterface == PatternInterface.Scroll)  
    {  
        ItemsControl owner = (ItemsControl) base.Owner;  
  
        // ScrollHost is internal to the ItemsControl class  
        if (owner.ScrollHost != null)  
        {  
            AutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(owner.ScrollHost);  
            if ((peer != null) && (peer is IScrollProvider))  
            {  
                peer.EventsSource = this;  
                return (IScrollProvider) peer;  
            }  
        }  
    }  
    return base.GetPattern(patternInterface);  
}  
Public Class Class1  
    Public Overrides Function GetPattern(ByVal patternInterface__1 As PatternInterface) As Object  
        If patternInterface1 = PatternInterface.Scroll Then  
            Dim owner As ItemsControl = DirectCast(MyBase.Owner, ItemsControl)  
  
            ' ScrollHost is internal to the ItemsControl class  
            If owner.ScrollHost IsNot Nothing Then  
                Dim peer As AutomationPeer = UIElementAutomationPeer.CreatePeerForElement(owner.ScrollHost)  
                If (peer IsNot Nothing) AndAlso (TypeOf peer Is IScrollProvider) Then  
                    peer.EventsSource = Me  
                    Return DirectCast(peer, IScrollProvider)  
                End If  
            End If  
        End If  
        Return MyBase.GetPattern(patternInterface1)  
    End Function  
End Class  

为指定用于模式处理的子元素,此代码会获取子元素对象,使用 CreatePeerForElement 方法创建对等点,将新对等点的 EventsSource 属性设置为当前对等点,并返回新对等点。 对子元素设置 EventsSource 可防止子元素出现在自动化对等树中,并将子元素引发的所有事件指定为源自 EventsSource 中指定的控件。 ScrollViewer 控件不会出现在自动化树中,并且它生成的滚动事件似乎源自 ItemsControl 对象。

重写“Core”方法

自动化代码通过调用对等类的公共方法来获取控件的相关信息。 在控件实现不同于自动化对等基类提供的实现的情况下,若要提供控件的相关信息,请重写名称以“Core”结尾的每个方法。 控件至少必须实现 GetClassNameCoreGetAutomationControlTypeCore 方法,如以下示例所示。

protected override string GetClassNameCore()
{
    return "NumericUpDown";
}

protected override AutomationControlType GetAutomationControlTypeCore()
{
    return AutomationControlType.Spinner;
}
Protected Overrides Function GetClassNameCore() As String
    Return "NumericUpDown"
End Function

Protected Overrides Function GetAutomationControlTypeCore() As AutomationControlType
    Return AutomationControlType.Spinner
End Function

GetAutomationControlTypeCore 的实现通过返回 ControlType 值来描述你的控件。 尽管你可以返回 ControlType.Custom,但如果它准确地描述了你的控件,你应返回更具体的控件类型之一。 ControlType.Custom 的返回值要求提供程序额外实现 UI 自动化,UI 自动化客户端产品预见不到控件结构、键盘交互和可能的控件模式。

实现 IsContentElementCoreIsControlElementCore 方法以指示你的控件是否包含数据内容或者/而且是否满足用户界面中的交互角色。 默认情况下,这两个方法返回 true。 这些设置提高了屏幕阅读器等自动化工具的可用性,此类工具可使用这些方法筛选自动化树。 如果 GetPattern 方法将模式处理转移到子元素对等点,则子元素对等点的 IsControlElementCore 方法可以返回 false 以从自动化树中隐藏子元素对等点。 例如,ListBox 中的滚动由 ScrollViewer 处理,PatternInterface.Scroll 的自动化对等点由与 ListBoxAutomationPeer 关联的 ScrollViewerAutomationPeerGetPattern 方法返回。因此,ScrollViewerAutomationPeerIsControlElementCore 方法会返回 false,使 ScrollViewerAutomationPeer 不出现在自动化树中。

自动化对等应为控件提供合适的默认值。 请注意,引用你的控件的 XAML 可以通过包括 AutomationProperties 属性来替代 Core 方法的对等实现。 例如,下面的 XAML 创建一个按钮,该按钮具有两个自定义的 UI 自动化属性。

<Button AutomationProperties.Name="Special"
    AutomationProperties.HelpText="This is a special button."/>  

实现模式提供程序

如果所属的元素直接派生自 Control,会显式声明自定义提供程序实现的接口。 例如,以下代码为实现一个范围值的 Control 声明了一个对等。

public class RangePeer1 : FrameworkElementAutomationPeer, IRangeValueProvider { }  
Public Class RangePeer1  
    Inherits FrameworkElementAutomationPeer  
    Implements IRangeValueProvider  
End Class  

如果所属的控件派生自特定类型的控件(如 RangeBase),则对等可派生自等效的派生对等类。 在此示例中,对等将从 RangeBaseAutomationPeer 派生,它提供了 IRangeValueProvider 的基本实现。 以下代码演示了此类对等的声明。

public class RangePeer2 : RangeBaseAutomationPeer { }  
Public Class RangePeer2  
    Inherits RangeBaseAutomationPeer  
End Class  

有关示例实现,请参阅实现和使用 NumericUpDown 自定义控件的 C#Visual Basic 源代码。

引发事件

自动化客户端可订阅自动化事件。 自定义控件必须通过调用 RaiseAutomationEvent 方法来报告控件状态的更改。 同样,更改属性值时,调用 RaisePropertyChangedEvent 方法。 以下代码演示了如何在控件代码内获取对等对象,并调用一个方法来引发事件。 作为一种优化,该代码会确定是否有适用于此事件类型的任何侦听器。 仅当有侦听器时才引发事件,可避免不必要的开销,有助于控件保持响应状态。

if (AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged))
{
    NumericUpDownAutomationPeer peer =
        UIElementAutomationPeer.FromElement(nudCtrl) as NumericUpDownAutomationPeer;

    if (peer != null)
    {
        peer.RaisePropertyChangedEvent(
            RangeValuePatternIdentifiers.ValueProperty,
            (double)oldValue,
            (double)newValue);
    }
}
If AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged) Then
    Dim peer As NumericUpDownAutomationPeer = TryCast(UIElementAutomationPeer.FromElement(nudCtrl), NumericUpDownAutomationPeer)

    If peer IsNot Nothing Then
        peer.RaisePropertyChangedEvent(RangeValuePatternIdentifiers.ValueProperty, CDbl(oldValue), CDbl(newValue))
    End If
End If

另请参阅