Click here to Skip to main content
15,891,372 members
Articles / Desktop Programming / WPF

A WPF/MVVM Countdown Timer

Rate me:
Please Sign up or sign in to vote.
4.95/5 (20 votes)
21 May 2012CPOL8 min read 105.7K   8.5K   52   22
A countdown timer implemented in WPF using the MVVM pattern

Image 1

Introduction

This article describes the construction of a countdown timer application written in C# and WPF, using Laurent Bugnion's MVVMLight Toolkit. This article is based on the work of some previous articles I've written:

As usual most of the code shown in this article is elided, particularly since no-one likes scrolling through five screens worth of XAML to see one line of interest. Please download the source zip file to see the whole thing.

This was written, and more importantly, tested, on a Windows 7, 64-bit box, but uses the .NET 4.0 framework, so should 'just work'!

Requirements and Features

The Countdown Timer is going to be relatively simple:

  • The starting time can be chosen by the user.
  • Notify the user visually (in the application and task bar) and audibly.
  • The application has settings that the user can change.
  • The Windows 7 taskbar icon shows the progress of the timer.

The original motivation for this was that I came across the Pomodoro Technique whilst browsing the web and thought it would be fun to write a countdown timer that could be used for this. It is, in short, a 'getting things done' idea which can be boiled down to:

  • work for 25 minutes
  • break for 5 minutes
  • repeat

So I decided that the default setting for the timer is 25 minutes, and that it should record the number of completed countdowns unobtrusively, should someone wish to use this application in that way.

Choosing the Underlying Timer

We use the WPF DispatchTimer to perform a count. We are not making any guarantees about the accuracy of the timer.

In fact neither does the documentation:

Timers are not guaranteed to execute exactly when the time interval occurs, but they are guaranteed to not execute before the time interval occurs. This is because DispatcherTimer operations are placed on the Dispatcher queue like other operations. When the DispatcherTimer operation executes is dependent on the other jobs in the queue and their priorities.

By leveraging the .NET Framework, we use a TimeSpan that allows us to increment, and importantly, decrement by a specified amount. We then simply decrement our starting value every time the DispatchTimer ticks, until we get a negative TimeSpan, and then we stop.

The code is written in such a way that the TimerModel is just a concrete implementation of ITimerModel and the concrete instantiation of an ITimerModel is generated from a single factory method: in other words, you could write your own ITimerModel derived class instead and update the factory method as required (e.g., use System.Threading.Timer instead).

MVVM

Since this is a WPF application, we will use the MVVM pattern to layout the code.

What does this mean? The application will be divided into:

  • Views - XAML-only layouts that the application uses: i.e., the GUI and all its windows!
  • ViewModels - translate the data between the Views and the Models.
  • Models - the actual code that does the work (and everything else).

If you find this confusing or want to know more, please see another of my articles: WPF/MVVM Quick Start Tutorial.

Application Settings

All applications have settings and this one is no different: to persist the application's settings, we take advantage of the class System.Configuration.ApplicationSettingsBase. This is subclassed for the WPF application when you create it, so you can then just address the application settings programmatically, for example:

C#
_timer.Duration = Properties.Settings.Default.Duration;

where we have created a Duration property.

In the same way that we hide the implementation of the Timer behind a ITimerModel interface, we also use an interface called ISettingsModel, and use a concrete instance called SettingsModel, along with a builder method to retrieve an instance of the class. This gives us the option, as before, to change the settings backing store, to something else in the future (ini file anyone?).

Updating Settings Between Application Versions

To cater for updates to the application, we can use the following method: define UpgradeRequired in our settings, and set to True by default. We then use:

C#
if (Properties.Settings.Default.UpgradeRequired)
{
  Properties.Settings.Default.Upgrade();
  Properties.Settings.Default.UpgradeRequired = false;
  Properties.Settings.Default.Save();
}

to force the upgrade of the application settings only when the UpgradeRequired flag is true. For newly versioned assemblies, all settings take their default values, this code is triggered, and the settings are copied from a previous application version, if it exists, to the new one.

