Empêcher le double-clic de double lancer une commande

Étant donné que vous avez un contrôle qui déclenche une commande:

Existe-t-il un moyen d’empêcher le lancement de la commande deux fois si l’utilisateur double-clique dessus?

EDIT: Ce qui est important dans ce cas, c’est que j’utilise le modèle Commandant dans WPF.

Il semble que chaque fois que vous appuyez sur le bouton, la commande est exécutée. Je ne vois aucun moyen d’empêcher cela en plus de désactiver ou de cacher le bouton.

Peut-être le bouton devrait-il être désactivé après le premier clic et jusqu’à la fin du traitement?

La réponse cochée à cette question, soumise par vidalsasoon, est fausse et elle est fausse pour toutes les manières dont cette même question a été posée.

Il est possible que tout gestionnaire d’événements contenant du code qui nécessite un temps de traitement important puisse retarder la désactivation du bouton concerné; indépendamment de l’endroit où la ligne de code de désactivation est appelée dans le gestionnaire.

Essayez les preuves ci-dessous et vous verrez que désactiver / activer n’a aucune corrélation avec l’enregistrement des événements. L’événement clic sur le bouton est toujours enregistré et est toujours géré.

Preuve par contradiction 1

 private int _count = 0; private void btnStart_Click(object sender, EventArgs e) { btnStart.Enabled = false; _count++; label1.Text = _count.ToSsortingng(); while (_count < 10) { btnStart_Click(sender, e); } btnStart.Enabled = true; } 

Preuve par Contradition 2

 private void form1_load(object sender, EventArgs e) { btnTest.Enabled = false; } private void btnStart_Click(object sender, EventArgs e) { btnTest.Enabled = false; btnTest_click(sender, e); btnTest_click(sender, e); btnTest_click(sender, e); btnTest.Enabled = true; } private int _count = 0; private void btnTest_click(object sender, EventArgs e) { _count++; label1.Text = _count.ToSsortingng(); } 

Simple et efficace pour bloquer les doubles, sortingples et quadruples clics

  private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e) { if (e.ClickCount >= 2) { e.Handled = true; } } 

J’ai eu le même problème et cela a fonctionné pour moi:

  

Nous l’avons résolu comme ceci … avec async, nous n’avons pas pu trouver un autre moyen de bloquer efficacement les clics supplémentaires sur le bouton qui appelle ce clic:

 private SemaphoreSlim _lockMoveButton = new SemaphoreSlim(1); private async void btnMove_Click(object sender, RoutedEventArgs e) { var button = sender as Button; if (_lockMoveButton.Wait(0) && button != null) { try { button.IsEnabled = false; } finally { _lockMoveButton.Release(); button.IsEnabled = true; } } } 

En supposant que WPF Command ne vous permette pas de contrôler suffisamment le gestionnaire de clics, pouvez-vous insérer du code dans le gestionnaire de commandes qui mémorise la dernière exécution de la commande et quitter si elle est demandée dans un délai donné? (exemple de code ci-dessous)

L’idée est que s’il s’agit d’un double-clic, vous recevrez l’événement deux fois en quelques millisecondes, alors ignorez le deuxième événement.

Quelque chose comme: (à l’intérieur de la commande)

// warning: I haven't sortinged compiling this, but it should be pretty close DateTime LastInvoked = DateTime.MinDate; Timespan InvokeDelay = Timespan.FromMilliseconds(100); { if(DateTime.Now - LastInvoked <= InvokeDelay) return; // do your work }
// warning: I haven't sortinged compiling this, but it should be pretty close DateTime LastInvoked = DateTime.MinDate; Timespan InvokeDelay = Timespan.FromMilliseconds(100); { if(DateTime.Now - LastInvoked <= InvokeDelay) return; // do your work } 

