Partilhar via


Automação de UI de um controlo personalizado no WPF

A automação da interface do usuário fornece uma interface única e generalizada que os clientes de automação podem usar para examinar ou operar as interfaces de usuário de uma variedade de plataformas e estruturas. A automação da interface do usuário permite que o código de garantia de qualidade (teste) e os 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 a partir de outro código. Para obter informações sobre automação da interface do usuário em todas as plataformas, consulte Acessibilidade.

Este tópico descreve como implementar um provedor de automação da interface do usuário do lado do servidor para um controle personalizado que é executado em um aplicativo WPF. O WPF suporta a automação da interface do usuário por meio de uma árvore de objetos de automação de mesmo nível que é paralela à á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 pares de automação

Os controles WPF suportam a automação da interface do usuário por meio de uma árvore de classes de pares que derivam de AutomationPeer. Por convenção, os nomes de classe de mesmo nível começam com o nome da classe de controle e terminam com "AutomationPeer". Por exemplo, ButtonAutomationPeer é a classe equivalente para a classe de controle Button. As classes de mesmo nível são aproximadamente equivalentes aos tipos de controle de automação da interface do usuário, mas são específicas para elementos WPF. O código de automação que acede a aplicações WPF através da interface de Automação da IU não utiliza diretamente os pares de automação, mas o código de automação no mesmo espaço de processos pode utilizar diretamente os pares de automação.

Classes de pares de automação integradas

Os elementos implementam uma classe de parceiro de automação se aceitarem atividade de interface do utilizador, ou se contiverem informações necessárias para utilizadores de aplicações de leitores de ecrã. 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 de pares correspondente. Se você precisar de uma classe de mesmo nível para corresponder a um controle personalizado que deriva de Control, você deve derivar a classe de 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 de par de automação não deve fazer referência a 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 fazer referência ao assembly UIAutomationTypes no código de par de automação.

Navegação entre pares

Depois de localizar um colega de automação, o código no processo pode navegar na árvore do sistema chamando os métodos GetChildren e GetParent do objeto. A navegação entre elementos WPF dentro de um controle é suportada pela implementação do método GetChildrenCore pelo par. O sistema de automação da interface do usuário chama esse método para construir uma árvore de subelementos contidos em um controle; Por exemplo, liste itens em uma caixa de listagem. O método UIElementAutomationPeer.GetChildrenCore padrão atravessa a árvore visual de elementos para criar 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 parceiro derivado

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

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

Substituir 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 aspetos de implementação dos provedores de automação da 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 suportam 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 peer ou por outro objeto. A implementação do peer de GetPattern retorna o objeto que suporta o 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. Sua substituição de GetPattern deve retornar o objeto que implementa o padrão especificado. Se seu controle não tiver uma implementação personalizada de um padrão, você pode chamar a implementação do tipo base de GetPattern para recuperar sua implementação ou null se o padrão não for suportado para esse tipo de controle. Por exemplo, um controle NumericUpDown personalizado pode ser definido como um valor dentro de um intervalo, para que seu par de automação da interface do usuário implemente a interface IRangeValueProvider. O exemplo a seguir mostra como o método GetPattern do companheiro é substituído para se adaptar 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ão. O código a seguir mostra como ItemsControl transfere a manipulação de padrões de rolagem para o par de seu controle ScrollViewer interno.

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 manipulação de padrões, esse código obtém o objeto subelement, cria um peer usando o método CreatePeerForElement, define a propriedade EventsSource do novo peer para o peer atual e retorna o novo peer. A definição de EventsSource em um subelemento impede que o subelemento apareça na árvore de pares 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 que ele gera 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 par. Para fornecer informações sobre seu controle, substitua cada método cujo nome termina com "Core" quando sua implementação de controle for diferente daquela fornecida pela classe de 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ê deve retornar um dos tipos de controle mais específicos se ele descrever com precisão seu controle. Um valor de retorno de ControlType.Custom requer trabalho extra para o provedor implementar a Automação da Interface do Usuário, e os produtos cliente da Automação da Interface do Usuário não conseguem antecipar 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 seu controle contém conteúdo de dados ou cumpre 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 a manipulação de padrões para um par de subelementos, o método de IsControlElementCore do par de subelementos poderá retornar false para ocultar o par de subelementos da árvore de automação. Por exemplo, a deslocação num ListBox é gerida por um ScrollViewere a interface de automação para PatternInterface.Scroll é retornada pelo método GetPattern do ScrollViewerAutomationPeer associado ao ListBoxAutomationPeer. Portanto, o método IsControlElementCore do ScrollViewerAutomationPeer retorna false, para que o ScrollViewerAutomationPeer não surja na árvore de automação.

Seu par de automação deve fornecer valores padrão apropriados para seu controle. Observe que o XAML que referencia o seu controlo pode substituir as implementações principais dos métodos por pares, ao incluir atributos AutomationProperties. Por exemplo, o XAML a seguir cria um botão que tem duas propriedades personalizadas de Automação da 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 explicitamente declaradas 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 deriva de um tipo específico de controle, como RangeBase, o par pode ser derivado de uma classe de par derivada equivalente. Neste caso, o peer derivaria de RangeBaseAutomationPeer, que fornece uma implementação básica de IRangeValueProvider. O código a seguir mostra a declaração de tal par.

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

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

Aumentar Eventos

Os clientes de automação podem se inscrever em eventos de automação. Os controles personalizados devem relatar alterações no estado do 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 peer de dentro do código de controle e chamar um método para gerar um evento. Como uma otimização, o código determina se há ouvintes para esse tipo de evento. Aumentar o evento apenas quando há 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

Ver também