如何跟踪WPF中的一个按钮上的垃圾邮件点击数量

时间:2022-01-13 15:21:24

I have a button that skips a video on by x seconds. if a user spam clicks that button my video updates over and over again which is an expensive operation. What is the best way to stop a user spamming the button? I am using a routed ui command and want to add up the seconds and do 1 operation. Is a delay timer best practice here? delay the operation for 10ms and reset the delay on every click? or is there something built into wpf that can help?

我有一个按钮,可以跳过视频x秒。如果一个用户垃圾邮件点击这个按钮,我的视频更新一次又一次,这是一个昂贵的操作。阻止用户向按钮发送垃圾邮件的最好方法是什么?我正在使用一个路由的ui命令,并希望添加秒数并执行一个操作。延迟计时器在这里是最佳实践吗?延迟操作10ms并在每次点击时重置延迟?或者wpf里面有什么东西可以帮助你吗?

UPDATE: I would like to track the number of clicks a user is making during the spam click of the button

更新:我想跟踪用户在垃圾邮件点击按钮期间点击的次数

4 个解决方案

#1


0  

I really hope the async way works, while we were trying that out i created a solution, feel free to tell me all i did wrong and any bad practices.

我真的希望异步方式能够工作,当我们尝试时,我创建了一个解决方案,请随意告诉我我所做的所有错误和任何不好的实践。

I decided to use dispatcher timer for this even though i didn't really want to. couldn't find any better practices online.

我决定使用dispatcher计时器,尽管我并不想这么做。在网上找不到更好的方法。

private TimeSpan overallSkipSpeed = TimeSpan.Zero;
private readonly TimeSpan Interval = TimeSpan.FromMilliseconds(400);
private DispatcherTimer _dispatcherTimer;
private TimeSpan _time;

// Execute command function
public void ExecuteClickCommand()
{
    // If the timer isn't going create and start it
    if (_dispatcherTimer == null)
    {
        overallSkipSpeed = TimeSpanToModifyBy(skipSpeed, skipForward);
        _time = Interval;

        _dispatcherTimer = new DispatcherTimer(Interval, DispatcherPriority.Normal, Tick, Application.Current.Dispatcher);
        _dispatcherTimer.Start();
    }
    else // If the timer is going reset to interval
    {
        // THIS IS WHERE I ADDED MY SKIP VALUES TOGETHER
        // So value from last click + value from this click

        _dispatcherTimer.Stop();
        _time = Interval;
        _dispatcherTimer.Start();
    }
}

// Method to run when timer ticks over
private void Tick(object sender, EventArgs eventArgs)
{
    // if the timer has reached below zero
    if (_time <= TimeSpan.Zero)
    {
        _dispatcherTimer.Stop();
        _dispatcherTimer = null;
        _time = TimeSpan.FromSeconds(0);

        // HERE IS WHERE WE CAN NOW SKIP VIDEO BY 
        // THE SKIP SPEED WE HAVE ACCUMULATED   
    }
    else
    {
        _time = _time.Add(-Interval);
    }
}

#2


0  

I have gone one step further with this and created my own command.

我更进一步,创造了我自己的命令。

This command works like a relay command but will delay if you set a delay time span in initialisation. you can retrieve the number of times it was clicked in your execute method.

此命令类似于中继命令,但如果在初始化中设置了延迟时间跨度,则会延迟。您可以检索它在执行方法中被单击的次数。

Initialise the command:

初始化命令:

ICommand DelayedClickCommand = new DelayedCommand(ExecuteDelayedClickCommand, TimeSpan.FromMilliseconds(200));

Create an execute method and retrive the amount of times clicked:

创建一个执行方法,并获取点击次数:

    private void ExecuteClickCommand()
    {
        TimesClicked = ((DelayedCommand)ClickCommand).TimesClicked;
    }

and here is the command class:

下面是命令类:

public class DelayedCommand : ICommand
{
    private readonly Action _methodToExecute;
    private readonly Func<bool> _canExecuteEvaluator;
    private readonly DispatcherTimer _dispatcherTimer;

