17

假设您有一个触发命令的控件:

<Button Command="New"/>

如果用户双击命令,有没有办法防止命令被触发两次?

编辑:在这种情况下,重要的是我在 WPF 中使用Commanding模型。

似乎每当按下按钮时,都会执行命令。除了禁用或隐藏按钮之外,我没有看到任何防止这种情况的方法。

4

16 回答 16

17

任何包含需要大量处理时间的代码的事件处理程序都可能导致相关按钮的禁用延迟;无论在处理程序中调用禁用代码行的位置。

试试下面的证明,你会发现禁用/启用与事件的注册没有关联。按钮单击事件仍被注册并仍在处理中。

矛盾证明 1

private int _count = 0;
    
private void btnStart_Click(object sender, EventArgs e)
{
    btnStart.Enabled = false;
    
    _count++;
    label1.Text = _count.ToString();

    while (_count < 10)
    {            
        btnStart_Click(sender, e);            
    }           

    btnStart.Enabled = true;
}

矛盾证明 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.ToString();
}
于 2013-05-06T21:22:58.357 回答
13

简单有效地阻止两次、三次和四次点击

<Button PreviewMouseDown="Button_PreviewMouseDown"/>

private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    if (e.ClickCount >= 2)
    {
        e.Handled = true;
    }
}
于 2014-04-02T14:27:40.283 回答
12

我有同样的问题,这对我有用:

<Button>
    <Button.InputBindings>
            <MouseBinding Gesture="LeftClick" Command="New" />
    </Button.InputBindings>
</Button>
于 2011-08-25T20:19:31.187 回答
7

Assuming that WPF Commanding doesn't give you enough control to mess with the click handler, could you put some code in the command handler that remembers the last time the command was executed and exits if it is requested within a given time period? (code example below)

The idea is that if it's a double-click, you'll receive the event twice within milliseconds, so ignore the second event.

Something like: (inside of the Command)


// warning:  I haven't tried 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
}

(note: if it were just a plain old click handler, I'd say follow this advice: http://blogs.msdn.com/oldnewthing/archive/2009/04/29/9574643.aspx )

于 2009-05-08T20:34:44.040 回答
7

我们像这样解决了它......使用异步我们找不到任何其他方法来有效阻止调用此点击的按钮上的额外点击:

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;
        }
    }
}
于 2015-09-30T19:49:48.573 回答
5

您可以使用MVVMLightToolkitEventToCommand中的类来防止这种情况。

处理 Click 事件并将其EventToCommand从您的视图发送到您的视图模型(您可以使用它EventTrigger来执行此操作)。
在您的视图中设置MustToggleIsEnabled="True"并在您的视图模型中实现一个CanExecute()方法。
设置CanExecute()为在命令开始执行时返回 false,并在命令完成时返回 true。

这将在处理命令期间禁用按钮。

于 2011-03-22T13:55:14.500 回答
5

你会认为它就像在命令运行时使用 aCommandCanExecute()返回 false 一样简单。你会错的。即使您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;
}

我怀疑如果这个命令引用了窗口的Dispatcher,并Invoke在它调用时使用OnCanExecuteChanged,它会起作用。

我可以想出几种方法来解决这个问题。一个 JMarsch 的方法:只需跟踪何时Execute被调用,如果在最后几百毫秒内被调用,则无需执行任何操作即可退出。

一种更健壮的方法可能是让Execute方法 start aBackgroundWorker进行实际处理,让CanExecutereturn(!BackgroundWorker.IsBusy)和 raiseCanExecuteChanged在任务完成时。该按钮应CanExecute()Execute()返回后立即重新查询,它将立即执行。

于 2011-08-25T21:24:55.227 回答
4

You could set a flag

bool boolClicked = false;
button_OnClick
{
    if(!boolClicked)
    {
        boolClicked = true;
        //do something
        boolClicked = false;
    }
}
于 2009-05-08T19:15:42.357 回答
3

一个简单而优雅的解决方案是在双击场景中创建第二次单击时的行为禁用反应。这很容易使用:

  <Button Command="New">
          <i:Interaction.Behaviors>
            <behaviors:DisableDoubleClickBehavior />
          </i:Interaction.Behaviors>
  </Button>

行为(更多关于行为 - 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<Button>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.PreviewMouseDoubleClick += AssociatedObjectOnPreviewMouseDoubleClick;
    }

    private void AssociatedObjectOnPreviewMouseDoubleClick(object sender, MouseButtonEventArgs mouseButtonEventArgs)
    {
        mouseButtonEventArgs.Handled = true;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.PreviewMouseDoubleClick -= AssociatedObjectOnPreviewMouseDoubleClick;
        base.OnDetaching();
    }
}
于 2016-11-03T12:43:32.273 回答
1

我正在使用 Xamarin 和 MVVMCross,虽然不是 WPF,但我认为以下解决方案适用,我创建了一个特定于视图模型的解决方案(不处理特定于平台的 UI),我认为它非常方便,使用帮助程序或基类为视图模型创建一个跟踪命令的列表,如下所示:

