Compartilhar via


Automação da interface do usuário de um controle personalizado do WPF

A Automação de Interface do Usuário fornece uma interface única e generalizada que os clientes de automação podem usar para examinar ou operar as interfaces do usuário de uma variedade de plataformas e estruturas. A Automação de Interface do Usuário permite que o código de garantia de qualidade (teste) e aplicativos de acessibilidade, como leitores de tela, examinem os elementos da interface do usuário e simulem a interação do usuário com eles de outro código. Para obter informações sobre a Automação da Interface do Usuário em todas as plataformas, consulte Acessibilidade.

Este tópico descreve como implementar um provedor de Automação de Interface do Usuário do lado do servidor para um controle personalizado executado em um aplicativo WPF. O WPF oferece suporte à Automação de Interface do Usuário por meio de uma árvore de objetos de automação que espelha a árvore de elementos da interface do usuário. O código de teste e os aplicativos que fornecem recursos de acessibilidade podem usar objetos pares de automação diretamente (para código em processo) ou por meio da interface generalizada fornecida pela Automação da Interface do Usuário.

Classes de par de automação

Os controles do WPF dão suporte à Automação de Interface do Usuário por meio de uma árvore de classes pares que derivam de AutomationPeer. Por convenção, os nomes de classe par começam com o nome da classe de controle e terminam com "AutomationPeer". Por exemplo, ButtonAutomationPeer é a classe par da classe de controle Button. As classes par são aproximadamente equivalentes aos tipos de controle de Automação da Interface do Usuário, mas são específicas para elementos do WPF. O código de automação que acessa aplicativos WPF por meio da interface de Automação de UI não utiliza diretamente os pares de automação, mas o código de automação no mesmo espaço de processo pode utilizá-los diretamente.

Classes de Automação Peer Integradas

Os elementos implementam uma classe de par de automação se aceitam a atividade de interface do usuário ou se contêm informações necessárias para os usuários de aplicativos de leitor de tela. Nem todos os elementos visuais do WPF têm pares de automação. Exemplos de classes que implementam pares de automação são Button, TextBoxe Label. Exemplos de classes que não implementam pares de automação são classes que derivam de Decorator, como Border, e classes baseadas em Panel, como Grid e Canvas.

A classe Control base não tem uma classe par correspondente. Se você precisar de uma classe de par para corresponder a um controle personalizado que deriva de Control, você deverá derivar a classe par personalizada de FrameworkElementAutomationPeer.

Considerações de segurança para pares derivados

Os pares de automação devem ser executados em um ambiente de confiança parcial. O código no assembly UIAutomationClient não está configurado para ser executado em um ambiente de confiança parcial e o código par de automação não deve referenciar esse assembly. Em vez disso, você deve usar as classes no assembly UIAutomationTypes. Por exemplo, você deve usar a classe AutomationElementIdentifiers do assembly UIAutomationTypes, que corresponde à classe AutomationElement no assembly UIAutomationClient. É seguro referenciar o assembly UIAutomationTypes no código par de automação.

Navegação entre pares

Depois de localizar um peer de automação, o código em processo pode navegar pela árvore de automação chamando os métodos GetChildren e GetParent do objeto. A navegação entre elementos do WPF em um controle é compatível com a implementação do método GetChildrenCore pelo par. O sistema de Automação da Interface do Usuário chama esse método para criar uma árvore de subelementos contidos em um controle; por exemplo, listar itens em uma caixa de listagem. O método padrão UIElementAutomationPeer.GetChildrenCore percorre a árvore visual de elementos para construir a árvore de pares de automação. Os controles personalizados substituem esse método para expor elementos filhos a clientes de automação, retornando os pares de automação de elementos que transmitem informações ou permitem a interação do usuário.

Personalizações em um par derivado

Todas as classes derivadas de UIElement e ContentElement contêm o método virtual protegido OnCreateAutomationPeer. O WPF chama OnCreateAutomationPeer para obter o objeto par de automação para cada controle. O código de automação pode usar o par para obter informações sobre características e recursos de um controle e simular o uso interativo. Um controle personalizado que dá suporte à automação deve substituir OnCreateAutomationPeer e retornar uma instância de uma classe que deriva de AutomationPeer. Por exemplo, se um controle personalizado derivar da classe ButtonBase, o objeto retornado por OnCreateAutomationPeer deverá derivar de ButtonBaseAutomationPeer.

Ao implementar um controle personalizado, você deve substituir os métodos "Core" da classe par de automação base que descrevem o comportamento exclusivo e específico ao seu controle personalizado.

Sobrescrever OnCreateAutomationPeer

Substitua o método OnCreateAutomationPeer para seu controle personalizado para que ele retorne seu objeto de provedor, que deve derivar direta ou indiretamente de AutomationPeer.

Sobrescrever GetPattern

