Udostępnij za pośrednictwem


Automatyzacja interfejsu użytkownika niestandardowej kontrolki WPF

Automatyzacja interfejsu użytkownika udostępnia jeden, uogólniony interfejs, którego klienci automatyzacji mogą używać do badania lub obsługiwania interfejsów użytkownika różnych platform i frameworków. Automatyzacja interfejsu użytkownika umożliwia korzystanie zarówno z kodu kontroli jakości (testu) jak i aplikacji ułatwień dostępu, takich jak czytniki zawartości ekranu w celu sprawdzenia elementów interfejsu użytkownika i symulowania interakcji użytkownika z nimi z innego kodu. Aby uzyskać informacje na temat automatyzacji interfejsu użytkownika na wszystkich platformach, zobacz Ułatwienia dostępu.

W tym temacie opisano sposób implementowania dostawcy automatyzacji interfejsu użytkownika po stronie serwera dla niestandardowej kontrolki uruchamianej w aplikacji WPF. WPF obsługuje automatyzację interfejsu użytkownika za pomocą drzewa obiektów automatyzacji równorzędnej, które są równoległe do drzewa elementów interfejsu użytkownika. Testy kodu i aplikacji, które oferują funkcje ułatwień dostępu, mogą bezpośrednio korzystać z obiektów automatyzacji (dla kodu wewnętrznego) lub za pośrednictwem uogólnionego interfejsu udostępnianego przez Automatyzację Interfejsu Użytkownika.

Klasy równorzędne automatyzacji

Kontrolki WPF obsługują automatyzację interfejsu użytkownika za pomocą drzewa klas równorzędnych, które pochodzą z AutomationPeer. Zgodnie z konwencją nazwy klas równorzędnych zaczynają się od nazwy klasy sterującej i kończą się ciągiem "AutomationPeer". Na przykład ButtonAutomationPeer jest klasą równorzędną klasy kontrolki Button. Klasy równorzędne z grubsza odpowiadają typom kontrolek automatyzacji UI, ale są specyficzne dla elementów WPF. Kod automatyzacji, który uzyskuje dostęp do aplikacji WPF za pośrednictwem interfejsu automatyzacji UI, nie korzysta bezpośrednio z elementów wspomagających automatyzację, ale kod automatyzacji w tej samej przestrzeni procesowej może korzystać z nich bezpośrednio.

Wbudowane klasy automatyzacji Peer

Elementy implementują klasę równorzędną automatyzacji, jeśli akceptują działanie interfejsu od użytkownika lub zawierają informacje wymagane przez użytkowników aplikacji czytnika zawartości ekranu. Nie wszystkie elementy wizualne WPF mają partnerów automatyzacji. Przykłady klas implementujących równorzędne automatyzacji to Button, TextBoxi Label. Przykłady klas, które nie implementują elementów równorzędnych automatyzacji, to klasy, które pochodzą z Decorator, takie jak Border, i to również klasy oparte na Panel, takie jak Grid i Canvas.

Klasa podstawowa Control nie ma odpowiedniej klasy równorzędnej. Jeśli potrzebujesz klasy równorzędnej odpowiadającej kontrolce niestandardowej pochodzącej z Control, należy utworzyć niestandardową klasę równorzędną z FrameworkElementAutomationPeer.

Zagadnienia dotyczące zabezpieczeń dla pochodnych elementów równorzędnych

Elementy równorzędne automatyzacji muszą działać w środowisku częściowo zaufanym. Kod w zestawie UIAutomationClient nie jest skonfigurowany do uruchamiania w środowisku o częściowym zaufaniu, a kod elementu równorzędnego automatyzacji nie powinien odwoływać się do tego zestawu. Zamiast tego należy użyć klas w zestawie UIAutomationTypes. Na przykład należy użyć klasy AutomationElementIdentifiers z zestawu UIAutomationTypes, który odpowiada klasie AutomationElement w zestawie UIAutomationClient. Można bezpiecznie odwołać się do zestawu UIAutomationTypes w kodzie automatyzacji peer.

Nawigacja poprzez rówieśników