    public int TimesClicked;

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }


    /// <summary>
    /// A command to stop the spamming of the <see cref="Execute"/> method
    /// </summary>
    /// <param name="methodToExecute">Method to run when command executes</param>
    /// <param name="canExecuteEvaluator">Method used to determine if the command can execute</param>
    /// <param name="delayTime">The cool down period required between click execution</param>
    public DelayedCommand(Action methodToExecute, Func<bool> canExecuteEvaluator, TimeSpan delayTime)
    {
        _methodToExecute = methodToExecute;
        _canExecuteEvaluator = canExecuteEvaluator;

        _dispatcherTimer = new DispatcherTimer(delayTime, DispatcherPriority.Normal, Callback, Application.Current.Dispatcher);
    }

    /// <summary>
    /// A command to stop the spamming of the <see cref="Execute"/> method
    /// when no <see cref="CanExecute"/> method is required
    /// </summary>
    /// <param name="methodToExecute">Method to run when command executes</param>
    /// <param name="delayTime">The cool down period required between click execution</param>
    public DelayedCommand(Action methodToExecute, TimeSpan delayTime)
        : this(methodToExecute, null, delayTime)
    {
    }

    /// <summary>
    /// A command when only a <see cref="Execute"/> method is needed
    /// </summary>
    /// <param name="methodToExecute">Method to run when command executes</param>
    public DelayedCommand(Action methodToExecute)
        : this(methodToExecute, null, TimeSpan.Zero)
    {
    }

    /// <summary>
    /// A command taking a <see cref="Execute"/> Method and a <see cref="CanExecute"/> method
    /// </summary>
    /// <param name="methodToExecute">Method to run when command executes</param>
    /// <param name="canExecuteEvaluator">Method used to determine if the command can execute</param>
    public DelayedCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
        : this(methodToExecute, canExecuteEvaluator, TimeSpan.Zero)
    {
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecuteEvaluator == null)
        {
            return true;
        }

        return _canExecuteEvaluator.Invoke();
    }

    public void Execute(object parameter)
    {
        if (!_dispatcherTimer.IsEnabled)
            TimesClicked = 0;

        TimesClicked++;

        _dispatcherTimer?.Stop();
        _dispatcherTimer?.Start();
    }

    private void Callback(object sender, EventArgs eventArgs)
    {
        _dispatcherTimer.Stop();
        _methodToExecute.Invoke();
    }
}

Note: that when you spam click this command execute will not run untill 200ms after the last click was performed, giving a lagging effect. I have added a sample project to git hub and will add better commands for this question on there

注意:当你在最后一次点击后,点击这个命令执行将不会运行直到200ms,这将会产生一个滞后的效果。我已经向git hub添加了一个示例项目,并将为这个问题添加更好的命令

https://github.com/sgreaves1/DelayedCommands

https://github.com/sgreaves1/DelayedCommands

#3


0  

Guess i'm the laziest person here...

我猜我是这里最懒的人……

