Benutzeroberflächenautomatisierung eines benutzerdefinierten WPF-Steuerelements

Die Benutzeroberflächenautomatisierung stellt eine einzelne allgemeine Schnittstelle bereit, die Automatisierungsclients zum Untersuchen oder Ausführen der Benutzeroberflächen für unterschiedliche Plattformen und Frameworks verwenden können. UI Automation ermöglicht es jeweils Qualitätssicherungcode (Testcode) und Barrierefreiheitsanwendungen, z.B. die Sprachausgabe, Benutzerschnittstellenelemente zu untersuchen und Benutzerzugriff mit ihnen aus anderem Code zu simulieren. Informationen zu UI Automation für alle Plattformen finden Sie unter „Barrierefreiheit“.

In diesem Thema wird beschrieben, wie ein serverseitiger Benutzeroberflächenautomatisierungs-Anbieter, der in einer WPF-Anwendung ausgeführt wird, für ein benutzerdefiniertes Steuerelement implementiert wird. WPF unterstützt U Automation über eine Struktur von Peer-Automatisierungsobjekten, die zur Struktur der Elemente der Benutzeroberfläche parallel ist. Testcode und -anwendungen, die Barrierefreiheitsfunktionen bereitstellen, können Automatisierungspeerobjekte direkt (für Code, der gerade ausgeführt wird) oder über eine allgemeine Schnittstelle verwenden, die von UI Automation bereitgestellt wird.

Automatisierungspeerklassen

WPF-Steuerelemente unterstützen die UI-Automatisierung durch einen Baum von Peer-Klassen, die sich von AutomationPeer ableiten. Gemäß der Konvention beginnen Namen von Peerklassen mit dem Klassennamen des Steuerelements und enden mit „AutomationPeer“. Zum Beispiel ist ButtonAutomationPeer die Peer-Klasse für die Button-Kontrollklasse. Die Peerklassen entsprechen weitgehend den UI Automation-Steuerelementtypen, gelten jedoch spezifisch für WPF-Elemente. Automatisierungscode der auf WPF-Anwendungen über die UI Automation-Schnittstelle zugreift, verwendet keine Automatisierungspeers direkt, jedoch kann der Automatisierungscode im selben Prozessbereich Automatisierungspeers direkt verwenden.

Integrierte Automatisierungspeerklassen

Elemente implementieren eine Automatisierungspeerklasse, wenn sie Schnittstellenaktivität vom Benutzer akzeptieren oder Informationen enthalten, die von Benutzern von Anwendungen der Sprachausgabe benötigt werden. Nicht alle visuellen WPF-Elemente verfügen über Automatisierungspeers. Beispiele für Klassen, die Automatisierungspeers implementieren, sind Button, TextBox und Label. Beispiele für Klassen, die keine Automatisierungspeer implementieren, sind Klassen, die von Decorator-Klassen abgeleitet werden, z. B. Border und Klassen basierend auf Panel, z. B. Grid und Canvas.

Die Control-Control-Basisklasse besitzt keine entsprechende Peerklasse. Wenn Sie eine Peer-Klasse benötigen, die einem benutzerdefinierten Steuerelement entspricht, das von Control abgeleitet ist, sollten Sie die benutzerdefinierte Peer-Klasse von FrameworkElementAutomationPeer ableiten.

Sicherheitsüberlegungen zu abgeleiteten Peers

Automatisierungspeers müssen in einer Teilausführungsumgebung ausgeführt werden. Code in der UIAutomationClient-Assembly ist nicht konfiguriert, in einer teilweise vertrauenswürdigen Umgebung ausgeführt zu werden, und Automatisierungspeercode sollte nicht auf diese Assembly verweisen. Stattdessen sollten Sie diese Klassen in der UIAutomationTypes-Assembly verwenden. Sie sollten zum Beispiel die AutomationElementIdentifiers-Klasse aus der Assembly UIAutomationTypes verwenden, die der AutomationElement-Klasse in der Baugruppe UIAutomationClient entspricht. Es ist sicher, auf die UIAutomationTypes-Assembly in Automatisierungspeercode zu verweisen.

Peernavigation

Nachdem ein Automatisierungspeer gefunden wurde, kann prozessinterner Code durch den Peerbaum navigieren, indem er die GetChildren- und GetParent-Methoden des Objekts aufruft. Die Navigation zwischen WPF-Elementen innerhalb eines Steuerelements wird von der Implementierung der GetChildrenCore-Methode durch die Gegenstelle unterstützt. Das Benutzeroberflächenautomatisierungssystem ruft diese Methode auf, um eine Struktur von Unterelementen zu erstellen, die in einem Steuerelement vorhanden sind, z.B. Listenelemente in einem Listenfeld. Die UIElementAutomationPeer.GetChildrenCore-Standardmethode durchläuft den visuellen Baum der Elemente, um den Baum der Automatisierungs-Peers zu erstellen. Benutzerdefinierte Steuerelemente überschreiben diese Methode, um untergeordnete Elemente für Automatisierungsclients verfügbar zu machen, dabei werden die Automatisierungspeers von Elementen zurückgegeben, die Informationen ausdrücken oder Benutzerinteraktion erlauben.