Po zlokalizowaniu równorzędnego elementu automatyzacji, kod procesu może nawigować po drzewie równorzędnym, wywołując metody GetChildren i GetParent obiektu. Nawigacja między elementami WPF w kontrolce jest obsługiwana przez implementację metody GetChildrenCore elementu równorzędnego. System automatyzacji interfejsu użytkownika wywołuje tę metodę w celu utworzenia drzewa podelementów zawartych w kontrolce; na przykład elementy listy w polu listy. Domyślna metoda UIElementAutomationPeer.GetChildrenCore przechodzi przez drzewo wizualne elementów w celu utworzenia drzewa elementów partnerskich dla automatyzacji. Kontrolki niestandardowe zastępują tę metodę, aby uwidocznić elementy podrzędne klientom automatyzacji, zwracając elementy automatyzacji, które przekazują informacje lub umożliwiają użytkownikowi interakcję.

Dostosowania w pochodnym elemencie równorzędnym

Wszystkie klasy pochodzące z UIElement i ContentElement zawierają chronioną metodę wirtualną OnCreateAutomationPeer. WPF wywołuje OnCreateAutomationPeer, aby uzyskać obiekt automatyzacji równorzędnej dla każdej kontrolki. Kod automatyzacji może używać obiektu równorzędnego do uzyskiwania informacji o charakterystykach i funkcjach kontrolki oraz do symulowania interakcji. Niestandardowa kontrolka obsługująca automatyzację musi zastąpić OnCreateAutomationPeer i zwrócić wystąpienie klasy pochodzącej z AutomationPeer. Jeśli na przykład kontrolka niestandardowa pochodzi z klasy ButtonBase, obiekt zwrócony przez OnCreateAutomationPeer powinien pochodzić z ButtonBaseAutomationPeer.

Podczas implementowania kontrolki niestandardowej należy przesłonić metody "Core" z podstawowej klasy automatyzacji peer, które opisują zachowanie unikatowe i specyficzne dla niestandardowej kontrolki.

Zastąpij metodę OnCreateAutomationPeer

Zastąpi metodę OnCreateAutomationPeer dla kontrolki niestandardowej, aby zwracała obiekt dostawcy, który musi pochodzić bezpośrednio lub pośrednio z AutomationPeer.

Zastąp metodę GetPattern

Równe elementy automatyzacji upraszczają niektóre aspekty implementacji dostawców automatyzacji interfejsu użytkownika po stronie serwera, ale elementy automatyzacji niestandardowej kontroli muszą nadal obsługiwać interfejsy wzorca. Podobnie jak dostawcy nienależący do WPF, elementy partnerskie obsługują wzorce kontrolne, zapewniając implementacje interfejsów w przestrzeni nazw System.Windows.Automation.Provider, takie jak IInvokeProvider. Interfejsy wzorca sterowania mogą być zaimplementowane przez sam obiekt równorzędny lub inny obiekt. Implementacja GetPattern równorzędnika zwraca obiekt, który obsługuje określony wzorzec. Kod automatyzacji interfejsu użytkownika wywołuje metodę GetPattern i określa wartość wyliczenia PatternInterface. Zastąpienie GetPattern powinno zwrócić obiekt implementujący określony wzorzec. Jeśli kontrolka nie ma niestandardowej implementacji wzorca, możesz wywołać implementację podstawowego typu GetPattern, aby pobrać jego implementację lub wartość null, jeśli wzorzec nie jest obsługiwany dla tego typu kontrolki. Na przykład kontrolkę niestandardową NumericUpDown można ustawić na wartość w określonym zakresie, a jej rówieśnicy w automatyzacji interfejsu użytkownika implementowaliby interfejs IRangeValueProvider. W poniższym przykładzie pokazano, jak metoda GetPattern elementu równorzędnego jest zastępowana w celu reagowania na wartość 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

Metoda GetPattern może również określać podelement jako dostawcę wzorca. Poniższy kod pokazuje, jak ItemsControl przekazuje obsługę wzorca przewijania do elementu równorzędnego kontrolki wewnętrznej 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  

Aby określić podelement obsługi wzorca, ten kod pobiera obiekt podelementu, tworzy element równorzędny przy użyciu metody CreatePeerForElement, ustawia właściwość EventsSource nowego elementu równorzędnego do bieżącego elementu równorzędnego i zwraca nowy element równorzędny. Ustawienie EventsSource w podelemencie uniemożliwia wyświetlenie podelementu w drzewie elementów automatyzacji równorzędnej i wyznacza wszystkie zdarzenia zgłaszane przez podelement jako pochodzące z kontroli określonej w EventsSource. Kontrolka ScrollViewer nie jest wyświetlana w drzewie automatyzacji, a zdarzenia przewijania, które generuje, wydają się pochodzić z obiektu ItemsControl.

