DispatcherTimer with MVVM in WPF
My speciality is backend, but sometimes I have to do frontend as well. I don't really like it, but I guess that's life.
Sometimes I even have to do frontend with ancient technologies like WPF. Again, I guess that's life.
So a colleague of mine came to me wanting to add WindowsBase to our class library that only contained out ViewModels in a WPF application. When I asked why, he said he wanted to use DispatcherTimer to update a value regularly in the VM and bind to it in the View.
We debated for a while whether this was a valid reason to add the WindowsBase.dll and whether it is allowed to use a DispatcherTimer in the VM. We finally decided we should do something else.
So what do you do when you have a component that you want to abstract away in a layer where its implementation details are neither relevant nor accessible? You use interfaces :) So I created an interface with the DispatcherTimer functionality in mind in the class library project of the VMs:
public interface ITimer
{
TimeSpan Interval { get; set; }
bool IsEnabled { get; set; }
void Start();
void Stop();
event EventHandler Tick;
}
And then created an implementation specific for WPF and DispatcherTimer in the WPF project:
public class Timer : ITimer
{
private readonly DispatcherTimer internalTimer;
public Timer()
{
internalTimer = new DispatcherTimer();
}
public bool IsEnabled
{
get => internalTimer.IsEnabled;
set => internalTimer.IsEnabled = value;
}
public Timespan Interval
{
get => internalTimer.Interval;
set => internalTimer.Interval = value;
}
public void Start() => internalTimer.Start();
public void Stop() => internalTimer.Stop();
public event EventHandler Tick
{
add => internalTimer.Tick += value;
remove => internalTimer.Tick -= value;
}
}
Finally, in order for VM-s to be able to use the ITimer service, I created a factory interface in the VM library:
public interface ITimerFactory
{
ITimer CreateTimer();
}
And again the WPF project, an implementation for the factory that creates an instance of the concrete, DispatcherTimer based implementation:
public class TimerFactory : ITimerFactory
{
public ITimer CreateTimer() => new Timer();
}
And now the VMs that want to use the timer can require an instance of the factory interface via dependency injection in their constructor. You can wire the interface to the implementation with your favorite DI container (mine's Autofac), and then whenever a VM is created, it is passed a factory. And then you can use the factory to create timers and use them whenever you need one:
public class PageViewModel
{
private readonly ITimerFactory timerFactory;
private readonly ITimer timer;
public CarsPageViewModel(ITimerFactory timerFactory)
{
this.timerFactory = timerFactory;
this.timer = timerFactory.CreateTimer();
this.timer.Interval = TimeSpan.FromSeconds(1);
this.timer.Tick += UpdateState();
this.timer.Start();
}
}