Pares de automação personalizados

Descreve o conceito de pares de automação para Automação da Interface do Usuário Microsoft e como você pode dar suporte à automação para sua própria classe de interface do usuário personalizada.

A Automação da Interface do Usuário fornece uma estrutura que os clientes de automação podem usar para examinar ou operar as interfaces do usuário de diversas plataformas e estruturas de interface do usuário. Se você estiver escrevendo um aplicativo do Windows, as classes que você usa para sua interface do usuário já fornecerão suporte à Automação da Interface do Usuário. Você pode derivar de classes existentes não lacradas para definir um novo tipo de classe de controle ou suporte da interface do usuário. Nesse processo, sua classe pode adicionar o comportamento que deve ter suporte à acessibilidade, mas que o suporte à Automação da Interface do Usuário padrão não abrange. Nesse caso, você deve estender o suporte existente à Automação da Interface do Usuário derivando da classe AutomationPeer usada pela implementação base, adicionando qualquer suporte necessário à implementação de par e informando à infraestrutura de controle de aplicativo do Windows que ela deve criar seu novo par.

A Automação da Interface do Usuário habilita aplicativos de acessibilidade e tecnologias assistenciais, como leitores de tela, além de código de controle de qualidade (teste). Em qualquer cenário, os clientes de Automação da Interface do Usuário podem examinar elementos da interface do usuário e simular a interação do usuário com seu aplicativo a partir de outro código externo ao seu aplicativo. Para saber mais sobre a Automação da Interface do Usuário em todas as plataformas e seu significado mais amplo, consulte Visão geral da Automação da Interface do Usuário.

Existem dois públicos diferentes que usam a estrutura de Automação da IU.

  • Os clientes de Automação da Interface do Usuário chamam APIs de Automação da Interface do Usuário para conhecer tudo sobre a interface do usuário atualmente mostrada para o usuário. Por exemplo, uma tecnologia assistencial, como um leitor de tela, age como um cliente de Automação da Interface do Usuário. A interface do usuário é apresentada como uma árvore de elementos de automação relacionados. O cliente de Automação da Interface do Usuário pode estar interessado em apenas um aplicativo por vez ou na árvore inteira. O cliente de Automação da Interface do Usuário pode usar APIs de Automação da Interface do Usuário para navegar na árvore e ler ou mudar as informações nos elementos de automação.
  • Os provedores de Automação da Interface do Usuário contribuem com informações para a árvore de Automação da Interface do Usuário, implementando APIs que expõem os elementos na interface do usuário que eles introduziram como parte de seu aplicativo. Quando cria um novo controle, você deve agir como participante do cenário de provedor de Automação da Interface do Usuário. Como provedor, você deve garantir que todos os clientes de Automação da Interface do Usuário possam usar a estrutura de Automação da IU de modo a interagir com o controle para fins de acessibilidade e testes.

Normalmente, existem APIs paralelas na estrutura de Automação da IU: uma API para clientes de Automação da Interface do Usuário e outra API com um nome parecido para os provedores de Automação da Interface do Usuário. Para a maior parte, este tópico aborda as APIs para o provedor da Automação da Interface do Usuário, e, especificamente, as classes e interfaces que permitem a extensibilidade de provedor nessa estrutura da IU. Ocasionalmente, nós mencionamos APIs de Automação da Interface do Usuário usadas por clientes de Automação da Interface do Usuário para dar uma perspectiva ou fornecemos uma tabela de pesquisa que relaciona as APIs de cliente e provedor. Para saber mais sobre a perspectiva do cliente, consulte Guia do programador do cliente de Automação da Interface do Usuário.

Observação

Os clientes da Automação da Interface do Usuário geralmente não usam código gerenciado e normalmente não são implementados como um aplicativo UWP (eles geralmente são aplicativos de área de trabalho). A Automação da Interface do Usuário tem como base um padrão, e não uma implementação ou estrutura específica. Muitos clientes de Automação da Interface do Usuário, incluindo produtos de tecnologia adaptativa (como leitores de tela), usam interfaces COM (Component Object Model) para interagir com a Automação da Interface do Usuário, o sistema e os aplicativos executados em janelas filho. Para saber mais sobre interfaces COM e sobre como escrever um cliente de Automação da Interface do Usuário usando COM, consulte Conceitos básicos da Automação da Interface do Usuário.

Determinando o estado existente de suporte à Automação de Interface do Usuário para sua classe de interface do usuário personalizada

Antes de tentar implementar um par de automação para um controle personalizado, você deve testar se a classe base e seu par de automação já fornecem o suporte à acessibilidade ou automação de que você precisa. Em muitos casos, a combinação das implementações de FrameworkElementAutomationPeer, pares específicos e os padrões que eles implementam pode fornecer uma experiência de acessibilidade básica, mas satisfatória. Isso será verdade dependendo de quantas alterações foram feitas na exposição do modelo de objeto para seu controle em relação à sua classe base. Além disso, depende da correlação entre suas adições à funcionalidade da classe base e os novos elementos da interface do usuário no contrato do modelo ou na aparência visual do controle. Em alguns casos, suas mudanças podem introduzir novos aspectos da experiência do usuário que requerem suporte adicional à acessibilidade.

Mesmo que o uso da classe de par base existente forneça o suporte básico à acessibilidade, é melhor definir um par para que você possa relatar informações precisas de ClassName para a Automação da Interface do Usuário em cenários de teste automatizados. Essa consideração é especialmente importante quando você escreve um controle destinado ao consumo de terceiros.

Classes de pares de automação

O UWP se baseia nas técnicas e convenções de Automação da Interface do Usuário existentes usadas pelas estruturas de IU anteriores de código gerenciado, como Windows Forms, Windows Presentation Foundation (WPF) e Microsoft Silverlight. Muitas das classes de controle e suas funções e finalidades também têm origem em uma estrutura da IU anterior.

Por convenção, os nomes de classe pares começam com o nome de classe do controle e terminam com "AutomationPeer". Por exemplo, ButtonAutomationPeer é a classe de par da classe de controle Button.

