Compartir a través de


Automatización de la interfaz de usuario de un control personalizado de WPF

La automatización de la interfaz de usuario proporciona una única interfaz generalizada que los clientes de automatización pueden usar para examinar o operar las interfaces de usuario de una variedad de plataformas y marcos. La automatización de la interfaz de usuario permite el código de control de calidad (prueba) y las aplicaciones de accesibilidad, como los lectores de pantalla, para examinar los elementos de la interfaz de usuario y simular la interacción del usuario con ellos desde otro código. Para obtener información sobre la automatización de la interfaz de usuario en todas las plataformas, consulte Accesibilidad.

En este tema se describe cómo implementar un proveedor de automatización de la interfaz de usuario del lado servidor para un control personalizado que se ejecuta en una aplicación WPF. WPF admite la automatización de la interfaz de usuario a través de un árbol de objetos de automatización que refleja el árbol de elementos de la interfaz de usuario. El código de prueba y las aplicaciones que proporcionan características de accesibilidad pueden usar objetos del mismo nivel de automatización directamente (para código en proceso) o a través de la interfaz generalizada proporcionada por la automatización de la interfaz de usuario.

Clases del mismo nivel de Automatización

Los controles de WPF admiten la automatización de la interfaz de usuario a través de un árbol de clases del mismo nivel que derivan de AutomationPeer. Por convención, los nombres de clase del mismo nivel comienzan con el nombre de la clase de control y terminan con "AutomationPeer". Por ejemplo, ButtonAutomationPeer es la clase homóloga de la clase de control Button. Las clases del mismo nivel son aproximadamente equivalentes a los tipos de control de automatización de la interfaz de usuario, pero son específicas de los elementos de WPF. El código de automatización que accede a aplicaciones WPF a través de la interfaz de automatización de la interfaz de usuario no utiliza directamente los colegas de automatización, pero el código de automatización en el mismo espacio de proceso puede utilizar directamente los colegas de automatización.

Clases del mismo nivel de automatización integradas

Los elementos implementan una clase del mismo nivel de automatización si aceptan actividad de interfaz del usuario o si contienen información necesaria para los usuarios de aplicaciones de lector de pantalla. No todos los elementos visuales de WPF tienen elementos del mismo nivel de automatización. Algunos ejemplos de clases que implementan sistemas del mismo nivel de automatización son Button, TextBoxy Label. Algunos ejemplos de clases que no implementan elementos del mismo nivel de automatización son clases que derivan de Decorator, como Border, y clases basadas en Panel, como Grid y Canvas.

La clase base Control no tiene una clase del mismo nivel correspondiente. Si necesita una clase del mismo nivel para corresponder a un control personalizado que deriva de Control, debe derivar la clase del mismo nivel personalizada de FrameworkElementAutomationPeer.

Consideraciones de seguridad para pares derivados

Los elementos del mismo nivel de automatización deben ejecutarse en un entorno de confianza parcial. El código del ensamblado UIAutomationClient no está configurado para ejecutarse en un entorno de confianza parcial y el código del mismo nivel de automatización no debe hacer referencia a ese ensamblado. En su lugar, debe usar las clases en el ensamblado UIAutomationTypes. Por ejemplo, debe usar la AutomationElementIdentifiers clase del ensamblado UIAutomationTypes, que corresponde a la AutomationElement clase del ensamblado UIAutomationClient. Es seguro hacer referencia al ensamblado UIAutomationTypes en el código del mismo nivel de automatización.

Navegación entre iguales

Después de localizar un compañero de automatización, el código en proceso puede navegar por el árbol de compañeros llamando a los métodos del objeto GetChildren y GetParent. La navegación entre los elementos de WPF dentro de un control es compatible con la implementación del método GetChildrenCore por parte del par. El sistema de automatización de la interfaz de usuario llama a este método para crear un árbol de subelementos contenidos en un control; por ejemplo, los elementos de lista de un cuadro de lista. El método predeterminado UIElementAutomationPeer.GetChildrenCore recorre el árbol visual de elementos para crear el árbol de pares de automatización. Los controles personalizados anulan este método para exponer elementos secundarios a los clientes de automatización, devolviendo los pares de automatización de elementos que transmiten información o permiten la interacción del usuario.

Personalizaciones en un elemento del mismo nivel derivado

Todas las clases que derivan de UIElement y ContentElement contienen el método OnCreateAutomationPeervirtual protegido . WPF llama a OnCreateAutomationPeer para obtener el objeto de par de automatización para cada control. El código de automatización puede usar el elemento del mismo nivel para obtener información sobre las características y características de un control y para simular el uso interactivo. Un control personalizado que admita la automatización debe invalidar OnCreateAutomationPeer y devolver una instancia de una clase que deriva de AutomationPeer. Por ejemplo, si un control personalizado deriva de la ButtonBase clase , el objeto devuelto por OnCreateAutomationPeer debe derivar de ButtonBaseAutomationPeer.

Al implementar un control personalizado, debe sobrescribir los métodos "Core" de la clase base de automatización peer que describen el comportamiento único y específico de su control personalizado.

Sobrescribir OnCreateAutomationPeer

Sobrescriba el método OnCreateAutomationPeer para su control personalizado de manera que devuelva su objeto de proveedor, el cual debe derivarse directa o indirectamente de AutomationPeer.

Invalidación de GetPattern