Anpassungen in einem abgeleiteten Peer

Alle Klassen, die von UIElement und ContentElement abgeleitet sind, enthalten die geschützte virtuelle Methode OnCreateAutomationPeer. WPF ruft OnCreateAutomationPeer auf, um das Automation-Peer-Objekt für jedes Steuerelement zu erhalten. Automatisierungscode kann Peers zum Abrufen von Informationen über Merkmale und Funktionen des Steuerelements verwenden und um interaktive Verwendung zu simulieren. Ein benutzerdefiniertes Steuerelement, das die Automatisierung unterstützt, muss OnCreateAutomationPeer außer Kraft setzen und eine Instanz einer Klasse zurückgeben, die sich von AutomationPeer ableitet. Wenn z.B. ein benutzerdefiniertes Steuerelement von der ButtonBase-Klasse abgeleitet ist, dann sollte das von OnCreateAutomationPeer zurückgegebene Objekt von ButtonBaseAutomationPeer abgeleitet sein.

Wenn Sie ein benutzerdefiniertes Steuerelement implementieren, müssen Sie die „Kernmethoden“ der grundlegenden Automatisierungspeerklasse außer Kraft setzen, die das Verhalten eindeutig beschreiben und für Ihr benutzerdefiniertes Steuerelement spezifisch sind.

OnCreateAutomationPeer außer Kraft setzen

Überschreiben Sie die OnCreateAutomationPeer-Methode für Ihr benutzerdefiniertes Steuerelement so, dass sie Ihr Provider-Objekt zurückgibt, das direkt oder indirekt von AutomationPeer abgeleitet ist.

GetPattern außer Kraft setzen

Automatisierungspeers vereinfachen zwar manche technischen Aspekte der Implementierung von serverseitigen UI Automation-Anbietern, aber Automatisierungspeers von benutzerdefinierten Steuerelementen müssen dennoch Musterschnittstellen behandeln. Wie Nicht-WPF-Anbieter unterstützen Peers Kontrollmuster, indem sie Implementierungen von Schnittstellen im System.Windows.Automation.Provider-Namespace bereitstellen, wie z.B. IInvokeProvider. Die Schnittstellen der Steuerelementmuster können entweder vom Peer selbst, oder von einem anderen Objekt implementiert werden. Die Implementierung des Peers von GetPattern gibt das Objekt zurück, das das angegebene Muster unterstützt. Der Code für die Benutzeroberflächenautomatisierung ruft die GetPattern-Methode auf und gibt einen PatternInterface-Enumerationswert an. Ihre Überschreibung von GetPattern sollte das Objekt zurückgeben, mit dem das angegebene Muster implementiert wird. Wenn Ihr Steuerelement über keine benutzerdefinierte Implementierung eines Musters verfügt, können Sie die Implementierung des Basistyps von GetPattern aufrufen, um entweder dessen Implementierung oder Null abzurufen, falls das Muster für diesen Steuerelementtyp nicht unterstützt wird. Beispielsweise kann ein benutzerdefiniertes NumericUpDown-Steuerelement auf einen Wert innerhalb eines Bereichs festgelegt werden, sodass sein UI Automation Peer die IRangeValueProvider-Schnittstelle implementiert. Das folgende Beispiel zeigt, wie die GetPattern-Methode der Gegenstelle außer Kraft gesetzt wird, um auf einen PatternInterface.RangeValue-Wert zu reagieren.

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

Eine GetPattern-Methode kann auch ein Unterelement als Musteranbieter angeben. Der folgende Code zeigt, wie ItemsControl das Scroll-Muster an den Peer der internen ScrollViewer-Kontrolle überträgt.

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  

Um ein Unterelement für die Behandlung von Mustern festzulegen, ruft dieser Code das Unterelement-Objekt ab, erstellt einen Peer mit Hilfe der CreatePeerForElement-Methode, setzt die EventsSource-Eigenschaft des neuen Peers auf den aktuellen Peer und gibt den neuen Peer zurück. Die Einstellung von EventsSource auf ein Unterelement verhindert, dass das Unterelement im Automatisierungspeerbaum erscheint und kennzeichnet alle von dem Unterelement ausgelösten Ereignisse als von der in EventsSource angegebenen Steuerung stammend. Das ScrollViewer-Steuerelement erscheint nicht in der Automatisierungsstruktur, und die von ihm erzeugten Bildlaufereignisse scheinen vom ItemsControl-Objekt auszugehen.

„Kern“-Methode außer Kraft setzen

