In one of my applications, I had to display a timer for all the items in a ListView, i.e., given the last update time of an item, I required to display the time elapsed since the update time and update it after every second. In a classic application, the solution would have been to add a timer control to the host form and update each elapsed time value at each tick (second) of the timer.
However, in WPF/Silverlight, we can easily achieve this by creating a custom control inheriting it from the TextBlock
control. The custom control defines a TimeSpan
property that holds the time elapsed (or time remaining in the case of count down) and binds it to the TextBlock.Text
property. The TimeSpan
is updated every second and thus the Text
property displays the updated value.

Count down and timer demo using the TimerTextBlock control.
The control initializes a static System.Threading.Timer
object with an interval of one second. Also, the control defines a private static event OnTick
that is raised in the timer callback method.
private static event EventHandler OnTick;
private static Timer _UpdateTimer =
new Timer(new TimerCallback(UpdateTimer), null, 1000, 1000);
private static void UpdateTimer(object state)
{
EventHandler onTick = OnTick;
if (onTick != null)
onTick(null, EventArgs.Empty);
}
The control subscribes to the OnTick
event while loading to receive the event, and unsubscribes the event while unloading.
private void Init()
{
Loaded += new RoutedEventHandler(TimerTextBlock_Loaded);
Unloaded += new RoutedEventHandler(TimerTextBlock_Unloaded);
}
void TimerTextBlock_Loaded(object sender, RoutedEventArgs e)
{
Binding binding = new Binding("TimeSpan");
binding.Source = this;
binding.Mode = BindingMode.OneWay;
binding.StringFormat = TimeFormat;
SetBinding(TextProperty, binding);
_UpdateTimeInvoker = new Invoker(UpdateTime);
OnTick += new EventHandler(TimerTextBlock_OnTick);
}
void TimerTextBlock_Unloaded(object sender, RoutedEventArgs e)
{
OnTick -= new EventHandler(TimerTextBlock_OnTick);
}
The TimeSpan
is updated in the OnTick
event handler.
void TimerTextBlock_OnTick(object sender, EventArgs e)
{
Dispatcher.Invoke(_UpdateTimeInvoker);
}
private void UpdateTime()
{
if (IsStarted)
{
TimeSpan step = TimeSpan.FromSeconds(1);
if (IsCountDown)
{
if (TimeSpan >= TimeSpan.FromSeconds(1))
{
TimeSpan -= step;
if (TimeSpan.TotalSeconds <= 0)
{
TimeSpan = TimeSpan.Zero;
IsStarted = false;
NotifyCountDownComplete();
}
}
}
else
{
TimeSpan += step;
}
}
}
private void NotifyCountDownComplete()
{
EventHandler handler = OnCountDownComplete;
if (handler != null)
handler(this, EventArgs.Empty);
}
The control defines additional properties like IsStarted
to start/stop the timer, IsCountDown
to increase or decrease the TimeSpan
at each tick, and TimeFormat
for formatting the elapsed time displayed. The control also defines an event OnCountDownComplete
that is raised when the count down completes, i.e., when it reaches zero.