Observação

Neste tópico, nós consideramos as propriedades relacionadas à acessibilidade como mais importantes quando você implementa um par de controles. Porém, para um conceito mais geral do suporte à Automação da Interface do Usuário, você deve implementar um par de acordo com as recomendações documentadas pelo Guia do programador do provedor de Automação da Interface do Usuário e Conceitos básicos de Automação da Interface do Usuário. Esses tópicos não incluem as APIs de AutomationPeer específicas que você usaria para fornecer as informações da estrutura de UWP para a Automação da Interface do Usuário, mas descrevem as propriedades que identificam sua classe ou fornecem outras informações ou interações.

Pares, padrões e tipos de controle

Um padrão de controle é uma implementação de interface que expõe um aspecto específico de uma funcionalidade do controle para um cliente de Automação da Interface do Usuário. Os clientes de Automação da Interface do Usuário usam as propriedades e os métodos expostos por meio de um padrão de controle para recuperar informações sobre funcionalidades do controle ou para tratar o comportamento do controle em tempo de execução.

Os padrões de controle permitem categorizar e expor a funcionalidade de um controle independente do tipo ou da aparência do controle. Por exemplo, um controle que apresenta uma interface tabular usa o padrão de controle Grid para expor o número de linhas e colunas na tabela e para permitir que um cliente de Automação da Interface do Usuário recupere os itens da tabela. Como outros exemplos, o cliente de Automação da Interface do Usuário pode usar o padrão de controle Invoke para controles que podem ser invocados, como botões, e o padrão de controle Scroll para controles que têm barras de rolagem, como caixas de listagem, modos de exibição em lista ou caixas de combinação. Cada padrão de controle representa uma tipo de funcionalidade separado, e os padrões de controle podem ser combinados para descrever todo o conjunto de funcionalidades com suporte por um controle específico.

Os padrões de controle se relacionam à interface do usuário da mesma maneira que as interfaces se relacionam com objetos COM. No COM, você pode consultar um objeto para saber a quais interfaces ele dá suporte e usar essas interfaces para acessar a funcionalidade. Na Automação da Interface do Usuário, os clientes de Automação da Interface do Usuário podem perguntar a um elemento de Automação da IU os padrões de controle aos quais ele dá suporte e então interagir com o elemento e seu controle par por meio das propriedades, métodos, eventos e estruturas expostos pelos padrões de controle com suporte.

Uma das principais finalidades de um par de automação é relatar para um cliente de Automação da Interface do Usuário a quais padrões de controle o elemento de interface do usuário pode dar suporte por meio de seu par. Para isso, os provedores de Automação da Interface do Usuário implementam novos pares que alteram o comportamento do método GetPattern substituindo o método GetPatternCore. Os clientes de Automação da Interface do Usuário fazem chamadas que o provedor de Automação da Interface do Usuário mapeia para chamar GetPattern. Os clientes de Automação da Interface do Usuário consultam cada padrão específico com o qual desejam interagir. Se o par dá suporte ao padrão, ele retorna uma referência do objeto a si próprio. Caso contrário, ele retorna null. Se null não for retornado, o cliente de Automação da Interface do Usuário vai esperar que ele chame as APIs da interface padrão como um cliente para interagir com esse padrão de controle.

Um tipo de controle é uma forma de definir amplamente a funcionalidade de um controle representado pelo par. Esse conceito é diferente do padrão de controle, pois o padrão informa à Automação da Interface do Usuário quais informações ela pode obter ou quais ações ela pode executar por meio de determinada interface; o tipo de controle fica um nível acima disso. Cada tipo de controle possui diretrizes sobre esses aspectos da Automação da Interface do Usuário:

  • Padrões de controle da Automação da Interface do Usuário: um tipo de controle permite mais de um padrão, cada um representando uma classificação diferente de informações ou interação. Cada tipo de controle possui um conjunto de padrões que o controle tem que permitir, um conjunto opcional e um conjunto que o controle não deve permitir.
  • Valores de propriedades da Automação da Interface do Usuário: cada tipo de controle possui um conjunto de propriedades que o controle tem que permitir. Estas são as propriedades gerais, como descrito na Visão geral das propriedades da Automação da Interface do Usuário, e não são específicas ao padrão.
  • Eventos de Automação da Interface do Usuário: cada tipo de controle possui um conjunto de eventos que o controle tem que permitir. Eles também são gerais, e não específicos ao padrão, conforme descrito na Visão geral dos eventos de Automação da Interface do Usuário.
  • Estrutura da árvore de automação da IU: cada tipo de controle define como o controle deve aparecer na estrutura da árvore de automação da IU.

Independentemente de como os pares de automação da estrutura estão implementados, a funcionalidade de cliente de Automação da Interface do Usuário não está vinculada a UWP, e, na realidade, é provável que os clientes de Automação da Interface do Usuário existentes, como as tecnologias adaptativas, usem outros modelos de programação, como COM. No COM, os clientes podem usar QueryInterface para a interface de padrão de controle do COM que implementa o padrão solicitado ou para a estrutura geral de Automação da IU para examinar as propriedades, os eventos ou a árvore. Para os padrões, a estrutura de Automação da IU realiza marshaling do código da interface no código do UWP que é executado no provedor da Automação da Interface do Usuário do aplicativo e no par relevante.

Ao implementar padrões de controle para uma estrutura de código gerenciado, como um aplicativo UWP usando C# ou Microsoft Visual Basic, você pode usar interfaces .NET Framework para representar esses padrões em vez de usar a representação da interface COM. Por exemplo, a interface do padrão de Automação da Interface do Usuário de uma implementação do provedor Microsoft .NET do padrão Invoke é IInvokeProvider.

Para ver uma lista de padrões de controle, interfaces de provedor e suas finalidades, consulte Padrões de controle e interfaces. Para ver a lista de tipos de controle, confira Visão geral dos tipos de controle de Automação da Interface do Usuário.

Diretrizes de como implementar padrões de controle