Los componentes de automatización simplifican algunos aspectos de la implementación de los proveedores de automatización del lado del servidor de la interfaz de usuario, pero los componentes de automatización de controles personalizados deben seguir gestionando las interfaces de patrón. Al igual que los proveedores que no son de WPF, los pares admiten patrones de control proporcionando implementaciones de interfaces en el System.Windows.Automation.Provider espacio de nombres, como IInvokeProvider. Las interfaces de patrón de control se pueden implementar mediante el propio elemento del mismo nivel o por otro objeto. La implementación del par de GetPattern devuelve el objeto que admite el patrón especificado. El código de automatización de la interfaz de usuario llama al GetPattern método y especifica un PatternInterface valor de enumeración. La anulación de GetPattern debe devolver el objeto que implementa el patrón especificado. Si su control no tiene una implementación personalizada de un patrón, puede llamar a la implementación del tipo base de GetPattern para obtener dicha implementación, o null si el patrón no es compatible con este tipo de control. Por ejemplo, un control NumericUpDown personalizado se puede establecer en un valor dentro de un rango, por lo que su elemento homólogo de Automatización de IU implementaría la interfaz IRangeValueProvider. En el ejemplo siguiente se muestra cómo se invalida el método del GetPattern mismo nivel para responder a un PatternInterface.RangeValue valor.

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

Un GetPattern método también puede especificar un subelemento como proveedor de patrones. En el código siguiente se muestra cómo ItemsControl transfiere el control de patrones de desplazamiento al mismo nivel de su control 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 un subelemento para el manejo de patrones, este código obtiene el objeto de subelemento, crea un par utilizando el método CreatePeerForElement, establece la propiedad EventsSource del nuevo par como el par actual y devuelve el nuevo par. Establecer EventsSource en un subelemento impide que el subelemento aparezca en el árbol del mismo nivel de automatización y designe todos los eventos generados por el subelemento como originado por el control especificado en EventsSource. El ScrollViewer control no aparece en el árbol de automatización y los eventos de desplazamiento que genera parecen originarse desde el ItemsControl objeto .

Invalidar los métodos "Core"

El código de automatización obtiene información sobre tu control llamando a métodos públicos de la clase compañera. Para proporcionar información sobre el control, invalide cada método cuyo nombre termina con "Core" cuando la implementación del control difiere de la proporcionada por la clase del mismo nivel de automatización base. Como mínimo, tu control debe implementar los métodos GetClassNameCore y GetAutomationControlTypeCore, como se muestra en el ejemplo siguiente.

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

La implementación de GetAutomationControlTypeCore describe el control devolviendo un ControlType valor. Aunque puede devolver ControlType.Custom, debe devolver uno de los tipos de control que son más específicos si describen con precisión su control. Un valor devuelto de ControlType.Custom requiere trabajo adicional para que el proveedor implemente automatización de la interfaz de usuario y los productos cliente de automatización de la interfaz de usuario no pueden anticiparse a la estructura de control, la interacción del teclado y los posibles patrones de control.

Implemente los IsContentElementCore métodos y IsControlElementCore para indicar si el control contiene contenido de datos o cumple un rol interactivo en la interfaz de usuario (o ambos). De forma predeterminada, ambos métodos devuelven true. Esta configuración mejora la facilidad de uso de las herramientas de automatización, como los lectores de pantalla, que pueden usar estos métodos para filtrar el árbol de automatización. Si el GetPattern método transfiere el manejo de patrones a una pareja de subelementos, el método del IsControlElementCore de la pareja de subelementos puede devolver false para ocultar dicha pareja del árbol de automatización. Por ejemplo, el desplazamiento en un ListBox se controla mediante un ScrollViewer, y el par de automatización para PatternInterface.Scroll lo devuelve el método GetPattern del ScrollViewerAutomationPeer que está asociado con el ListBoxAutomationPeer. Por lo tanto, el método IsControlElementCore de ScrollViewerAutomationPeer devuelve false, para que ScrollViewerAutomationPeer no aparezca en el árbol de automatización.

Tu compañero de automatización debería proporcionar los valores predeterminados adecuados para tu control. Tenga en cuenta que XAML que hace referencia a su control puede invalidar sus implementaciones de métodos principales al incluir atributos AutomationProperties. Por ejemplo, el código XAML siguiente crea un botón que tiene dos propiedades personalizadas de automatización de la interfaz de usuario.

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

Implementar proveedores de patrones

Las interfaces implementadas por un proveedor personalizado se declaran explícitamente si el elemento propietario deriva directamente de Control. Por ejemplo, el código siguiente declara un elemento del mismo nivel para un Control que implementa un valor de intervalo.

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

Si el control propietario deriva de un tipo específico de control como RangeBase, el par puede derivarse de una clase par derivada equivalente. En este caso, el elemento del mismo nivel derivaría de RangeBaseAutomationPeer, que proporciona una implementación base de IRangeValueProvider. En el siguiente código se muestra la declaración de tal par.

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

Para obtener una implementación de ejemplo, consulte el código fuente de C# o Visual Basic que implementa y consume un control personalizado NumericUpDown.

Activar eventos

Los clientes de Automation pueden suscribirse a eventos de automatización. Los controles personalizados deben notificar los cambios en el estado de control llamando al RaiseAutomationEvent método . Del mismo modo, cuando cambia un valor de propiedad, llame al RaisePropertyChangedEvent método . El código siguiente muestra cómo obtener el objeto del mismo nivel desde el código de control y llamar a un método para generar un evento. Como optimización, el código determina si hay oyentes para este tipo de evento. Generar el evento solo cuando hay oyentes evita sobrecargas innecesarias y ayuda a que el controlador siga siendo receptivo.

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 también