Wie klicke ich programmgesteuert auf eine Schaltfläche in WPF?

144

button.PerformClick()Gibt es eine Möglichkeit, programmgesteuert auf eine WPF-Schaltfläche zu klicken, da es in WPF keine Methode gibt?

tghoang
quelle

Antworten:

128

WPF verfolgt hier einen etwas anderen Ansatz als WinForms. Anstatt die Automatisierung eines Objekts in die API zu integrieren, haben sie für jedes Objekt, das für die Automatisierung verantwortlich ist, eine separate Klasse. In diesem Fall benötigen Sie die ButtonAutomationPeer, um diese Aufgabe auszuführen.

ButtonAutomationPeer peer = new ButtonAutomationPeer(someButton);
IInvokeProvider invokeProv = peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider;
invokeProv.Invoke();

Hier ist ein Blog-Beitrag zu diesem Thema.

Hinweis: Die IInvokeProviderSchnittstelle ist in der UIAutomationProviderAssembly definiert .

JaredPar
quelle
2
Slick, das war mir nicht bewusst. Könnte für automatisierte Tests sehr nützlich sein. Beachten Sie, dass es zu diesem Link einen Kommentar gibt, der vorschlägt, eine bereitgestellte Factory zu verwenden, um den Automatisierungs-Peer zu erhalten, anstatt selbst einen zu erstellen.
Greg D
7
Danke dafür. Ich hatte Mühe, die richtigen Namespaces in meiner App zu finden, bis ich einen Verweis auf hinzufügte UIAutomationProvider. Dann musste hinzufügenusing System.Windows.Automation.Peers; using System.Windows.Automation.Provider;
SergeantKK
5
Ein Einzeiler für Neigte:((IInvokeProvider) (new ButtonAutomationPeer(someButton).GetPattern(PatternInterface.Invoke)).Invoke();
Danny Beckett
3
Zu beachten ist, dass der Aufruf von Invoke asynchron ist. Das heißt, wenn Sie es in einem Komponententest verwenden und in Ihrer nächsten Zeile das erwartete Ergebnis des Klickens auf die Schaltfläche überprüfen möchten, müssen Sie möglicherweise warten.
Denver
3
Nur als Referenz wird die IInvokeProviderSchnittstelle in der UIAutomationProviderBaugruppe definiert .
Steven Rands
188

Wie JaredPar sagte, können Sie sich auf Josh Smiths Artikel zur Automatisierung beziehen. Wenn Sie jedoch die Kommentare zu seinem Artikel durchsehen, werden Sie eine elegantere Möglichkeit finden, Ereignisse gegen WPF-Steuerelemente auszulösen

someButton.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));

Ich persönlich bevorzuge die oben genannte anstelle von Kollegen aus der Automatisierung.

Denis Vuyka
quelle
26
Die RaiseEvent-Lösung löst nur das Ereignis aus. Der mit dem Button verknüpfte Befehl wird nicht ausgeführt (wie Skanaar sagt)
Eduardo Molteni
4
Wenn Sie XAML verwenden, z. B. <Button Name = "X" Click = "X_Click" />, wird das Ereignis wie gewohnt vom On-Click-Handler abgefangen. +1 von mir!
Metao
4
Ich verwende VB ... bin mir nicht sicher, ob der Code für VB-Verse C # unterschiedlich ist, new RoutedEventArgs(Button.ClickEvent)habe aber bei mir nicht funktioniert. Ich musste benutzen new RoutedEventArgs(Primitives.ButtonBase.ClickEvent). Ansonsten funktioniert super!
BrianVPS
3
Um auf @EduardoMolteni und Skanaar näher einzugehen, verlieren Sie die von Befehlen bereitgestellte IsEnabled-Funktionalität auf diese Weise. Wenn Sie also nicht möchten, dass alle Ihre Ereignisse überprüfen, ob sie aktiviert sind, funktioniert AutomationPeer besser.
Justin Pihony
34

Wenn Sie das Klickereignis aufrufen möchten:

SomeButton.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));

Und wenn Sie möchten, sieht der Knopf so aus, als wäre er gedrückt:

