Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

An Extremely Simple WPF TimeSpan Control

0.00/5 (No votes)
11 Jan 2012 1  
A TimeSpan control implemented using sliders.

UserControlExample

Introduction

This article was motivated by a recent attempt I made to create a control that allows you to choose a TimeSpan. This article is aimed at beginners and quickly runs through a few of the common aspects of WPF. This article will construct a UserControl from basic WPF controls. WPF (4.0) lacks a numeric up/down control (see this question on StackOverflow for example) so we will use sliders.

As usual, please download the source code to see what is going on in more detail.

Some Basics

In any XAML, I add a name to the root element, called root of course:

<UserControl x:Class="Btl.Controls.ShortTimeSpanControl" x:Name="root" >

This allows us to easily refer to properties on the root element in question when we are data binding.

Also, I make further use of the data-binding nature of WPF so that I do not have to create unnecessary events in my XAML, and wire them into my code-behind, hence my UserControl implements INotifyPropertyChanged.

The Control: ShortTimeSpanControl

For my requirements, I am only interested in creating a positive TimeSpan from 00:00:00 to 23:59:59. Since, for reasons known only unto Microsoft, a numeric up/down, or spinner, was omitted from the default WPF Toolkit, and users are generally error prone, the easiest way to control numbers is to use a Slider.

I implement the properties Seconds, Minutes, and Hours as ints, and raise PropertyChanged if their value is changed. This then allows me to bind each slider to the Seconds/Minutes/Hours properties. We will revisit these in a moment.

Using DependencyProperty

We want to allow the consumer of a ShortTimeSpanControl to just use a property that returns a TimeSpan. So whilst we have implemented the Seconds/Minutes/Hours properties, we want to implement a DependencyProperty that allows consumers of the control to bind to it. This is achieved by registering the property called Value:

public partial class ShortTimeSpanControl : UserControl, INotifyPropertyChanged
{        
    public TimeSpan Value
    {
        get { return (TimeSpan)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }
    
    private static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(TimeSpan), typeof(ShortTimeSpanControl),
        new FrameworkPropertyMetadata(TimeSpan.Zero, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged));
    
    //......
}

It is worth noting that Value is a very common property name, but for this example it is sufficient, and hopefully obvious. We do several things here that are worth explaining:

  • We use the standard convention of suffixing the property with Property.
  • We register the property with a default value (TimeSpan.Zero).
  • The Value property is trivially defined; we do not add anything into the setter: if the property is set in XAML, WPF calls SetValue directly, and any custom code in your setter is not called.
  • We state that our property binds two-way by default.

The last point is worth re-iterating: in most simple examples that you may find on the web, you will probably see this:

private static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register("Value", typeof(TimeSpan), typeof(ShortTimeSpanControl),
    new PropertyMetadata(TimeSpan.Zero, OnValueChanged));

By default, that means the binding is one-way, i.e., if a consumer binds to your DependencyProperty, they have to add Mode=TwoWay to their binding/XAML markup. If anyone knows why this is the default, please leave a comment below, as I find it a little strange that all the properties on default WPF controls are two-way.

Putting It All Together

Taking a look at one of the three sliders:

<Slider Name="SecondsSlider"
    Grid.Row="2"
    IsDirectionReversed="True"
    LargeChange="5"
    Maximum="59"
    SmallChange="1"
    Value="{Binding ElementName=root, Path=Seconds}" />

We bind the Slider to the Seconds property on our root element, i.e., our UserControl code-behind:

private int _seconds;
public int Seconds
{
    get
    {
        return _seconds;
    }
    set
    {
        if (value == _seconds)
            return;
        _seconds = value;
        RaisePropertyChanged("Seconds");
        var v = Value;
        Value = new TimeSpan(v.Hours, v.Minutes, _seconds);
    }
}

private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    ShortTimeSpanControl control = obj as ShortTimeSpanControl;
    var newValue = (TimeSpan)e.NewValue;

    //  we update the Control properties to set the sliders.
    control.Seconds = newValue.Seconds;
    control.Minutes = newValue.Minutes;
    control.Hours = newValue.Hours;
}

Since we now have two ways of updating the control: either by setting the Value, or by setting Seconds, Minutes, Hours, we need to ensure that the UserControl displays the correct values. By using _seconds as a backing store for the property, we use the setter guard to prevent us from getting into a loop: if Value gets set, we then set the Seconds property within the OnValueChanged callback. Once _seconds is set, we check to ensure we don't set it again, and then set the Value again.

Finally, we add the control to our main project window, and wire that up using data-binding and INotifyPropertyChanged to demonstrate the control. The image shows the UserControl in grey, and the data-bound value in the MainWindow in white.

Final Words

The next step for this control would be to swap out the sliders for numeric up/down/spinner controls, and to make it easy to theme.

If you found this helpful, please leave a comment below. If you didn't find this helpful, or found a mistake, also, please leave a comment below saying why!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here