It is worth noting that for this 'trick' to work, you always need to define this field in your application settings in the very first version of your application.

The Views and ViewModels

The application has several views that are all UserControls and hosted in the MainWindow. This means no pop-up dialogs! They are:

  • The main TimerView
  • The SettingsView
  • The AboutView

with the corresponding ViewModels:

  • The TimerViewModel
  • The SettingsViewModel
  • The AboutViewModel

Changing Views and Messaging

As we want to use 'separation of concerns', or 'encapsulation' (if you prefer), we do not want view-models to communicate directly. In order to do this, we simply use message passing, in other words:

330073/Messaging.png

The MVVMLight Toolkit provides us with a singleton Messenger class that we can register message consumers and message producers with. So to raise an 'event' in one view model from another, we simple pass a message, for example:

C#
public class MainViewModel : ViewModelBase
{
    public MainViewModel()
    {
        //  Lastly, listen for messages from other view models.
        Messenger.Default.Register<SimpleMessage>(this, ConsumeMessage);
    }

    private void ConsumeMessage(SimpleMessage message)
    {
        switch (message.Type)
        {
            case MessageType.TimerTick:
                WindowTitle = message.Message;
                break;
            // ....
        }
    }
}

and in the TimerViewModel:

C#
public class TimerViewModel : ViewModelBase
{
    private void OnTick(object sender, TimerModelEventArgs e)
    {
        Messenger.Default.Send(new SimpleMessage(MessageType.TimerTick, TimerValue));
    }
}

What this achieves is as follows: the TimerViewModel updates the TimerView countdown clock in the main window's ContentControl, but we want to update the window's title to also show the countdown. The main window View is bound to the MainViewModel, so to do this, and to keep the view-models separate, we pass a message containing the time remaining. The reason we update the window title bar is discussed a little later.

The TaskBar Preview and the Window Title

As you can see in this screenshot:

330073/CountdownTimer2.png

The countdown value is shown in the taskbar item thumbnail, and in the main window's title. The reason we update the window's title is that when a window is minimzed, the taskbar item thumbnail is not updated by Windows, so if you were to hover your mouse pointer over the icon on the task bar when the item is minimized, the thumbnail preview will display the countdown at the time you minimized the window. Fortunately, the title of the window is updated in the thumbnail preview, so we ensure that we update that to provide a visual clue to the user.

TaskBar Messages

We need a second message type to communicate task bar progress updates in Windows 7: since the MainWindow 'view' is bound to the MainViewModel, we need to receive messages from the TimerViewModel that are appropriate to update the task bar progress indicator. Fortunately this is relatively straightforward, and once again we make use of the Messenger.Default.Register and Messenger.Default.Send pattern we saw earlier.

The second message class is simply:

C#
public class TaskbarItemMessage
{
    public TaskbarItemMessage()
    {
        State = TaskbarItemProgressState.None;
        Value = -1.0;
    }
    public TaskbarItemProgressState State { get; set; }

    public double Value { get; set; }

    public bool HasValue { get { return ! (Value < 0.0); } }
}

Our TimerViewModel just sends instances of these messages and the MainViewModel receives them, and via the magic of data-binding, between the view model (MainViewModel) and the view (MainWindow) the taskbar progress indicator just updates:

XML
<Window x:Class="Btl.MainWindow"
        DataContext="{Binding Main,
                              Source={StaticResource Locator}}">
    <Window.TaskbarItemInfo>
        <TaskbarItemInfo ProgressState="{Binding ProgressState}" 
                      ProgressValue="{Binding ProgressValue}">
            <TaskbarItemInfo.ThumbButtonInfos>
                <ThumbButtonInfoCollection>
                    <ThumbButtonInfo Command="{Binding PlayCommand}"
                                     Description="Start"
                                     DismissWhenClicked="False"
                                     ImageSource="Resources\icon.play.png" />
                    <ThumbButtonInfo Command="{Binding PauseCommand}"
                                     Description="Pause"
                                     DismissWhenClicked="False"
                                     ImageSource="Resources\icon.pause.png" />
                </ThumbButtonInfoCollection>
            </TaskbarItemInfo.ThumbButtonInfos>
        </TaskbarItemInfo>
    </Window.TaskbarItemInfo>
    <!-- ELIDED  -->