(Remarque: s'il s'agissait simplement d'un ancien gestionnaire de clics, je vous conseillerais de suivre ce conseil: http://blogs.msdn.com/oldnewthing/archive/2009/04/29/9574643.aspx )

Vous pouvez utiliser la classe EventToCommand dans le MVVMLightToolkit pour empêcher cela.

Gérez l’événement Click et envoyez-le via EventToCommand de votre vue vers votre modèle de vue (vous pouvez utiliser EventTrigger pour le faire).
Définissez MustToggleIsEnabled="True" dans votre vue et implémentez une méthode CanExecute() dans votre viewmodel.
Définissez CanExecute() pour renvoyer false lorsque la commande commence à s’exécuter et revenir à true lorsque la commande est terminée.

Cela désactivera le bouton pendant toute la durée du traitement de la commande.

Vous penseriez que ce serait aussi simple que d’utiliser une Command et de faire en sorte que CanExecute() renvoie false pendant l’exécution de la commande. Vous auriez tort. Même si vous soulevez explicitement CanExecuteChanged :

 public class TestCommand : ICommand { public void Execute(object parameter) { _CanExecute = false; OnCanExecuteChanged(); Thread.Sleep(1000); Console.WriteLine("Executed TestCommand."); _CanExecute = true; OnCanExecuteChanged(); } private bool _CanExecute = true; public bool CanExecute(object parameter) { return _CanExecute; } private void OnCanExecuteChanged() { EventHandler h = CanExecuteChanged; if (h != null) { h(this, EventArgs.Empty); } } public event EventHandler CanExecuteChanged; } 

Je suppose que si cette commande comportait une référence au Dispatcher la fenêtre et utilisait Invoke lorsqu’elle appelait OnCanExecuteChanged , cela fonctionnerait.

Je peux penser à deux façons de résoudre ce problème. L’approche de JMarsch est la suivante: suivre simplement le moment où Execute est appelé et renflouer sans rien faire s’il a été appelé au cours des dernières centaines de millisecondes.

Une méthode plus robuste pourrait consister à laisser la méthode Execute démarrer un BackgroundWorker pour effectuer le traitement réel, renvoyer CanExecute (!BackgroundWorker.IsBusy) et CanExecuteChanged lorsque la tâche est terminée. Le bouton doit requery CanExecute() dès CanExecute() retourne, ce qu’il fera instantanément.

Vous pouvez définir un drapeau

 bool boolClicked = false; button_OnClick { if(!boolClicked) { boolClicked = true; //do something boolClicked = false; } } 

Une solution simple et élégante consiste à créer une réaction désactivant le comportement au deuxième clic dans le scénario du double-clic. C’est assez facile à utiliser:

   

Comportement (plus sur les comportements – https://www.jayway.com/2013/03/20/behaviors-in-wpf-introduction/ )

 using System.Windows.Controls; using System.Windows.Input; using System.Windows.Interactivity; public class DisableDoubleClickBehavior : Behavior 

Si votre contrôle provient de System.Windows.Forms.Control, vous pouvez utiliser l’ événement double-clic .

S’il ne provient pas de System.Windows.Forms.Control, connectez-vous à la souris et confirmez le nombre de clics == 2:

 private void Button_MouseDown(object sender, MouseButtonEventArgs e) { if (e.ClickCount == 2) { //Do stuff } } 

Avait le même problème, résolu en utilisant le comportement attaché.

 namespace VLEva.Core.Controls { ///  public static class ButtonBehavior { ///  public static readonly DependencyProperty IgnoreDoubleClickProperty = DependencyProperty.RegisterAttached("IgnoreDoubleClick", typeof(bool), typeof(ButtonBehavior), new UIPropertyMetadata(false, OnIgnoreDoubleClickChanged)); ///  public static bool GetIgnoreDoubleClick(Button p_btnButton) { return (bool)p_btnButton.GetValue(IgnoreDoubleClickProperty); } ///  public static void SetIgnoreDoubleClick(Button p_btnButton, bool value) { p_btnButton.SetValue(IgnoreDoubleClickProperty, value); } static void OnIgnoreDoubleClickChanged(DependencyObject p_doDependencyObject, DependencyPropertyChangedEventArgs e) { Button btnButton = p_doDependencyObject as Button; if (btnButton == null) return; if (e.NewValue is bool == false) return; if ((bool)e.NewValue) btnButton.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(btnButton_PreviewMouseLeftButtonDown); else btnButton.PreviewMouseLeftButtonDown -= btnButton_PreviewMouseLeftButtonDown; } static void btnButton_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (e.ClickCount >= 2) e.Handled = true; } } } 

puis définissez simplement la propriété sur TRUE, soit directement dans XAML, en déclarant un style afin qu’il affecte tous vos boutons à la fois. (n’oubliez pas la déclaration d’espace de noms XAML)

  

J’utilise Xamarin et MVVMCross, bien que n’étant pas WPF, je pense que la solution suivante s’applique, j’ai créé une solution spécifique à Viewmodel (ne traite pas l’interface utilisateur spécifique à la plate-forme), ce qui me semble très pratique, à l’aide d’un assistant ou d’une classe de base. pour le modèle de vue, créez une liste qui garde une trace des commandes, quelque chose comme ceci:

 private readonly List Commands = new List(); public bool IsCommandRunning(ssortingng command) { return Commands.Any(c => c == command); } public void StartCommand(ssortingng command) { if (!Commands.Any(c => c == command)) Commands.Add(command); } public void FinishCommand(ssortingng command) { if (Commands.Any(c => c == command)) Commands.Remove(command); } public void RemoveAllCommands() { Commands.Clear(); } 

Ajoutez la commande dans l’action comme ceci:

 public IMvxCommand MyCommand { get { return new MvxCommand(async() => { var command = nameof(MyCommand); if (IsCommandRunning(command)) return; try { StartCommand(command); await Task.Delay(3000); //click the button several times while delay } finally { FinishCommand(command); } }); } } 

L’essai / enfin assure simplement que la commande est toujours terminée.

Testé en définissant l’action asynchrone et en faisant un délai, le premier tap fonctionne, le second retourne dans l’état.

Ceci vérifie si la validation est réussie et si elle le fait, désactive le bouton.

 private void checkButtonDoubleClick(Button button) { System.Text.SsortingngBuilder sbValid = new System.Text.SsortingngBuilder(); sbValid.Append("if (typeof(Page_ClientValidate) == 'function') { "); sbValid.Append("if (Page_ClientValidate() == false) { return false; }} "); sbValid.Append("this.value = 'Please wait...';"); sbValid.Append("this.disabled = true;"); sbValid.Append(this.Page.ClientScript.GetPostBackEventReference(button, "")); sbValid.Append(";"); button.Atsortingbutes.Add("onclick", sbValid.ToSsortingng()); }