public class PostponeCommand : ICommand
    {
        private TimeSpan _delay;
        private Action<object> _command;
        private CancellationTokenSource _cancellation;

        public PostponeCommand(Action<object> command, int delayMs)
        {
            this._command = command;
            this._delay = TimeSpan.FromMilliseconds(delayMs);
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public async void Execute(object parameter)
        {
            _cancellation?.Cancel();
            _cancellation = new CancellationTokenSource();
            try
            {
                await Task.Delay(_delay, _cancellation.Token);
                _command?.Invoke(parameter);
            }
            catch (TaskCanceledException ex)
            {
                // canceled
            }
        }

        public event EventHandler CanExecuteChanged;
    }

#4


0  

Not sure about build-in Command ability to do this, but you can do it with delay (updated based on comments):

不确定内建的命令能力是否可以做到这一点,但是可以延迟(根据评论更新):

private int spamCount = 0;

private int delayValue = 0;

private object isHoldedLock = new object(); 

private bool isHolded = false;

public bool CanProceed(int delay, Action updateVideo)
{
    lock (this.isHoldedLock)
    {
        if (this.isHolded)
        {
            this.spamCount++;

            this.delayValue = delay;

            return false;
        }

        this.isHolded = true;
        this.delayValue = delay;

        Task.Run(async () =>
        {
            while (this.delayValue > 0)
            {
                await Task.Delay(100);

                this.delayValue -= 100;
            }

            updateVideo();

            lock (this.isHoldedLock)
            {
                this.isHolded = false;
            }
        });

        return true;
    }
}

Process/reset spamCount value inside SkipVideo any way you need.

在SkipVideo中以任何你需要的方式处理/重置spamCount值。

And using in your command handler:

在您的命令处理程序中使用:

private void InvokedFromCommand()
{
    if (CanProceed(1000, SkipVideo()))
    {
        // SkipVideo();
    }
}

#1


0  

I really hope the async way works, while we were trying that out i created a solution, feel free to tell me all i did wrong and any bad practices.

我真的希望异步方式能够工作,当我们尝试时,我创建了一个解决方案,请随意告诉我我所做的所有错误和任何不好的实践。

I decided to use dispatcher timer for this even though i didn't really want to. couldn't find any better practices online.

我决定使用dispatcher计时器,尽管我并不想这么做。在网上找不到更好的方法。

private TimeSpan overallSkipSpeed = TimeSpan.Zero;
private readonly TimeSpan Interval = TimeSpan.FromMilliseconds(400);
private DispatcherTimer _dispatcherTimer;
private TimeSpan _time;

// Execute command function
public void ExecuteClickCommand()
{
    // If the timer isn't going create and start it
    if (_dispatcherTimer == null)
    {
        overallSkipSpeed = TimeSpanToModifyBy(skipSpeed, skipForward);
        _time = Interval;

        _dispatcherTimer = new DispatcherTimer(Interval, DispatcherPriority.Normal, Tick, Application.Current.Dispatcher);
        _dispatcherTimer.Start();
    }
    else // If the timer is going reset to interval
    {
        // THIS IS WHERE I ADDED MY SKIP VALUES TOGETHER
        // So value from last click + value from this click

        _dispatcherTimer.Stop();
        _time = Interval;
        _dispatcherTimer.Start();
    }
}

// Method to run when timer ticks over
private void Tick(object sender, EventArgs eventArgs)
{
    // if the timer has reached below zero
    if (_time <= TimeSpan.Zero)
    {
        _dispatcherTimer.Stop();
        _dispatcherTimer = null;
        _time = TimeSpan.FromSeconds(0);

        // HERE IS WHERE WE CAN NOW SKIP VIDEO BY 
        // THE SKIP SPEED WE HAVE ACCUMULATED   
    }
    else
    {
        _time = _time.Add(-Interval);
    }
}

#2


0  

I have gone one step further with this and created my own command.

我更进一步,创造了我自己的命令。

This command works like a relay command but will delay if you set a delay time span in initialisation. you can retrieve the number of times it was clicked in your execute method.

此命令类似于中继命令,但如果在初始化中设置了延迟时间跨度,则会延迟。您可以检索它在执行方法中被单击的次数。

Initialise the command:

初始化命令:

ICommand DelayedClickCommand = new DelayedCommand(ExecuteDelayedClickCommand, TimeSpan.FromMilliseconds(200));

Create an execute method and retrive the amount of times clicked:

创建一个执行方法,并获取点击次数:

    private void ExecuteClickCommand()
    {
        TimesClicked = ((DelayedCommand)ClickCommand).TimesClicked;
    }

and here is the command class:

下面是命令类:

public class DelayedCommand : ICommand
{
    private readonly Action _methodToExecute;
    private readonly Func<bool> _canExecuteEvaluator;
    private readonly DispatcherTimer _dispatcherTimer;

    public int TimesClicked;

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }


    /// <summary>
    /// A command to stop the spamming of the <see cref="Execute"/> method
    /// </summary>
    /// <param name="methodToExecute">Method to run when command executes</param>
    /// <param name="canExecuteEvaluator">Method used to determine if the command can execute</param>
    /// <param name="delayTime">The cool down period required between click execution</param>
    public DelayedCommand(Action methodToExecute, Func<bool> canExecuteEvaluator, TimeSpan delayTime)
    {
        _methodToExecute = methodToExecute;
        _canExecuteEvaluator = canExecuteEvaluator;

        _dispatcherTimer = new DispatcherTimer(delayTime, DispatcherPriority.Normal, Callback, Application.Current.Dispatcher);
    }

    /// <summary>
    /// A command to stop the spamming of the <see cref="Execute"/> method
    /// when no <see cref="CanExecute"/> method is required
    /// </summary>
    /// <param name="methodToExecute">Method to run when command executes</param>
    /// <param name="delayTime">The cool down period required between click execution</param>
    public DelayedCommand(Action methodToExecute, TimeSpan delayTime)
        : this(methodToExecute, null, delayTime)
    {
    }

    /// <summary>
    /// A command when only a <see cref="Execute"/> method is needed
    /// </summary>
    /// <param name="methodToExecute">Method to run when command executes</param>
    public DelayedCommand(Action methodToExecute)
        : this(methodToExecute, null, TimeSpan.Zero)
    {
    }

    /// <summary>
    /// A command taking a <see cref="Execute"/> Method and a <see cref="CanExecute"/> method
    /// </summary>
    /// <param name="methodToExecute">Method to run when command executes</param>
    /// <param name="canExecuteEvaluator">Method used to determine if the command can execute</param>
    public DelayedCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
        : this(methodToExecute, canExecuteEvaluator, TimeSpan.Zero)
    {
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecuteEvaluator == null)
        {
            return true;
        }

        return _canExecuteEvaluator.Invoke();
    }

    public void Execute(object parameter)
    {
        if (!_dispatcherTimer.IsEnabled)
            TimesClicked = 0;

        TimesClicked++;

        _dispatcherTimer?.Stop();
        _dispatcherTimer?.Start();
    }

    private void Callback(object sender, EventArgs eventArgs)
    {
        _dispatcherTimer.Stop();
        _methodToExecute.Invoke();
    }
}