Os padrões de controle e suas finalidades fazem parte de uma definição maior da estrutura de Automação da IU e não se aplicam apenas ao suporte para acessibilidade de um aplicativo UWP. Ao implementar um padrão de controle, você deve verificar se está implementando-o de uma maneira que corresponda às diretrizes, conforme documentado nesses documentos e também na especificação de Automação da Interface do Usuário. Se você estiver procurando diretrizes, geralmente poderá usar a documentação da Microsoft e não precisará se referir à especificação. As diretrizes de cada padrão estão documentadas aqui: Implementando padrões de controle de Automação da Interface do Usuário. Observe que cada tópico dessa área tem uma seção de diretrizes e convenções de implementação e outra de membros necessários. As diretrizes geralmente referem-se a APIs específicas da interface de padrão de controle na referência Interfaces padrão de controle para provedores. Essas interfaces são do tipo nativo/COM (e suas APIs usam sintaxe de estilo COM). Mas tudo o que aparece lá tem seu equivalente no namespace Windows.UI.Xaml.Automation.Provider.

Se você está usando os pares de automação padrão e expandindo seu comportamento, esses pares serão gravados de acordo com as diretrizes da Automação da Interface do Usuário. Se eles permitirem padrões de controle, você poderá confiar no suporte aos padrões em conformidade com as diretrizes em Implementando padrões de controle da Automação da Interface do Usuário. Se um par de controle relatar que é representante de um tipo de controle definido pela Automação da Interface do Usuário, ele terá seguido as diretrizes documentadas em Permitindo tipos de controle de Automação da Interface do Usuário.

Mas talvez você precise de diretrizes adicionais para os padrões ou tipos de controle para seguir as recomendações da Automação da Interface do Usuário na implementação do par. Isso poderá ocorrer principalmente se você estiver implementando suporte a tipo ou padrão de controle que ainda não exista como uma implementação padrão em um controle de UWP. Por exemplo, o padrão para anotações não está implementado em nenhum dos controles XAML padrão. Mas você pode ter um aplicativo que use bastante as anotações e, portanto, deseja expor essa funcionalidade para que fique acessível. Nesse cenário, seu par deve implementar IAnnotationProvider e, provavelmente, relatar a si mesmo como o tipo de controle Document com as propriedades adequadas para indicar que seus documentos permitem anotações.

Nós recomendamos que você use as diretrizes referentes aos padrões em Implementando padrões de controle de Automação da Interface do Usuário ou aos tipos de controle em Permitindo tipos de controle de Automação da Interface do Usuário de acordo com a orientação e as diretrizes gerais. Você pode até mesmo tentar seguir alguns dos links de API para descrições e comentários sobre a finalidade das APIs. Mas, para obter as especificações de sintaxe que são necessárias para a programação de aplicativo UWP, encontre a API equivalente no namespace Windows.UI.Xaml.Automation.Provider e use suas páginas de referência para saber mais.

Classes de pares de automação internas

Em geral, os elementos implementam uma classe de par de automação quando aceitam a atividade da interface do usuário ou quando contêm informações necessárias para usuários de tecnologias adaptativas que representam a interface do usuário interativa ou significativa dos aplicativos. Nem todos os elementos visuais do UWP têm pares de automação. Os exemplos de classes que implementam pares de automação são Button e TextBox. Os exemplos de classes que não implementam pares de automação são Border e as classes baseadas em Panel, como Grid e Canvas. Um Panel não tem par porque fornece um comportamento de layout apenas visual. O usuário não pode interagir de jeito nenhum de acessibilidade relevante com o Panel. Todos os elementos filhos que um Panel contém são relatados a árvores de Automação da Interface do Usuário como elementos filho do próximo pai disponível na árvore que tenha um par ou representação de elemento.

Limites de processo de Automação de Interface do Usuário e de UWP

Normalmente, o código de cliente da Automação da Interface do Usuário que acessa um aplicativo UWP é executado fora do processo. A infraestrutura da estrutura de Automação da IU permite que as informações passem o limite do processo. Esse conceito é explicado melhor em Conceitos básicos de Automação da Interface do Usuário.

OnCreateAutomationPeer

Todas as classes derivadas de UIElement contêm o método virtual protegido OnCreateAutomationPeer. A sequência de inicialização do objeto para pares de automação chama OnCreateAutomationPeer para obter o objeto de par de automação para cada controle e assim construir uma árvore de Automação da IU para uso em tempo de execução. O código de Automação da Interface do Usuário pode usar o par para obter informações sobre recursos e características de um controle e para simular o uso interativo por meio de seus padrões de controle. Um controle personalizado que dá suporte à automação deve substituir OnCreateAutomationPeer e retornar uma instância de uma classe derivada de AutomationPeer. Por exemplo, se um controle personalizado é derivado da classe ButtonBase, o objeto retornado por OnCreateAutomationPeer deve derivar de ButtonBaseAutomationPeer.

Se você estiver escrevendo uma classe de controle personalizada e pretende também fornecer um novo par de automação, você deverá substituir o método OnCreateAutomationPeer do controle personalizado para que ele retorne uma nova instância do par. A classe de par precisa ser derivada direta ou indiretamente de AutomationPeer.

Por exemplo, o código a seguir declara que o controle personalizado NumericUpDown deve usar o par NumericUpDownPeer para fins de Automação da Interface do Usuário.

using Windows.UI.Xaml.Automation.Peers;
...
public class NumericUpDown : RangeBase {
    public NumericUpDown() {
    // other initialization; DefaultStyleKey etc.
    }
    ...
    protected override AutomationPeer OnCreateAutomationPeer()
    {
        return new NumericUpDownAutomationPeer(this);
    }
}
Public Class NumericUpDown
    Inherits RangeBase
    ' other initialization; DefaultStyleKey etc.
       Public Sub New()
       End Sub
       Protected Overrides Function OnCreateAutomationPeer() As AutomationPeer
              Return New NumericUpDownAutomationPeer(Me)
       End Function
End Class
// NumericUpDown.idl
namespace MyNamespace
{
    runtimeclass NumericUpDown : Windows.UI.Xaml.Controls.Primitives.RangeBase
    {
        NumericUpDown();
        Int32 MyProperty;
    }
}