</Window>

Since the TaskBarItemInfo thumbnail previews offer us more than just the preview, we can add a thumbnail 'Start' and 'Pause' button (just like Media Player), so we can control the countdown timer from the thumbnail preview, hence the ThumbButtonInfo elements above.

A Note on the UI Design

There is some method to the madness of the Countdown Timer UI: since the Play and Pause buttons are likely to be the most used, they are the largest, then the settings and reset buttons are smaller so they are less likely to be clicked on. The 'About' window is accessed by a small '?' in the bottom right hand corner.

Similarly, the 'OK' and 'Cancel' buttons are widely separated in the Settings view so it is clear which one you want to click on.

330073/CountdownTimer3.png

And lastly, aside from the button icons (play, pause, etc.), I've left the theming of the application alone, so that the OS can choose how to theme it. Of course, since this is an MVVM application, you can take the source code, fire up Blend, and change it however you like.

There are even some third-party libraries that will do a lot of the work for you, e.g., MahApps.Metro.

Bonus Features

In the downloads section at the start of the article, there is also an MSI installer for anyone that cares to use it and just wants to install the timer.

All the source code is also on github and you are free to fork it, copy it, break it, etc. The MSI installer project uses InstallShield, so it is part of the solution hosted on github, but not included in the zip files above (in case you, the reader, do not have it installed).

Finally

If you found the article helpful, or interesting, please vote and/or add any comments below. Thanks!

Revision History

  • 22 February, 2012: Updated code base slightly based on reader comments.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
Jack of all trades.

Comments and Discussions

 
QuestionCirle control for countdown time Pin
Red.Devlab22-Jun-16 3:20
Red.Devlab22-Jun-16 3:20 
QuestionIt even shows how to use ThumbButtonInfos in a XAML-way! Pin
divStar23-Aug-15 15:03
divStar23-Aug-15 15:03 
GeneralMy vote of 5 Pin
AmitGajjar17-Dec-13 0:55
professionalAmitGajjar17-Dec-13 0:55 
Questionquestion about your project Pin
mifodii20125-May-13 21:55
mifodii20125-May-13 21:55 
QuestionHELP Pin
mifodii20121-May-13 12:17
mifodii20121-May-13 12:17 
AnswerRe: HELP Pin
Barry Lapthorn4-May-13 0:16
protectorBarry Lapthorn4-May-13 0:16 
BugThe download links do not work! Pin
Norm1125-Oct-12 22:44
Norm1125-Oct-12 22:44 
GeneralRe: The download links do not work! Pin
Barry Lapthorn26-Oct-12 8:03
protectorBarry Lapthorn26-Oct-12 8:03 
GeneralRe: The download links do not work! Pin
Norm1127-Oct-12 7:18
Norm1127-Oct-12 7:18 
GeneralRe: The download links do not work! Pin
Norm1128-Oct-12 9:56
Norm1128-Oct-12 9:56 
QuestionDatacontext Pin
Dran Dane24-May-12 4:20
Dran Dane24-May-12 4:20 
AnswerRe: Datacontext Pin
Barry Lapthorn26-Oct-12 8:02
protectorBarry Lapthorn26-Oct-12 8:02 
GeneralMy vote of 5 Pin
Madhan Mohan Reddy P21-May-12 20:58
professionalMadhan Mohan Reddy P21-May-12 20:58 
Questionnice Pin
BillW3322-Feb-12 10:44
professionalBillW3322-Feb-12 10:44 
AnswerRe: nice Pin
Barry Lapthorn22-Feb-12 23:18
protectorBarry Lapthorn22-Feb-12 23:18 
GeneralMy vote of 2 Pin
Qaiser_Iftikhar14-Feb-12 10:48
Qaiser_Iftikhar14-Feb-12 10:48 
GeneralRe: My vote of 2 Pin
Barry Lapthorn15-Feb-12 0:08
protectorBarry Lapthorn15-Feb-12 0:08 
GeneralRe: My vote of 2 Pin
Qaiser_Iftikhar15-Feb-12 12:47
Qaiser_Iftikhar15-Feb-12 12:47 
GeneralRe: My vote of 2 Pin
Barry Lapthorn17-Feb-12 1:08
protectorBarry Lapthorn17-Feb-12 1:08 
Ok, now you have listed some points in detail:


