Модель автоматизации пользовательского интерфейса пользовательского элемента управления WPF

модель автоматизации пользовательского интерфейса предоставляет единый обобщенный интерфейс, который клиенты автоматизации могут использовать для изучения или эксплуатации пользовательских интерфейсов различных платформ и платформ. Автоматизация пользовательского интерфейса предоставляет как код для проверки качества (тестирования), так и приложения с поддержкой специальных возможностей, например средства чтения с экрана для проверки элементов пользовательского интерфейса и моделирования пользовательского взаимодействия с ними из другого кода. Сведения об автоматизации пользовательского интерфейса на всех платформах см. в разделе, посвященном специальным возможностям.

В этом разделе описывается реализация серверного поставщика автоматизации пользовательского интерфейса для пользовательского элемента управления, применяемого в приложении WPF. WPF поддерживает автоматизацию пользовательского интерфейса с помощью дерева одноранговых объектов автоматизации, которое сопутствует дереву элементов интерфейса пользователя. Тестовый код и приложения с поддержкой специальных возможностей могут использовать одноранговые объекты автоматизации напрямую (для внутрипроцессного кода) или через универсальный интерфейс, предоставляемый средствами автоматизации пользовательского интерфейса.

Одноранговые классы автоматизации

В элементах управления WPF поддерживается автоматизация пользовательского интерфейса посредством дерева одноранговых классов, которые являются производными от AutomationPeer. В соответствии с соглашением имена одноранговых классов начинаются с имени класса элемента управления, к которому добавляется строка "AutomationPeer". Например, ButtonAutomationPeer является одноранговым для класса элемента управления Button. Одноранговые классы примерно похожи на типы элементов управления автоматизацией пользовательского интерфейса, но относятся к элементам WPF. Код автоматизации, обращающийся к приложениям WPF через интерфейс автоматизации пользовательского интерфейса, не использует одноранговые классы автоматизации напрямую, но код автоматизации в том же пространстве процесса может использовать их напрямую.

Встроенные одноранговые классы автоматизации

Элементы реализуют одноранговый класс автоматизации, если они принимают взаимодействие пользователя с интерфейсом или содержат информацию, необходимую пользователям приложений для чтения с экрана. Не все визуальные элементы WPF имеют одноранговые классы автоматизации. Примерами классов, реализующих одноранговые элементы автоматизации, являются Button, TextBox и Label. Примерами классов, которые не реализуют одноранговые узлы автоматизации, являются классы, производные от Decorator, например Border, и классы, основанные на Panel, такие, как Grid и Canvas.

В базовом классе Control нет соответствующего однорангового класса. Если нужно использовать одноранговый класс, соответствующий пользовательскому элементу управления, производному от Control, необходимо создать пользовательский одноранговый класс на основе класса FrameworkElementAutomationPeer.

Вопросы безопасности для производных одноранговых классов

Одноранговые классы автоматизации должны работать в среде с частичным доверием. Код в сборке UIAutomationClient не настроен для выполнения в среде с частичным доверием и код однорангового класса автоматизации не должен ссылаться на эту сборку. Вместо этого следует использовать классы в сборке UIAutomationTypes. Например, нужно использовать класс AutomationElementIdentifiers из сборки UIAutomationTypes, который соответствует классу AutomationElement из сборки UIAutomationClient. В коде однорангового класса автоматизации можно безопасно ссылаться на сборку UIAutomationTypes.

Навигация по одноранговым объектам

После нахождения однорангового элемента автоматизации внутрипроцессный код может выполнять навигацию по дереву однорановых элементов путем вызова методов объекта GetChildren и GetParent. Навигация между элементами WPF в элементе управления поддерживается реализацией одноранговых элементов в методе GetChildrenCore. Система модели автоматизации пользовательского интерфейса вызывает этот метод для построения дерева подэлементов, содержащихся в элементе управления (например, элементов списка в элементе управления "список" (ListBox)). Метод UIElementAutomationPeer.GetChildrenCore, используемый по умолчанию, выполняет обход по визуальному дереву элементов для построения дерева одноранговых элементов автоматизации. Пользовательские элементы управления переопределяют этот метод для предоставления дочерних элементов клиентам автоматизации, возвращая одноранговые объекты автоматизации элементов, которые передают информацию или разрешают взаимодействие с пользователем.

Настройка производного однорангового класса

