繁体   English   中英

防止双击双击命令

[英]Prevent double-click from double firing a command

鉴于您有一个触发命令的控件:

<Button Command="New"/>

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

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

似乎只要按下按钮,就会执行命令。 除了禁用或隐藏按钮之外,我看不出有任何方法可以防止这种情况发生。

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

试试下面的证明,你会看到禁用/启用与事件注册没有关联。 按钮点击事件仍然被注册并且仍然被处理。

矛盾证明 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();
}

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

<Button PreviewMouseDown="Button_PreviewMouseDown"/>

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

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

<Button>
    <Button.InputBindings>
            <MouseBinding Gesture="LeftClick" Command="New" />
    </Button.InputBindings>
</Button>

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

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

您可以使用EventToCommand类的MVVMLightToolkit ,以防止这一点。

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

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

您会认为这就像使用Command并在命令运行时使CanExecute()返回 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 ,并且在调用OnCanExecuteChanged时使用了Invoke ,它就会起作用。

我可以想到几种方法来解决这个问题。 JMarsch 的方法是:简单地跟踪Execute何时被调用,如果在过去几百毫秒内被调用,则不做任何事情就退出。

更可靠的方法可能是让Execute方法启动BackgroundWorker来执行实际处理,让CanExecute返回(!BackgroundWorker.IsBusy) ,并在任务完成时引发CanExecuteChanged 按钮应该在Execute()返回后立即重新查询CanExecute() ,它会立即执行。

假设 WPF Commanding 没有给您足够的控制权来干扰单击处理程序,您能否在命令处理程序中放置一些代码来记住上次执行命令的时间并在给定时间段内请求时退出? (下面的代码示例)

这个想法是,如果是双击,您将在几毫秒内收到该事件两次,因此请忽略第二个事件。

类似的东西:(在命令中)


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

(注意:如果它只是一个普通的旧点击处理程序,我会说遵循以下建议: http : //blogs.msdn.com/oldnewthing/archive/2009/04/29/9574643.aspx

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

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

你可以设置一个标志

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

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

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>

我正在使用 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 只是确保命令始终完成。

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

将代码包装在 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}>

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

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

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

我的按钮绑定到一个可以触发 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;
    }
}

如果您的控件派生自 System.Windows.Forms.Control,则可以使用双击事件

如果它不是从 System.Windows.Forms.Control 派生的,则改为连接 mousedown并确认 click count == 2 :

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM