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