// NumericUpDown.h
...
struct NumericUpDown : NumericUpDownT<NumericUpDown>
{
	...
    Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer()
    {
        return winrt::make<MyNamespace::implementation::NumericUpDownAutomationPeer>(*this);
    }
};
//.h
public ref class NumericUpDown sealed : Windows::UI::Xaml::Controls::Primitives::RangeBase
{
// other initialization not shown
protected:
    virtual AutomationPeer^ OnCreateAutomationPeer() override
    {
         return ref new NumericUpDownAutomationPeer(this);
    }
};

Observação

A implementação OnCreateAutomationPeer não deve fazer nada além de inicializar uma nova instância do par de automação personalizado (passando o controle da chamada como proprietário) e retornar a instância. Não tente lógicas adicionais nesse método. Especificamente, qualquer lógica que possa levar à destruição do AutomationPeer na mesma chamada pode levar a um comportamento de tempo de execução inesperado.

Em implementações típicas de OnCreateAutomationPeer, o owner é especificado como this ou Me, pois a substituição do método está no mesmo escopo que o resto da definição de classe do controle.

A definição de classe de par real pode ser feita no mesmo arquivo de código do controle ou em um arquivo de código separado. As definições de pares existem no namespace Windows.UI.Xaml.Automation.Peers, separado dos controles para os quais os pares são fornecidos. Você também pode optar por declarar os pares em um namespace separado, desde que faça referência aos namespaces necessários para a chamada do método OnCreateAutomationPeer.

Escolhendo a classe base do par correto

Verifique se o seu AutomationPeer é derivado de uma classe base que dá a melhor correspondência para a lógica de par existente da classe de controle da qual você está derivando. No caso do exemplo anterior, como NumericUpDown deriva de RangeBase, há uma classe RangeBaseAutomationPeer disponível que você deve basear seu ponto. Usando a classe de par mais próxima correspondente em paralelo a como você deriva o próprio controle, é possível evitar substituir pelo menos algumas das funcionalidades de IRangeValueProvider porque a classe de par base já as implementa.

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

Se você derivar diretamente de ContentControl, essa classe não terá um comportamento de par de automação padrão, porque não há uma implementação de OnCreateAutomationPeer que referencie uma classe de par. Assim, implemente OnCreateAutomationPeer para usar seu próprio par ou use FrameworkElementAutomationPeer como par, se esse nível de suporte à acessibilidade é adequado para seu controle.

Observação

Você geralmente não deriva de AutomationPeer em vez de FrameworkElementAutomationPeer. Se derivar diretamente de AutomationPeer, você precisará duplicar muito suporte básico de acessibilidade que poderiam vir de FrameworkElementAutomationPeer.

Inicialização de uma classe de par personalizada

O par de automação deve definir um construtor de tipo seguro que usa uma instância do controle do proprietário para a inicialização base. No próximo exemplo, a implementação passa o valor de owner para a base de RangeBaseAutomationPeer e, em última instância, é o FrameworkElementAutomationPeer que realmente usa owner para definir FrameworkElementAutomationPeer.Owner.

public NumericUpDownAutomationPeer(NumericUpDown owner): base(owner)
{}
Public Sub New(owner As NumericUpDown)
    MyBase.New(owner)
End Sub
// NumericUpDownAutomationPeer.idl
import "NumericUpDown.idl";
namespace MyNamespace
{
    runtimeclass NumericUpDownAutomationPeer : Windows.UI.Xaml.Automation.Peers.AutomationPeer
    {
        NumericUpDownAutomationPeer(NumericUpDown owner);
        Int32 MyProperty;
    }
}

// NumericUpDownAutomationPeer.h
...
struct NumericUpDownAutomationPeer : NumericUpDownAutomationPeerT<NumericUpDownAutomationPeer>
{
    ...
    NumericUpDownAutomationPeer(MyNamespace::NumericUpDown const& owner);
};
//.h
public ref class NumericUpDownAutomationPeer sealed :  Windows::UI::Xaml::Automation::Peers::RangeBaseAutomationPeer
//.cpp
public:    NumericUpDownAutomationPeer(NumericUpDown^ owner);

Métodos Core de AutomationPeer

Devido à infraestrutura de UWP, os métodos substituíveis de um par de automação fazem parte de um par de métodos: o método de acesso público que o provedor de Automação da Interface do Usuário usa como ponto de encaminhamento dos clientes de Automação da Interface do Usuário e o método de personalização "Core" protegido que uma classe UWP pode substituir para afetar o comportamento. O par de métodos é conectado por padrão de forma que a chamada do método de acesso sempre invoca o método "Core" paralelo que tem a implementação do provedor ou, como um fallback, invoca a implementação padrão das classes base.

Ao implementar um par para um controle personalizado, substitua todos os métodos "Core" da classe de par de automação base onde você quer expor um comportamento exclusivo para seu controle personalizado. O código de Automação da Interface do Usuário obtém informações sobre o seu controle chamando métodos públicos da classe de par. Para fornecer informações sobre seu controle, substitua cada método por um nome terminado com "Core" quando a implementação e o design do controle cria cenários de acessibilidade ou outros cenários de Automação da Interface do Usuário diferentes dos que têm suporte na classe de par de automação base.

Sempre que você define uma nova classe de par, implemente pelo menos o método GetClassNameCore, como mostra o próximo exemplo.

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

Observação

Convém armazenar as cadeias como constantes em vez de fazer isso diretamente no corpo do método, mas é você quem decide. Para GetClassNameCore, você não precisa localizar essa cadeia de caracteres. A propriedade LocalizedControlType é usada sempre que um cliente de Automação da Interface do Usuário (não ClassName) necessita de uma cadeia localizada.

GetAutomationControlType