Во всех классах, производных от UIElement и ContentElement, содержится защищенный виртуальный метод OnCreateAutomationPeer. WPF выполняет вызов OnCreateAutomationPeer, чтобы получить одноранговый объект автоматизации для каждого элемента управления. Код автоматизации может использовать одноранговый объект для получения информации о характеристиках и функциях элемента управления и моделирования интерактивного использования. Пользовательский элемент управления, который поддерживает автоматизацию, должен переопределить OnCreateAutomationPeer и возвратить экземпляр класса, производного от AutomationPeer. Например, если пользовательский элемент управления является производным от класса ButtonBase, тогда объект, возвращаемый OnCreateAutomationPeer, должен быть производным от ButtonBaseAutomationPeer.

При реализации пользовательского элемента управления необходимо переопределить методы "Core" базового однорангового класса автоматизации, которые описывают специфическое поведение пользовательского элемента управления.

Переопределение метода OnCreateAutomationPeer

Необходимо переопределить метод OnCreateAutomationPeer для пользовательского элемента управления, чтобы он возвращал объект поставщика, который напрямую или опосредованно должен быть производным от AutomationPeer.

Переопределение метода GetPattern

Одноранговые классы автоматизации упрощают некоторые аспекты реализации поставщиков автоматизации пользовательского интерфейса на стороне сервера, но одноранговые классы автоматизации пользовательских элементов управления по-прежнему должны обрабатывать интерфейсы шаблонов. Также как и поставщики, не основанные на WPF, одноранговые элементы поддерживают шаблоны элементов управления путем предоставления реализаций интерфейсов в пространстве имен System.Windows.Automation.Provider, таких как IInvokeProvider. Интерфейсы шаблонов элементов управления могут быть реализованы самим одноранговым классом или другим объектом. Одноранговый элемент реализован в GetPattern так, что возвращается объект, который поддерживает определенный шаблон. Код автоматизации пользовательского интерфейса вызывает метод GetPattern и задает значение перечисления PatternInterface. Переопределение GetPattern должно возвращать объект, который реализует указанный шаблон. Если ваш элемент управления не имеет пользовательской реализации шаблона, вы можете вызвать реализацию метода GetPattern в базовом типе, получив либо его реализацию, либо значение NULL, если шаблон не поддерживается для данного типа элемента управления. Например, для пользовательского элемента управления NumericUpDown может быть установлено значение в некотором диапазоне, чтобы его одноранговый элемент автоматизации пользовательского интерфейса реализовал интерфейс 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", если ваша реализация элемента управления отличается от той, которая имеется в базовом одноранговом классе автоматизации. Как минимум элемент управления должен реализовать методы GetClassNameCore и GetAutomationControlTypeCore, как показано в следующем примере.

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 от поставщика потребуются дополнительные действия по реализации автоматизации пользовательского интерфейса, и продукты клиента по автоматизации пользовательского интерфейса не смогут предварительно получать информацию о структуре элемента управления, взаимодействии с клавиатурой и возможных шаблонах элементов управления.

Следует реализовать методы IsContentElementCore и IsControlElementCore, чтобы указать, содержит ли элемент управления данные или же выполняет интерактивную роль в пользовательском интерфейсе (либо то и другое одновременно). По умолчанию оба метода возвращают значение true. Эти параметры повышают удобство использования средств автоматизации (например, средств чтения с экрана), которые могут использовать эти методы для фильтрации дерева автоматизации. Если метод GetPattern передает обработку шаблона одноранговому объекту дочернего элемента, то метод IsControlElementCore однорангового объекта дочернего элемента может возвращать значение «false», чтобы исключить попадание однорангового объекта дочернего элемента в дерево автоматизации. Например, прокрутку в объекте ListBox обрабатывает ScrollViewer, и одноранговый объект автоматизации для PatternInterface.Scrollвозвращается методом GetPattern из ScrollViewerAutomationPeer, связанным с элементом ListBoxAutomationPeer. Таким образом, метод IsControlElementCore из ScrollViewerAutomationPeer возвращает значение false, поэтому ScrollViewerAutomationPeer не отображается в дереве автоматизации.

Одноранговый класс автоматизации должен предоставлять необходимые значения по умолчанию для вашего элемента управления. Обратите внимание, что код XAML, который ссылается на ваш элемент управления, может переопределить реализации базовых методов путем добавления атрибутов AutomationProperties. Например, следующий XAML-код создает кнопку, которая имеет два настраиваемых свойства модели автоматизации пользовательского интерфейса.

<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  

Пример реализации см. в исходном коде на C# или Visual Basic, где реализуется и применяется пользовательский элемент управления NumericUpDown.

Создание событий

Клиенты автоматизации могут подписаться на события автоматизации. Пользовательские элементы управления должны сообщать об изменениях состояния элемента управления путем вызова метода 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

См. также