Comment cliquer par programme sur un bouton dans WPF?

144

Puisqu'il n'y a pas de button.PerformClick()méthode dans WPF, existe-t-il un moyen de cliquer sur un bouton WPF par programme?

tghoang
la source

Réponses:

128

WPF adopte ici une approche légèrement différente de celle de WinForms. Au lieu d'avoir l'automatisation d'un objet intégrée à l'API, ils ont une classe distincte pour chaque objet qui est responsable de l'automatiser. Dans ce cas, vous avez besoin du ButtonAutomationPeerpour accomplir cette tâche.

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

Voici un article de blog sur le sujet.

Remarque: l' IInvokeProviderinterface est définie dans l' UIAutomationProviderassemblage.

JaredPar
la source
2
Slick, je n'étais pas au courant. Peut être très utile pour les tests automatisés. Notez qu'il y a un commentaire sur ce lien qui suggère d'utiliser une fabrique fournie pour obtenir le pair d'automatisation au lieu d'en créer un vous-même.
Greg D
7
Merci pour cela. J'ai eu du mal à trouver les espaces de noms corrects dans mon application, jusqu'à ce que j'aie ajouté une référence à UIAutomationProvider. Puis a dû ajouterusing System.Windows.Automation.Peers; using System.Windows.Automation.Provider;
sergeantKK
5
Un one-liner pour les inclinés:((IInvokeProvider) (new ButtonAutomationPeer(someButton).GetPattern(PatternInterface.Invoke)).Invoke();
Danny Beckett
3
Une chose à noter est que l'appel Invoke est asynchrone. Cela signifie que si vous l'utilisez dans un test unitaire et que votre ligne suivante consiste à vérifier le résultat attendu en cliquant sur le bouton, vous devrez peut-être attendre.
denver
3
Juste pour référence, l' IInvokeProviderinterface est définie dans l' UIAutomationProviderassemblage.
Steven Rands
188

Comme JaredPar l'a dit, vous pouvez vous référer à l'article de Josh Smith sur l'automatisation. Cependant, si vous regardez à travers les commentaires de son article, vous trouverez un moyen plus élégant de déclencher des événements contre les contrôles WPF

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

Personnellement, je préfère celui ci-dessus aux pairs d'automatisation.

Denis Vuyka
la source
26
La solution RaiseEvent déclenche uniquement l'événement. Il n'exécute pas la commande associée au bouton (comme le dit Skanaar)
Eduardo Molteni
4
Si vous utilisez XAML, par exemple <Button Name = "X" Click = "X_Click" />, l'événement sera intercepté par le gestionnaire on click comme d'habitude. +1 de moi!
metao
4
J'utilise VB ... je ne sais pas si le code est différent pour les versets VB C #, mais cela new RoutedEventArgs(Button.ClickEvent)n'a pas fonctionné pour moi. J'ai dû utiliser new RoutedEventArgs(Primitives.ButtonBase.ClickEvent). Sinon, ça marche très bien!
BrianVPS
3
Pour élaborer sur @EduardoMolteni et Skanaar, vous perdez la fonctionnalité IsEnabled donnée par les commandes de cette façon, donc à moins que vous ne vouliez que tous vos événements vérifient s'ils sont activés, AutomationPeer fonctionne mieux.
Justin Pihony
34

si vous souhaitez appeler l'événement de clic:

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

Et si vous voulez que le bouton ait l'air d'avoir été enfoncé:

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

et non pressé après cela:

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

ou utilisez le bouton ToggleButton

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

Une façon de "cliquer" par programme sur le bouton, si vous avez accès à la source, consiste simplement à appeler le gestionnaire d'événements OnClick du bouton (ou à exécuter l'ICommand associée au bouton, si vous faites les choses de la manière plus WPF-y ).

Pourquoi fais-tu ça? Faites-vous une sorte de test automatisé, par exemple, ou essayez-vous d'effectuer la même action que le bouton effectue à partir d'une section de code différente?

Greg D
la source
Ce n'est pas la seule solution à mon problème, mais quand j'ai essayé de le faire, j'ai trouvé que ce n'était pas aussi simple que button.PerformClick (), donc juste un peu curieux ... :)
tghoang
J'ai dû cliquer sur le menu d'une autre fenêtre dans le même projet, et MyOtherWindow.mnuEntry_Click (Me, New Windows.RoutedEventArgs) l'a fait. Vraiment simple.
Andrea Antonangeli
4

Comme l'a dit Greg D , je pense qu'une alternative au fait Automationde cliquer sur un bouton à l'aide du modèle MVVM (événement de clic déclenché et commande exécutée) est d'appeler la OnClickméthode en utilisant la réflexion:

typeof(System.Windows.Controls.Primitives.ButtonBase).GetMethod("OnClick", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(button, new object[0]);
Max
la source
C'est un bon moyen d'utiliser un événement d'un élément enfant pour déclencher une commande sur un parent.
Cool Blue
2

Lors de l'utilisation du modèle de commande MVVM pour la fonction Button (pratique recommandée), un moyen simple de déclencher l'effet du Button est le suivant:

someButton.Command.Execute(someButton.CommandParameter);

Cela utilisera l' objet Command que le bouton déclenche et transmettra le CommandParameter défini par le XAML .

Conseil KVK
la source
Une note, cela ne déclenche que l' effet que le bouton aura si vous cliquez dessus. Il ne génère aucun événement de clic dans la file d'attente de distribution des messages. Pour tous les cas pratiques, c'est ce que vous souhaiterez et évitera des événements de répartiteur inutiles (par exemple plus performants), mais si vous avez réellement besoin des événements de répartiteur, d'autres solutions ci-dessus sont plus appropriées
KVKConsultancy
0

Le problème avec la solution d'API Automation est qu'elle nécessitait une référence à l'assembly Framework en UIAutomationProvidertant que dépendance projet / package.

Une alternative consiste à émuler le comportement. Dans ce qui suit, il y a ma solution étendue qui conditionne également le modèle MVVM avec ses commandes liées - implémentées comme méthode d'extension :

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
la source