Algumas tecnologias assistenciais usam o valor GetAutomationControlType diretamente ao relatar característica dos itens em uma árvore de Automação da IU, como informações adicionais além do Name de Automação da Interface do Usuário. Se seu controle for bastante diferente do controle do qual está fazendo a derivação e quiser relatar um tipo de controle diferente daquele relatado pela classe do par base usada pelo controle, você deverá implementar um par e substituir GetAutomationControlTypeCore na implementação do par. Isso é especialmente importante quando você deriva de uma classe base generalizada, como ItemsControl ou ContentControl, em que o par base não fornece informações precisas sobre o tipo de controle.

Sua implementação de GetAutomationControlTypeCore descreve seu controle retornando um valor de AutomationControlType. Você pode retornar AutomationControlType.Custom, mas deve retornar um dos tipos de controle mais específicos se ele descrever precisamente os cenários principais de seu controle. Veja um exemplo.

protected override AutomationControlType GetAutomationControlTypeCore()
{
    return AutomationControlType.Spinner;
}

Observação

A menos que você especifique AutomationControlType.Custom, não é necessário implementar GetLocalizedControlTypeCore para fornecer um valor da propriedade LocalizedControlType aos clientes. A infraestrutura comum da Automação da Interface do Usuário fornece cadeias de caracteres traduzidas para cada valor de AutomationControlType possível diferente de AutomationControlType.Custom.

GetPattern e GetPatternCore

A implementação do par de GetPatternCore retorna o objeto que dá suporte ao padrão solicitado no parâmetro de entrada. Especificamente, um cliente de Automação da Interface do Usuário chama um método que é encaminhado ao método GetPattern do provedor e especifica um valor de enumeração PatternInterface que nomeia o padrão solicitado. Sua substituição do GetPatternCore deve retornar o objeto que implementa o padrão especificado. Esse objeto é o próprio par, pois o par deve implementar a interface do padrão correspondente sempre que relata o suporte a um padrão. Se o seu par não tem uma implementação personalizada de um padrão, mas você sabe que a base do par não implementa o padrão, pode chamar a implementação de GetPatternCore do tipo base de GetPatternCore. O GetPatternCore de um par deve retornar null se o par não dá suporte ao padrão. Porém, em vez de retornar null diretamente de sua implementação, normalmente você dependeria da chamada da implementação base para retornar null a qualquer padrão sem suporte.

Quando um padrão tem suporte, a implementação de GetPatternCore pode retornar this ou Me. Espera-se que o cliente de Automação da Interface do Usuário converta o valor de retorno de GetPattern para a interface padrão solicitada sempre que não for null.

Se uma classe de par é herdada de outro par e todos os relatórios de padrão e suporte já são tratados pela classe base, a implementação de GetPatternCore não é necessária. Por exemplo, se você está implementando um controle de intervalo que deriva de RangeBase e seu par derivar de RangeBaseAutomationPeer, esse par se retorna para o PatternInterface.RangeValue e tem implementações ativas da interface IRangeValueProvider que dão suporte ao padrão.

Embora este não seja o código literal, este exemplo é próximo da implementação de GetPatternCore já presente no RangeBaseAutomationPeer.

protected override object GetPatternCore(PatternInterface patternInterface)
{
    if (patternInterface == PatternInterface.RangeValue)
    {
        return this;
    }
    return base.GetPattern(patternInterface);
}

Se você estiver implementando um par em que não tem todo o suporte necessário de uma classe de pares base ou se desejar alterar ou adicionar ao conjunto de padrões herdados de base aos quais seu par pode dar suporte, substitua o GetPatternCore para permitir que os clientes de Automação de Interface do Usuário usem os padrões.

Para obter uma lista dos padrões de provedor disponíveis na implementação do UWP do suporte à Automação de Interface do Usuário, consulte Windows.UI.Xaml.Automation.Provider. Cada tal padrão tem um valor correspondente a enumeração de PatternInterface, que é como os clientes de Automação da Interface do Usuário solicitam o padrão em uma chamada do GetPattern.

Um par pode relatar que dá suporte a mais de um padrão. Nesse caso, a substituição deve incluir a lógica do caminho de retorno para cada valor de PatternInterface com suporte e retornar o par a cada correspondência. Espera-se que o chamador solicite apenas uma interface por vez e o chamador decide se deve fazer a conversão para a interface esperada.

Consulte um exemplo da uma substituição de GetPatternCore para um par personalizado. Ele relata o suporte a dois padrões, IRangeValueProvider e IToggleProvider. Este é um controle de exibição de mídia que pode ser mostrado em tela inteira (o modo de alternância) e tem uma barra de progresso na qual os usuários podem escolher uma posição (o controle de intervalo). Esse código vem do Exemplo de acessibilidade XAML.

protected override object GetPatternCore(PatternInterface patternInterface)
{
    if (patternInterface == PatternInterface.RangeValue)
    {
        return this;
    }
    else if (patternInterface == PatternInterface.Toggle)
    {
        return this;
    }
    return null;
}

Encaminhando padrões de subelementos

Uma implementação do método GetPatternCore também pode especificar um subelemento ou uma parte como um provedor de padrões para seu host. Este exemplo imita a maneira como ItemsControl transfere o tratamento do padrão de rolagem para o par de seu controle ScrollViewer interno. Para especificar um subelemento para o tratamento de padrão, esse código obtém o objeto de subelemento, cria um par para esse subelemento usando o método FrameworkElementAutomationPeer.CreatePeerForElement e retorna o novo par.

protected override object GetPatternCore(PatternInterface patternInterface)
{
    if (patternInterface == PatternInterface.Scroll)
    {
        ItemsControl owner = (ItemsControl) base.Owner;
        UIElement itemsHost = owner.ItemsHost;
        ScrollViewer element = null;
        while (itemsHost != owner)
        {
            itemsHost = VisualTreeHelper.GetParent(itemsHost) as UIElement;
            element = itemsHost as ScrollViewer;
            if (element != null)
            {
                break;
            }
        }
        if (element != null)
        {
            AutomationPeer peer = FrameworkElementAutomationPeer.CreatePeerForElement(element);
            if ((peer != null) && (peer is IScrollProvider))
            {
                return (IScrollProvider) peer;
            }
        }
    }
    return base.GetPatternCore(patternInterface);
}