Note: that when you spam click this command execute will not run untill 200ms after the last click was performed, giving a lagging effect. I have added a sample project to git hub and will add better commands for this question on there

注意:当你在最后一次点击后,点击这个命令执行将不会运行直到200ms,这将会产生一个滞后的效果。我已经向git hub添加了一个示例项目,并将为这个问题添加更好的命令

https://github.com/sgreaves1/DelayedCommands

https://github.com/sgreaves1/DelayedCommands

#3


0  

Guess i'm the laziest person here...

我猜我是这里最懒的人……

public class PostponeCommand : ICommand
    {
        private TimeSpan _delay;
        private Action<object> _command;
        private CancellationTokenSource _cancellation;

        public PostponeCommand(Action<object> command, int delayMs)
        {
            this._command = command;
            this._delay = TimeSpan.FromMilliseconds(delayMs);
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public async void Execute(object parameter)
        {
            _cancellation?.Cancel();
            _cancellation = new CancellationTokenSource();
            try
            {
                await Task.Delay(_delay, _cancellation.Token);
                _command?.Invoke(parameter);
            }
            catch (TaskCanceledException ex)
            {
                // canceled
            }
        }

        public event EventHandler CanExecuteChanged;
    }

#4


0  

Not sure about build-in Command ability to do this, but you can do it with delay (updated based on comments):

不确定内建的命令能力是否可以做到这一点,但是可以延迟(根据评论更新):

private int spamCount = 0;

private int delayValue = 0;

private object isHoldedLock = new object(); 

private bool isHolded = false;

public bool CanProceed(int delay, Action updateVideo)
{
    lock (this.isHoldedLock)
    {
        if (this.isHolded)
        {
            this.spamCount++;

            this.delayValue = delay;

            return false;
        }

        this.isHolded = true;
        this.delayValue = delay;

        Task.Run(async () =>
        {
            while (this.delayValue > 0)
            {
                await Task.Delay(100);

                this.delayValue -= 100;
            }

            updateVideo();

            lock (this.isHoldedLock)
            {
                this.isHolded = false;
            }
        });

        return true;
    }
}

Process/reset spamCount value inside SkipVideo any way you need.

在SkipVideo中以任何你需要的方式处理/重置spamCount值。

And using in your command handler:

在您的命令处理程序中使用:

private void InvokedFromCommand()
{
    if (CanProceed(1000, SkipVideo()))
    {
        // SkipVideo();
    }
}