Introduction
I had a case where I was creating undo/redo functionality for a pan and zoom control. For a lot of the actions I could let the action happen and save the details on the stack, but there was the zooming using the mouse scroll button, and the fixed zoom in, zoom out, paging and arrow key actions that I did not think it made sense to store every action. I looked around for a timer that somebody may have created to do this task, and did not find anything, so I created my own.
The Code
The code is pretty basic:
public class KeepAliveTimer
{
private readonly DispatcherTimer _timer;
private DateTime _startTime;
private TimeSpan? _runTime;
public TimeSpan Time { get; set; }
public Action Action { get; set; }
public bool Running { get; private set; }
public KeepAliveTimer(TimeSpan time, Action action)
{
Time = time;
Action = action;
_timer = new DispatcherTimer(DispatcherPriority.ApplicationIdle) { Interval = time };
_timer.Tick += TimerExpired;
}
private void TimerExpired(object sender, EventArgs e)
{
lock (_timer)
{
Running = false;
_timer.Stop();
_runTime = DateTime.UtcNow.Subtract(_startTime);
Action();
}
}
public void Nudge()
{
lock (_timer)
{
if (!Running)
{
_startTime = DateTime.UtcNow;
_runTime = null;
_timer.Start();
Running = true;
}
else
{
_timer.Stop();
_timer.Start();
}
}
}
public TimeSpan GetTimeSpan()
{
return _runTime ?? DateTime.UtcNow.Subtract(_startTime);
}
}
The Nudge
method is called which starts the timer, the Running
property is set to true
and the time is saved. Each time the Nudge
is called, the timer is restarted. If the timer expires, then the timer is stopped, the Action
provided by the user is executed, the Running
property is set to false
, and the time the timer was active is saved.
The GetTimeSpan
method will either get the TimeSpan
for the last run if the timer is not running, or the TimeSpan
since the timer started.
Using the code
Using the timer is very easy:
_timer = new KeepAliveTimer(TimeSpan.FromMilliseconds(500), () =>
{
ResultTextBlock.Text = $"You lasted {_timer.GetTimeSpan()
.TotalSeconds.ToString("0.000")} seconds";
BombImage.Visibility = Visibility.Visible;
});
MouseWheel += (s, e) =>
{
_timer.Nudge();
BombImage.Visibility = Visibility.Collapsed;
IntroductionTextBlock.Visibility = Visibility.Collapsed;
WarningTextBlock.Visibility = Visibility.Visible;
};
Just have to initialize an instance with the TimeSpan
that will indicated how long since the last nudge to wait before the timer is considered expired, and the Action to execute when the timer expires.
The Sample
I just used the concept of requireing the user to continue to use the mouse scroll wheel, and if the user fails to continue to scroll the scroll wheel, a bomb explodes. The user is informed of how long he was scrolling.
This is the initial screen that the user will see. It basiclly tells the user to start using the scroll button on the mouse
This screen shows what happens when the user starts scrolling the mouse scroll wheel. If the user stops...
This screen shows the bomb exploding, telling the user that he has to keep scrolling the scroll button. The message on the bottom tells the user how long they were scrolling.
The last screen shows what it looks like when the user is scrolling. There is no bomb image since the user has not stopped scrolling. The time at the bottom is the time the user kept scrolling the previous time.
History
- 2016-08-26: Initial version
- 2016-08-29: Moved from using DateTime.Now to DateTime.UtcNow as suggested.