Outros métodos Core

Talvez seu controle precise dar suporte a equivalentes de teclado para os cenários principais. Para saber porque isso pode ser necessário, consulte Acessibilidade pelo teclado. A implementação do suporte a chaves deve fazer parte do código do controle e não do código do par, porque ela faz parte de uma lógica de controle, mas a classe de par deve substituir os métodos GetAcceleratorKeyCore e GetAccessKeyCore para relatar para clientes de Automação da Interface do Usuário cujas teclas são usadas. Considere que talvez as cadeias de caracteres que relatam informações de teclas precisem ser localizadas e, portanto, devem ser provenientes de recursos e não de cadeias de caracteres embutidas em código.

Se você estiver fornecendo um par para uma classe suporta uma coleção, será melhor derivar de classes funcionais e classes de par que já têm esse tipo de suporte à coleção. Se você não puder fazê-lo, os pares de controles que mantêm as coleções de filhos podem ter que substituir o método de par relacionado à coleção do GetChildrenCore para relatar corretamente as relações pai-filho para a árvore de Automação de Interface do Usuário.

Implemente os métodos IsContentElementCore e IsControlElementCore para indicar se o controle contém dados ou tem uma função interativa na interface do usuário (ou ambos). Por padrão, os dois métodos retornam true. Essas configurações melhoram a usabilidade de tecnologias assistenciais, como leitores de tela, que podem usar esses métodos para filtrar a árvore de automação. Se o seu método GetPatternCore transfere o tratamento de padrões para um par de subelemento, o método IsControlElementCore do par de subelemento pode retornar false para ocultar o par de subelemento na árvore de automação.

Alguns controles podem dar suporte a cenários de rotulamento, em que uma parte do rótulo de texto fornece informações para uma parte que não é de texto, ou um controle se destina a uma relação de rotulamento conhecida com outro controle da interface do usuário. Quando é possível fornecer um comportamento baseado em classe útil, você pode substituir GetLabeledByCore para fornecer esse comportamento.

GetBoundingRectangleCore e GetClickablePointCore são usados principalmente para cenários de testes automatizados. Se você quiser dar suporte a testes automatizados para seu controle, convém substituir esses métodos. Isso pode ser útil em controles de tipo de intervalo, em que você não pode sugerir um único ponto porque o local em que o usuário clica no espaço de coordenadas tem um efeito diferente em um intervalo. Por exemplo, o par de automação ScrollBar padrão substitui GetClickablePointCore para retornar um valor Point "não é um número".

GetLiveSettingCore influencia o padrão de controle do valor de LiveSetting de Automação da Interface do Usuário. Convém substitui-lo quando você quer que o controle retorne um valor diferente de AutomationLiveSetting.Off. Para saber mais sobre o que LiveSetting representa, consulte AutomationProperties.LiveSetting.

Você pode substituir GetOrientationCore quando o controle tem uma propriedade de orientação configurável que pode mapear para AutomationOrientation. As classes ScrollBarAutomationPeer e SliderAutomationPeer são capazes disso.

Implementação base em FrameworkElementAutomationPeer

A implementação base de FrameworkElementAutomationPeer fornece algumas informações de Automação da Interface do Usuário que podem ser interpretadas por meio de várias propriedades de layout e comportamento definidas em nível de estrutura.

  • GetBoundingRectangleCore: retorna uma estrutura Rect baseada nas características de layout conhecidas. Retorna um valor de 0 Rect se IsOffscreen é true.
  • GetClickablePointCore: retorna uma estrutura Point baseada nas características de layout conhecidas, desde que haja um BoundingRectangle diferente de zero
  • GetNameCore: um comportamento mais amplo do que pode ser resumido aqui; consulte GetNameCore. Basicamente, é tentada uma conversão de cadeia de caracteres em qualquer conteúdo conhecido de um ContentControl ou de classes relacionadas que têm conteúdo. Além disso, quando há um valor de LabeledBy, o valor de Name desse item é usado como Name.
  • HasKeyboardFocusCore: avaliado com base nas propriedades FocusState e IsEnabled do proprietário. Os elementos que não são controles sempre retornam false.
  • IsEnabledCore: avaliado com base na propriedade IsEnabled do proprietário, se tem um Control. Os elementos que não são controles sempre retornam true. Isso não significa que o proprietário está habilitado do ponto de vista da interação convencional. Significa que o par está habilitado, mesmo que o proprietário não tenha uma propriedade IsEnabled.
  • IsKeyboardFocusableCore: retorna true se proprietário é um Control; caso contrário, é false.
  • IsOffscreenCore: uma Visibility de Collapsed no elemento do proprietário ou qualquer de seus pais se iguala a um valor true para IsOffscreen. Exceção: um objeto Popup pode estar visível, mesmo que os pais de seu proprietário não estejam.
  • SetFocusCore: chama Focus.
  • GetParent: chama FrameworkElement.Parent do proprietário e pesquisa o par apropriado. Este não é um par de substituição com um método "Core". Por isso, você não pode alterar esse comportamento.

Observação

Os pares do UWP padrão implementam um comportamento usando o código nativo interno que implementa o UWP, não necessariamente usando o código de UWP padrão. Você não poderá ver o código ou a lógica da implementação por meio da reflexão do CLR (common language runtime) ou de outras técnicas. Você também não vê páginas de referência distintas na referência para substituições específicas de subclasse do comportamento do par base. Por exemplo, pode haver um comportamento adicional para GetNameCore de um TextBoxAutomationPeer que não é descrito na página de referência de AutomationPeer.GetNameCore, e não existe uma página de referência de TextBoxAutomationPeer.GetNameCore. Não existe nem mesmo uma página de referência de TextBoxAutomationPeer.GetNameCore. Em vez disso, leia o tópico de referência para a classe dos pares mais imediatos e consulte as notas de implementação na seção Comentários.

Pares e AutomationProperties