private readonly List<string> Commands = new List<string>();

        public bool IsCommandRunning(string command)
        {
            return Commands.Any(c => c == command);
        }

        public void StartCommand(string command)
        {
            if (!Commands.Any(c => c == command)) Commands.Add(command);
        }

        public void FinishCommand(string command)
        {
            if (Commands.Any(c => c == command))  Commands.Remove(command);
        }

        public void RemoveAllCommands()
        {
            Commands.Clear();
        }

像这样在动作中添加命令:

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);
                    }
                });
            }
        }

try/finally 只是确保命令始终完成。

通过设置异步操作并进行延迟对其进行测试,第一次点击有效,第二次在条件下返回。

于 2018-10-30T16:48:37.790 回答
1

有同样的问题,通过使用附加行为解决了它。

namespace VLEva.Core.Controls
{
    /// <summary></summary>
    public static class ButtonBehavior
    {
        /// <summary></summary>
        public static readonly DependencyProperty IgnoreDoubleClickProperty = DependencyProperty.RegisterAttached("IgnoreDoubleClick",
                                                                                                                  typeof(bool),
                                                                                                                  typeof(ButtonBehavior),
                                                                                                                  new UIPropertyMetadata(false, OnIgnoreDoubleClickChanged));

        /// <summary></summary>
        public static bool GetIgnoreDoubleClick(Button p_btnButton)
        {
            return (bool)p_btnButton.GetValue(IgnoreDoubleClickProperty);
        }

        /// <summary></summary>
        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;
        }

    }
}

然后直接在 XAML 中通过声明样式将属性设置为 TRUE,这样它就可以立即影响所有按钮。(不要忘记 XAML 命名空间声明)

<Style x:Key="styleBoutonPuff" TargetType="{x:Type Button}">
    <Setter Property="VLEvaControls:ButtonBehavior.IgnoreDoubleClick" Value="True" />
    <Setter Property="Cursor" Value="Hand" />
</Style>
于 2017-04-20T13:02:33.353 回答
0

If your control derives from System.Windows.Forms.Control, you can use the double click event.

If it doesn't derive from System.Windows.Forms.Control, then wire up mousedown instead and confirm the click count == 2 :

private void Button_MouseDown(object sender, MouseButtonEventArgs e)
{
    if (e.ClickCount == 2)
    {
       //Do stuff
    }
 }
于 2009-05-08T19:24:47.717 回答
0

我将按钮绑定到可以触发 Run() 的委托函数:

private const int BUTTON_EVENT_DELAY_MS = 1000; //1 second. Setting this time too quick may allow double and triple clicking if the user is quick.
private bool runIsRunning = false;

private void Run()
{
    try
    {
        if (runIsRunning) //Prevent Double and Triple Clicking etc. We just want to Run run once until its done!
        {
            return;
        }
        runIsRunning = true;

        EventAggregator.GetEvent<MyMsgEvent>().Publish("my string");

        Thread.Sleep(BUTTON_EVENT_DELAY_MS);  
        runIsRunning = false;
    }
    catch  //catch all to reset runIsRunning- this should never happen.
    {
        runIsRunning = false;
    }
}
于 2021-05-14T18:59:35.533 回答
0

这里唯一真正的解决方案是创建一个使用 ConcurrentQueue 命令的 CommandHandler 单例类。命令处理程序需要它自己的处理循环,该循环在第一次按下按钮后启动并在队列为空时结束,这需要在它自己的线程中运行。

然后每个单击处理程序将命令推送到该队列,然后该队列执行该命令。如果同一命令在队列中出现两次,您可以简单地忽略处理它(或执行其他操作)

我看到的这个问题中的其他所有内容都不起作用,因为他们使用非原子操作来检查按钮是否已连续快速按下两次。这可能会失败,因为您可以在设置布尔值/计时器/信号量之前获得双重入口。

于 2021-04-07T05:19:48.240 回答
0

将代码包装在 try-catch-finally 或 try-finally 块中。无论 try 中是否发生任何错误,都将始终调用 finally 语句。

例子

    private Cursor _CursorType;
    // Property to set and get the cursor type
    public Cursor CursorType
    {
      get {return _CursorType; }
      set
      {
        _CursorType = value;
        OnPropertyChanged("CursorType");
      }
    }


    private void ExecutedMethodOnButtonPress()
    {
       try
       {
         CursorType = Cursors.Wait;
         // Run all other code here
       }
       finally
       {
         CursorType = Cursors.Arrow;
       }
    }

注意:CursorType 是 UserControl 或 Window 绑定到的属性

<Window 
Cursor = {Binding Path=CursorType}>
于 2018-11-09T16:21:59.930 回答
-3

这将检查验证是否通过,如果通过则禁用按钮。

private void checkButtonDoubleClick(Button button)
    {
        System.Text.StringBuilder sbValid = new System.Text.StringBuilder();
        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.Attributes.Add("onclick", sbValid.ToString());
    }
于 2009-12-15T21:12:51.143 回答