Nadpisywanie metod "Core"

Kod automatyzacji pobiera informacje o kontrolce, wywołując publiczne metody klasy równorzędnej. Aby podać informacje o kontrolce, zastąpij każdą metodę, której nazwa kończy się ciągiem "Core", gdy implementacja kontrolki różni się od tej udostępnionej przez klasę równorzędną automatyzacji podstawowej. Co najmniej kontrolka musi implementować metody GetClassNameCore i GetAutomationControlTypeCore, jak pokazano w poniższym przykładzie.

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

Implementacja GetAutomationControlTypeCore opisuje kontrolę, zwracając wartość ControlType. Chociaż można zwrócić ControlType.Custom, należy zwrócić jeden z bardziej szczegółowych typów kontrolek, jeśli dokładnie opisuje Twój element sterujący. Wartość zwracana ControlType.Custom wymaga dodatkowej pracy dla dostawcy w celu zaimplementowania automatyzacji interfejsu użytkownika, a produkty klienckie automatyzacji interfejsu użytkownika nie mogą przewidzieć struktury sterowania, interakcji z klawiaturą i możliwych wzorców sterowania.

Zaimplementuj metody IsContentElementCore i IsControlElementCore, aby wskazać, czy kontrolka zawiera zawartość danych, czy spełnia interaktywną rolę w interfejsie użytkownika (lub oba te metody). Domyślnie obie metody zwracają true. Te ustawienia zwiększają użyteczność narzędzi automatyzacji, takich jak czytniki zawartości ekranu, które mogą używać tych metod do filtrowania drzewa automatyzacji. Jeśli metoda GetPattern przenosi obsługę wzorca do równorzędnego podelementu, metoda IsControlElementCore tego równorzędnego podelementu może zwrócić wartość false, aby ukryć go z drzewa automatyzacji. Na przykład przewijanie w ListBox jest obsługiwane przez ScrollViewer, a element równorzędny automatyzacji dla PatternInterface.Scroll jest zwracany przez metodę GetPattern klasy ScrollViewerAutomationPeer, skojarzoną z ListBoxAutomationPeer. W związku z tym metoda IsControlElementCore klasy ScrollViewerAutomationPeer zwraca wartość false, tak że ScrollViewerAutomationPeer nie pojawia się w drzewie automatyzacji.

Twój partner automatyzacji powinien zapewnić odpowiednie wartości domyślne dla kontrolki. Należy pamiętać, że język XAML, który odwołuje się do kontrolki, może zastąpić implementacje równorzędne metod podstawowych przez uwzględnienie atrybutów AutomationProperties. Na przykład poniższy kod XAML tworzy przycisk z dwoma dostosowanymi właściwościami automatyzacji interfejsu użytkownika.

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

Implementacja dostawców wzorców

Interfejsy implementowane przez dostawcę niestandardowego są jawnie deklarowane, jeśli element właścicielski pochodzi bezpośrednio z Control. Na przykład poniższy kod deklaruje element równorzędny dla Control, który implementuje wartość zakresu.

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

Jeśli kontrolka będąca właścicielem pochodzi z określonego typu kontrolki, takiego jak RangeBase, element równorzędny może pochodzić z równoważnej klasy równorzędnej pochodnej. W tym przypadku odpowiednik jest generowany z RangeBaseAutomationPeer, który dostarcza podstawową implementację IRangeValueProvider. Poniższy kod przedstawia deklarację takiego elementu równorzędnego.

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

Aby zapoznać się z przykładową implementacją, zobacz kod źródłowy języka C# lub Visual Basic, który implementuje i używa niestandardowej kontrolki NumericUpDown.

Wywoływanie zdarzeń

Klienci usługi Automation mogą subskrybować zdarzenia automatyzacji. Kontrolki niestandardowe muszą zgłaszać zmiany stanu kontroli przez wywołanie metody RaiseAutomationEvent. Podobnie, gdy wartość właściwości zmieni się, wywołaj metodę RaisePropertyChangedEvent. Poniższy kod pokazuje, jak uzyskać obiekt elementu równorzędnego z poziomu kodu sterującego i wywołać metodę w celu wywołania zdarzenia. W ramach optymalizacji kod określa, czy istnieją odbiorniki dla tego typu zdarzenia. Wywołanie zdarzenia tylko wtedy, gdy są słuchacze, unika niepotrzebnego obciążenia i pomaga w utrzymaniu responsywności kontrolki.

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

Zobacz też