O ponto de automação deve fornecer os valores padrão apropriados para obter informações relacionadas à acessibilidade de seu controle. Observe que qualquer código de aplicativo que use o controle pode substituir alguns desses comportamentos incluindo valores de propriedades anexadas de AutomationProperties em instâncias de controles. Os chamadores podem fazer isso para os controles padrão ou para controles personalizados. Por exemplo, o XAML a seguir cria um botão que tem duas propriedades personalizadas da Automação da Interface do Usuário: <Button AutomationProperties.Name="Special" AutomationProperties.HelpText="This is a special button."/>

Para saber mais sobre propriedades anexadas de AutomationProperties, consulte Informações básicas de acessibilidade.

Alguns dos métodos AutomationPeer existem devido ao contrato geral de como se espera que os provedores de Automação da Interface do Usuário relatem informações, mas geralmente esses métodos não são implementados em pares de controle. Isso ocorre porque espera-se que essas informações sejam fornecidas por valores de AutomationProperties aplicados ao código do aplicativo que usa os controles em uma interface do usuário específica. Por exemplo, a maioria dos aplicativos definiria a relação de rotulamento entre dois controles diferentes na interface do usuário aplicando um valor AutomationProperties.LabeledBy. Porém, LabeledByCore é implementado em determinados pares que representam relações de dados ou itens em um controle, como ao usar uma parte do cabeçalho para rotular uma parte do campo de dados, rotular itens com seus contêineres ou cenários semelhantes.

ImpIementando padrões

Consultemos como escrever um par para um controle que implementa um comportamento de expandir-recolher implementando a interface do padrão do controle de expandir-recolher. O par deve permitir a acessibilidade para o comportamento de expandir-recolher retornando a si mesmo sempre que o GetPattern é chamado com um valor de PatternInterface.ExpandCollapse. O par deve então herdar a interface do provedor para esse padrão (IExpandCollapseProvider) e fornecer implementações para cada um dos membros dessa interface do provedor. Nesse caso, a interface tem três membros para serem substituídos: Expand, Collapse, ExpandCollapseState.

É útil planejar com antecedência a acessibilidade no design de API da própria classe. Sempre que você tem um comportamento que pode ser solicitado por interações típicas com um usuário que trabalha na interface do usuário ou por meio de um padrão de provedor de automação, forneça um método único que a resposta da interface do usuário ou o padrão de automação possa chamar. Por exemplo, se o seu controle tem partes de botões com manipuladores de eventos conectados que podem expandir ou recolher o controle, e tem equivalentes de teclado para essas ações, esses manipuladores de eventos devem chamar o mesmo método que você chama no corpo das implementações de Expand ou Collapse para IExpandCollapseProvider no par. Usar um método de lógica comum também pode ser uma maneira útil para verificar se os estados visuais do seu controle são atualizados para mostrar o estado lógico de maneira uniforme, independentemente de como o comportamento foi invocado.

Em uma implementação típica, as APIs do provedor primeiro chamam Owner para acessar a instância do controle em tempo de execução. Depois, os métodos de comportamento necessários podem ser chamados nesse objeto.

public class IndexCardAutomationPeer : FrameworkElementAutomationPeer, IExpandCollapseProvider {
    private IndexCard ownerIndexCard;
    public IndexCardAutomationPeer(IndexCard owner) : base(owner)
    {
         ownerIndexCard = owner;
    }
}

Como alternativa de implementação, o próprio controle pode fazer referência a seu par. Esse é um padrão comum em caso de acionamento de eventos de automação do controle, pois RaiseAutomationEvent é um método de par.

Eventos de Automação da Interface do Usuário

Os eventos de Automação da Interface do Usuário estão nas seguintes categorias.

Evento Descrição
Alteração da propriedade É disparado quando uma propriedade em um padrão de controle ou elemento de Automação da IU muda. Por exemplo, se um cliente precisa monitorar o controle de uma caixa de seleção do aplicativo, ele pode registrar-se para escutar um evento de alteração de propriedade na propriedade ToggleState. Quando o controle da caixa de seleção está marcado ou desmarcado, o provedor dispara o evento e o cliente pode agir conforme o necessário.
Ação de elemento É disparado quando uma alteração na interface do usuário resulta de atividade do usuário ou programática. Por exemplo, quando um botão é clicado ou invocado por meio do padrão Invoke.
Alteração de estrutura É disparado quando a estrutura da árvore de Automação da IU muda. A estrutura muda quando novos itens da interface do usuário se tornam visíveis, ocultos ou são removidos da área de trabalho.
Alteração global É disparado quando ocorrem ações de interesse global do cliente, como quando o foco muda de um elemento para outro ou quando uma janela filha é fechada. Alguns eventos não indicam necessariamente que o estado da interface do usuário mudou. Por exemplo, se o usuário vai para um campo de entrada de texto e clica em um botão para atualizar o campo, um evento TextChanged é disparado, mesmo que o usuário não tenha realmente mudado o texto. Ao processar um evento, pode ser necessário que o aplicativo cliente verifique se algo realmente mudou antes de executar uma ação.

Identificadores AutomationEvents

Os eventos de Automação da Interface do Usuário são identificados por valores de AutomationEvents. Os valores da enumeração identificam o tipo de evento de forma exclusiva.

Gerando eventos

Os clientes de Automação de Interface do Usuário podem se inscrever nos eventos de automação. No modelo de par de automação, os pares de controles personalizados devem informar as alterações de estado do controle que são relevantes para a acessibilidade, chamando o método RaiseAutomationEvent. Da mesma forma, quando um valor de propriedade de Automação de Interface do Usuário de chave é alterado, os pares de controle personalizados devem chamar o método RaisePropertyChangedEvent.

O próximo exemplo de código mostra como obter o objeto de par do código de definição do controle e chamar um método para disparar um evento desse par. Como uma otimização, o código determina se há algum ouvinte para esse tipo de evento. Disparar o evento e criar o objeto par somente quando há ouvintes evita sobrecarga desnecessária e ajuda o controle a continuar respondendo.