Os pares de automação simplificam alguns aspectos de implementação dos provedores de Automação de Interface do Usuário do lado do servidor, mas os pares de automação de controle personalizado ainda devem lidar com interfaces de padrão. Como provedores não WPF, os pares dão suporte a padrões de controle fornecendo implementações de interfaces no namespace System.Windows.Automation.Provider, como IInvokeProvider. As interfaces de padrão de controle podem ser implementadas pelo próprio par ou por outro objeto. A implementação de GetPattern pelo peer retorna o objeto que dá suporte ao padrão especificado. O código de Automação da Interface do Usuário chama o método GetPattern e especifica um valor de enumeração PatternInterface. A substituição de GetPattern deve retornar o objeto que implementa o padrão especificado. Caso seu controle não possua uma implementação personalizada de um padrão, você pode chamar a implementação do tipo base de GetPattern para recuperar a implementação do padrão ou nulo, caso o padrão não seja suportado para esse tipo de controle. Por exemplo, um controle NumericUpDown personalizado pode ser configurado para um valor dentro de um intervalo, de modo que seu par de Automação de UI implementaria a interface IRangeValueProvider. O exemplo a seguir mostra como o método GetPattern do par é substituído para responder a um valor 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

Um método GetPattern também pode especificar um subelemento como um provedor de padrões. O código a seguir mostra como ItemsControl transfere o tratamento do padrão de rolagem para o equivalente de seu controle interno 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  

Para especificar um subelemento para tratamento de padrões, esse código obtém o objeto de subelemento, cria um par usando o método CreatePeerForElement, define a propriedade EventsSource do novo par para o par atual e retorna o novo par. A configuração EventsSource em um subelemento impede que o subelemento apareça na árvore de par de automação e designa todos os eventos gerados pelo subelemento como originários do controle especificado em EventsSource. O controle ScrollViewer não aparece na árvore de automação e os eventos de rolagem gerados por ele parecem ter origem no objeto ItemsControl.

Substituir métodos "Core"

O código de automação obtém informações sobre seu controle chamando métodos públicos da classe correspondente. Para fornecer informações sobre seu controle, substitua cada método cujo nome termina com "Core" quando a implementação do controle for diferente daquela fornecida pela classe par de automação base. No mínimo, seu controle deve implementar os métodos GetClassNameCore e GetAutomationControlTypeCore, conforme mostrado no exemplo a seguir.

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

Sua implementação de GetAutomationControlTypeCore descreve seu controle retornando um valor ControlType. Embora você possa retornar ControlType.Custom, você deverá retornar um dos tipos de controle mais específicos se ele descrever com precisão o controle. Um valor retornado de ControlType.Custom requer um trabalho extra para o provedor implementar a Automação da Interface do Usuário e os produtos cliente de Automação da Interface do Usuário não conseguem prever a estrutura de controle, a interação do teclado e os possíveis padrões de controle.

Implemente os métodos IsContentElementCore e IsControlElementCore para indicar se o controle contém conteúdo de dados ou atende a uma função interativa na interface do usuário (ou ambos). Por padrão, ambos os métodos retornam true. Essas configurações melhoram a usabilidade de ferramentas de automação, como leitores de tela, que podem usar esses métodos para filtrar a árvore de automação. Se o método GetPattern transferir o tratamento de padrões para um colega subelemento, o método IsControlElementCore desse colega subelemento poderá retornar false para ocultá-lo da árvore de automação. Por exemplo, a rolagem em um ListBox é tratada por um ScrollViewer, e o par de automação para PatternInterface.Scroll é retornado pelo método GetPattern do ScrollViewerAutomationPeer associado ao ListBoxAutomationPeer. Portanto, o método IsControlElementCore do ScrollViewerAutomationPeer retorna false, garantindo que o ScrollViewerAutomationPeer não apareça na árvore de automação.

Seu par de automação deve fornecer valores padrão apropriados para o controle. Observe que o XAML que faz referência ao seu controle pode substituir suas implementações principais de métodos por meio dos atributos AutomationProperties. Por exemplo, o XAML a seguir cria um botão que tem duas propriedades personalizadas da Automação de Interface do Usuário.

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

Implementar provedores de padrões

As interfaces implementadas por um provedor personalizado são declaradas explicitamente se o elemento proprietário deriva diretamente de Control. Por exemplo, o código a seguir declara um par para um Control que implementa um valor de intervalo.

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

Se o controle proprietário derivar de um tipo específico de controle, como RangeBase, o par poderá ser derivado de uma classe de par derivada equivalente. Nesse caso, o par derivaria de RangeBaseAutomationPeer, que oferece uma implementação base para IRangeValueProvider. O código a seguir mostra a declaração desse tipo de par.

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

Para obter uma implementação de exemplo, consulte o código-fonte C# ou Visual Basic que implementa e consome um controle personalizado NumericUpDown.

Gerar eventos

Clientes de automação podem assinar eventos de automação. Os controles personalizados devem relatar alterações no estado de controle chamando o método RaiseAutomationEvent. Da mesma forma, quando um valor de propriedade é alterado, chame o método RaisePropertyChangedEvent. O código a seguir mostra como obter o objeto par de dentro do código de controle e chamar um método para gerar um evento. Como otimização, o código determina se há ouvintes para esse tipo de evento. Elevar o evento somente quando houver ouvintes evita sobrecarga desnecessária e ajuda o controle a permanecer responsivo.

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

Consulte também