typeof(Button).GetMethod("set_IsPressed", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(SomeButton, new object[] { true });

und danach ungedrückt:

typeof(Button).GetMethod("set_IsPressed", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(SomeButton, new object[] { false });

oder verwenden Sie den ToggleButton

Sonorx
quelle
22
this.PowerButton.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
Mahdi
quelle
5

Eine Möglichkeit, programmgesteuert auf die Schaltfläche zu "klicken", wenn Sie Zugriff auf die Quelle haben, besteht darin, einfach den OnClick-Ereignishandler der Schaltfläche aufzurufen (oder den mit der Schaltfläche verknüpften ICommand auszuführen, wenn Sie die Dinge auf WPF-y-Weise ausführen ).

Warum tust du das? Führen Sie beispielsweise eine Art automatisiertes Testen durch oder versuchen Sie, dieselbe Aktion auszuführen, die die Schaltfläche in einem anderen Codeabschnitt ausführt?

Greg D.
quelle
Es ist nicht die einzige Lösung für mein Problem, aber als ich dies versuchte, stellte ich fest, dass es nicht so einfach ist wie button.PerformClick (), also nur ein bisschen neugierig ... :)
tghoang
Ich musste im selben Projekt auf das Menü eines anderen Fensters klicken, und MyOtherWindow.mnuEntry_Click (Me, New Windows.RoutedEventArgs) hat es getan. Wirklich einfach.
Andrea Antonangeli
4

Wie Greg D sagte, besteht eine Alternative zum AutomationKlicken auf eine Schaltfläche mithilfe des MVVM-Musters (Klickereignis ausgelöst und Befehl ausgeführt) darin, die OnClickMethode mithilfe von Reflection aufzurufen :

typeof(System.Windows.Controls.Primitives.ButtonBase).GetMethod("OnClick", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(button, new object[0]);
Max
quelle
Dies ist eine gute Möglichkeit, ein Ereignis aus einem untergeordneten Element zu verwenden, um einen Befehl auf ein übergeordnetes Element auszulösen.
Cool Blue
2

Wenn Sie das MVVM- Befehlsmuster für die Schaltflächenfunktion verwenden (empfohlene Vorgehensweise), können Sie den Effekt der Schaltfläche auf einfache Weise wie folgt auslösen :

someButton.Command.Execute(someButton.CommandParameter);

Dies verwendet das Command- Objekt, das die Schaltfläche auslöst, und übergibt den von der XAML definierten CommandParameter .

KVKConsultancy
quelle
Ein Hinweis, dies löst nur den Effekt aus, den die Schaltfläche hat, wenn Sie darauf klicken. Es werden keine Klickereignisse in der Nachrichtenversandwarteschlange generiert. Für alle praktischen Fälle ist dies das, was Sie wünschen und unnötige Dispatcher-Ereignisse vermeiden (z. B. leistungsfähiger), aber wenn Sie tatsächlich irgendwie die Dispatcher-Ereignisse benötigen, sind andere oben genannte Lösungen besser geeignet
KVKConsultancy
0

Das Problem mit der Automation API-Lösung besteht darin, dass ein Verweis auf die Framework-Assembly UIAutomationProviderals Projekt- / Paketabhängigkeit erforderlich ist.

Eine Alternative besteht darin, das Verhalten zu emulieren. Im Folgenden gibt es meine erweiterte Lösung, die auch das MVVM-Muster mit seinen gebundenen Befehlen bedingt - implementiert als Erweiterungsmethode :

public static class ButtonExtensions
{
    /// <summary>
    /// Performs a click on the button.<br/>
    /// This is the WPF-equivalent of the Windows Forms method "<see cref="M:System.Windows.Forms.Button.PerformClick" />".
    /// <para>This simulates the same behaviours as the button was clicked by the user by keyboard or mouse:<br />
    /// 1. The raising the ClickEvent.<br />
    /// 2.1. Checking that the bound command can be executed, calling <see cref="ICommand.CanExecute" />, if a command is bound.<br />
    /// 2.2. If command can be executed, then the <see cref="ICommand.Execute(object)" /> will be called and the optional bound parameter is p
    /// </para>
    /// </summary>
    /// <param name="sourceButton">The source button.</param>
    /// <exception cref="ArgumentNullException">sourceButton</exception>
    public static void PerformClick(this Button sourceButton)
    {
        // Check parameters
        if (sourceButton == null)
            throw new ArgumentNullException(nameof(sourceButton));

        // 1.) Raise the Click-event
        sourceButton.RaiseEvent(new RoutedEventArgs(System.Windows.Controls.Primitives.ButtonBase.ClickEvent));

        // 2.) Execute the command, if bound and can be executed
        ICommand boundCommand = sourceButton.Command;
        if (boundCommand != null)
        {
            object parameter = sourceButton.CommandParameter;
            if (boundCommand.CanExecute(parameter) == true)
                boundCommand.Execute(parameter);
        }
    }
}
rittergig
quelle