Qaiser_Iftikhar wrote:
1) ISettingsModel: Why have you made this interface? This is your model and needs to be a class. What you should have done is create a simple interface to get/set read/save settings object via an interface say ISettingsManager.

 

2) ITimerModel: Same issue as above.


As mentioned in the article this is to provide future opportunity to switch out the implementation of the timer and the settings with minimal code changes. In fact it is one of key principles of oo-design (program to an interface not a concrete implementation, see Gamma et al., or even the .NET framework).


Qaiser_Iftikhar wrote:
3) SettingsModleFactory: Why is this under models? It is not a model, is it? Should be under factories.


Fair enough. This is just organising the code into various sub-folders, and arguably should be called SettingsModelBuilder, if we want to stick with strict design pattern names.


Qaiser_Iftikhar wrote:
4) AboutViewModel: Why are you trying to launch a browser with your homepage in a viewmodel. It is a viewmodel (hint is in the name, model for your view so only put stuff in it which is required by your view), your view doesn't require the that, does it?


Again, another fair point. However my reasoning is to just 'get it done', rather than coding up even more classes to provide some kind of 'OpenerService' just to perform what could be done in one line. Again, this is a *simple* application.


Qaiser_Iftikhar wrote:
5) Version property in AboutViewModel: What will be your app version if you re-factor and put this view model in a separate assembly? Again why are you setting this in viewmodel, it is the job of Services to give Version information to the viewmodel, it shouldn't be trying to get that.


Again, valid point. I will change this in a later version, reasoning was, as above, this is a simple app, and unlikely to have the code broken up into separate assemblies.


Qaiser_Iftikhar wrote:
6) MainViewModel: Why are you creating all your viewmodels in the constructor? So each time someone launch your app they will be creating these viewmodel even if they never launch settings or about page. what will happen if your app grows and you require 50 or 100 viewmodels?


Agreed. These could all be moved to the view-model locator class and lazy-initialized. In fact I had already posted this question on StackOverflow for opinions, prior to posting this on CP.

Qaiser_Iftikhar wrote:
7) SettingsViewModel: Basically you are wrapping all the properties from the SettingsModel just to update view via INotifyPropertyChanged. Why not implement that on your model class and in your viewmdoel simply expose a Settings object? Why duplicate for such a simple model? What if you add two more properties in your settings and forget to include them in viewmodel?


I agree it is duplication, however I disagree on your approach. There are situations where you would store settings that are not presented to the user (e.g. 'remember last window position', and store left, and top, but not show them in a settings dialog). Hence the viewmodel != model in terms of public properties in many situations.

Thanks for taking the time to actually look through the code and explaining some of the issues in detail. I'll make some changes based on some of your comments.
Regards,

Barry

GeneralRe: My vote of 2 Pin
Qaiser_Iftikhar17-Feb-12 2:10
Qaiser_Iftikhar17-Feb-12 2:10 
GeneralMy vote of 5 Pin
Len Holgate14-Feb-12 6:46
Len Holgate14-Feb-12 6:46 
GeneralRe: My vote of 5 Pin
Barry Lapthorn14-Feb-12 9:07
protectorBarry Lapthorn14-Feb-12 9:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.