if (AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged))
{
    NumericUpDownAutomationPeer peer =
        FrameworkElementAutomationPeer.FromElement(nudCtrl) as NumericUpDownAutomationPeer;
    if (peer != null)
    {
        peer.RaisePropertyChangedEvent(
            RangeValuePatternIdentifiers.ValueProperty,
            (double)oldValue,
            (double)newValue);
    }
}

Navegação de par

Depois de localizar um par de automação, um cliente de Automação de Interface do Usuário pode navegar pela estrutura de par de um aplicativo chamando os métodos GetChildren e GetParent do objeto de par. A navegação entre elementos de interface do usuário dentro de um controle é suportada pela implementação de par do método GetChildrenCore. O sistema de Automação da Interface do Usuário chama esse método para construir uma árvore de subelementos contida em um controle; por exemplo, itens de lista em uma caixa de lista. O método GetChildrenCore padrão na FrameworkElementAutomationPeer percorre a árvore visual de elementos para construir a árvore de pares de automação. Os controles personalizados podem substituir esse método para expor uma representação diferente dos elementos filho para os clientes de automação, retornando os pares de automação dos elementos que transmitem informações ou permitem a interação do usuário.

Suporte à automação nativo para padrões de texto

Alguns dos pares de automação de aplicativo UWP padrão dão suporte ao padrão de controle para o padrão de texto (PatternInterface.Text). O suporte ocorre por meio de métodos nativos e os pares envolvidos não notam a interface de ITextProvider na herança (gerenciada). No entanto, se um cliente de Automação da Interface do Usuário gerenciado ou não gerenciado consulta os padrões do par, ele relata suporte ao padrão de texto e fornece comportamento para partes do padrão quando as APIs do cliente são chamadas.

Se você pretende derivar de um dos controles de texto de aplicativo UWP e também criar um par personalizado que deriva de um dos pares relacionados a texto, verifique as seções Comentários do par para saber mais sobre qualquer suporte a padrões de nível nativo. É possível acessar o comportamento de base nativo no par personalizado se você chamar a implementação base a partir da implementações da interface do provedor gerenciado, mas é difícil modificar o que a implementação base faz porque as interfaces nativas no par e no controle do proprietário não estão expostas. Normalmente você deve usar as implementações base como foram fornecidas (somente com base em chamada) ou substituir por completo a funcionalidade com seu próprio código gerenciado e não chamar a implementação de base. Esse último é um cenário avançado no qual você precisa de um bom conhecimento da estrutura de serviços de texto que é usada pelo controle para permitir os requisitos de acessibilidade durante o uso dessa estrutura.

AutomationProperties.AccessibilityView

Além de proporcionar um par personalizado, você também pode ajustar a representação da exibição de grade para qualquer instância de controle, através da criação AutomationProperties.AccessibilityView em XAML. Isso não é implementado como parte de uma classe de pares, mas vamos mencioná-lo aqui porque é pertinente para suporte geral de acessibilidade para controles personalizados ou para modelos que você personalizar.

O principal cenário para uso de AutomationProperties.AccessibilityView é para omitir deliberadamente determinados controles em um modelo das exibições da Automação da Interface do Usuário, pois eles não contribuem significativamente para a visualização de acessibilidade do controle inteiro. Para evitar isso, defina AutomationProperties.AccessibilityView como "Raw".

Gerando exceções de pares de automação

As APIs que você implementa para seu suporte a par de automação são permitidas para gerar exceções. Espera-se que qualquer cliente da Automação da Interface do Usuário que esteja escutando seja robusto o suficiente para continuar mesmo após a geração da maioria das exceções. Muito provavelmente, o ouvinte busca uma árvore de automação totalmente ativa que inclua aplicativos diferentes dos seus, e é inaceitável para o design do cliente desativar o cliente inteiro apenas porque uma área da árvore gerou uma exceção de mesmo nível quando o cliente chamou suas APIs.

Para os parâmetros que são transmitidos para o seu par, é aceitável validar a entrada e, por exemplo, lançar ArgumentNullException caso ele tenha passado null e isso não é um valor válido para sua implementação. Mas se houver operações subsequentes para serem executadas por seu par, lembre-se de que as interações do par com o controle de hospedagem têm alguma característica assíncrona em relação a elas. Tudo o que um par faz não necessariamente bloqueia o thread de interface de usuário no controle (e é muito provável que não faça isso). Portanto, pode haver situações em que um objeto estava disponível ou tinha determinadas propriedades quando o par foi criado ou um método de par de automação foi chamado pela primeira vez mas, no meio tempo, o estado do controle foi modificado. Nesses casos, há duas exceções dedicadas que o provedor pode gerar:

  • Gerar ElementNotAvailableException caso você não consiga contatar o proprietário do par nem um elemento do par relacionado com base nas informações originais passadas por sua API. Por exemplo, você pode ter um par que esteja tentando executar seus métodos, mas o proprietário já foi removido da interface do usuário, como uma caixa de diálogo modal que tenha sido fechada. Para um cliente non-.NET, isso mapeia para UIA_E_ELEMENTNOTAVAILABLE.
  • Gerar ElementNotEnabledException caso ainda exista um proprietário, mas ele esteja em um modo, como IsEnabled=false, que bloqueia algumas das mudanças programáticas específicas que seu par esteja tentando fazer. Para um cliente non-.NET, isso é mapeado para UIA_E_ELEMENTNOTENABLED.

Além disso, os pares devem ser relativamente conservadores no que diz respeito às exceções que eles geram usando seu suporte a par. A maioria dos clientes não pode tratar exceções de pares nem torná-los opções acionáveis para os usuários ao interagirem com o cliente. Portanto, as exceções inoperantes e de captura, sem serem geradas novamente em suas implementações de par, às vezes são uma estratégia melhor do que gerar exceções toda vez que o par tentar fazer alguma coisa que não funcione. Leve em consideração também que a maioria dos clientes da Automação da Interface do Usuário não é gravada em código gerenciado. A maioria é escrita em COM e está apenas verificando se há S_OK em um HRESULT sempre que chamam um método de cliente de Automação da Interface do Usuário que acaba acessando seu par.