Der Automatisierungscode ruft Informationen über Ihr Steuerelement ab, indem er die öffentlichen Methoden der Peer-Klasse aufruft. Um Informationen über Ihr Steuerelement bereitzustellen, setzen Sie jede Methode außer Kraft, deren Name mit „Core“ endet, falls die Implementierung Ihrer Steuerelemente sich von der Implementierung unterscheidet, die von der Basis-Automatisierungspeerklasse bereitgestellt wird. Ihr Steuerelement muss mindestens die GetClassNameCore- und GetAutomationControlTypeCore-Methoden und implementieren, wie im folgenden Beispiel gezeigt.

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

Ihre Implementierung von GetAutomationControlTypeCore beschreibt Ihr Steuerelement, indem sie einen ControlType-Wert zurückgibt. Obwohl Sie ControlType.Custom zurückgeben können, sollten Sie einen dieser spezifischeren Kontrolltypen zurückgeben, wenn er Ihr Steuerelement genau beschreibt. Ein Rückgabewert von ControlType.Custom erfordert zusätzliche Arbeit für den Anbieter, um die UI Automation zu implementieren, und die UI-Automation-Clientprodukte sind nicht in der Lage, die Kontrollstruktur, die Tastaturinteraktion und mögliche Kontrollmuster vorherzusehen.

Implementieren Sie die Methoden IsContentElementCore und IsControlElementCore, um anzugeben, ob Ihr Steuerelement Dateninhalte enthält und/oder eine interaktive Rolle auf der Benutzeroberfläche ausübt. Standardmäßig geben die beiden Methoden true zurück. Diese Einstellungen verbessern die Nutzbarkeit der Automatisierungstools, wie z.B. Sprachausgabe-Tools, die mit diesen Methoden die Automatisierungsstruktur filtern. Wenn Ihre GetPattern-Methode die Musterbehandlung an einen Unterelementpeer überträgt, kann die IsControlElementCore-Methode des Unterelementpeers false zurückgeben, um den Unterelementpeer in der Automatisierungsstruktur auszublenden. Zum Beispiel wird das Blättern in ListBox von ScrollViewer gehandhabt, und der Automatisierungs-Peer für PatternInterface.Scroll wird von der GetPattern-Methode von ScrollViewerAutomationPeer zurückgegeben, die mit dem ListBoxAutomationPeer verknüpft ist. Daher gibt die IsControlElementCore-Methode der ScrollViewerAutomationPeerfalse zurück, sodass ScrollViewerAutomationPeer nicht in der Automatisierungsstruktur erscheint.

Ihr Automatisierungspeer sollte dem Steuerelement entsprechende Standardwerte bereitstellen. Beachten Sie, dass XAML, das Ihr Steuerelement referenziert, Ihre Peer-Implementierungen von Core-Methoden durch Einfügen von AutomationProperties-Attributen außer Kraft setzen kann. Mithilfe des folgenden XAML wird beispielsweise eine Schaltfläche erstellt, die über zwei angepasste Benutzeroberflächenautomatisierungs-Eigenschaften verfügt.

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

Implementieren von Musteranbietern

Die von einem benutzerdefinierten Anbieter implementierten Schnittstellen werden explizit deklariert, falls das besitzende Element direkt aus Control ableitet. Der folgende Code deklariert zum Beispiel einen Peer für Control, der einen Bereichswert implementiert.

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

Falls das besitzende Steuerelement von einem spezifischen Steuerelement abgeleitet wird, z.B. RangeBase, kann der Peer aus einer Peer-Klasse abgeleitet werden, die auf eine ähnliche Art und Weise abgeleitet worden ist. In diesem Fall würde der Peer von RangeBaseAutomationPeer abgeleitet werden, der eine Basisimplementierung von IRangeValueProvider bereitstellt. Der folgende Code veranschaulicht die Deklaration eines solchen Peers.

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

Eine Beispielimplementierung finden Sie unter C# oder Visual Basic-Quellcode, der ein benutzerdefiniertes NumericUpDown-Steuerelement implementiert und verwendet.

Auslösen von Ereignissen

Automatisierungsclients können Automatisierungsereignisse abonnieren. Benutzerdefinierte Steuerelemente müssen Änderungen am Status des Steuerelements durch den Aufruf der RaiseAutomationEvent-Methode melden. Ebenso rufen Sie die RaisePropertyChangedEvent-Methode auf, wenn sich ein Eigenschaftswert ändert. Der folgende Code zeigt, wie Sie das Peerobjekt aus dem Steuerelementcode abrufen und eine Methode zum Auslösen eines Ereignisses aufrufen. Als Optimierung überprüft der Code, ob Listener für diesen Ereignistyp vorhanden sind. Ereignisse werden nur dann ausgelöst, wenn Listener vorhanden sind, sodass unnötiger Aufwand vermieden wird und das Steuerelement reaktionsfähig bleibt.

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

Weitere Informationen