In my previous two articles, I first described a simple short TimeSpan UserControl
that used sliders to pick the hours, minutes, and seconds of a
TimeSpan. In my second article, I described a custom spinner
control that could be used to replace the sliders.
In this third article, I will update the
TimeSpan control to use the
SpinnerControl, and make the
TimeSpan control a custom control,
rather than a
UserControl (in particular so that we can apply custom themes to it). I refer to this as a
ShortTimeSpanControl as it only represents
TimeSpan from 00:00:00 to 23:59:59. A full
TimeSpan is described here:
A TimeSpan value can be represented as [-]d.hh:mm:ss.ff, where the optional minus sign indicates a negative time interval,
the d component is days, hh is hours as measured on a 24-hour clock, mm is minutes, ss is seconds, and ff is fractions of a second.
That is, a time interval consists of a positive or negative number of days without a time of day, or a number of days with a time of day, or only a time of day.
With the generic theme supplied, this is a selection control, where the user can choose a
TimeSpan. As it is a custom control,
you can apply any custom theme you like, and, perhaps, just make it a read-only control (interactively, that is) that updates its
Value via data-binding.
We would like the control to look something like this:
where we have three spinner controls for 0..23 hours, 0..59 minutes, and 0..59 seconds.
The control should:
- Return and accept a
- Keep the
- Raise an event when
Data Binding and Not Repeating Yourself
We need to consider how to get the values from the
SpinnerControl to update the value in the
ShortTimeSpanControl and vice-versa.
In the spinner control in the previous article, we set:
private static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(decimal), typeof(SpinnerControl),
such that the
Value property binds two-way by default.
ShortTimeSpanControl, we create the following dependency properties with two-way binding:
Then by simply using XAML data-binding, we can bind the three
SpinnerControls to the respective properties that they represent, and the framework takes care
of the binding and update notifications for us.
However, this means that not only do we have a
TimeSpan representing the underlying value of the control, but we also have a copy of the hours, minutes,
and seconds, which is a little bit awkward as there isn't a single point of truth for the
that we are holding.
However, as long as we keep all the data in sync, we will not have a problem. To do this, we just need to check that if we set any property to a new value,
that it doesn't match the existing value, and if it does, then do not call the setter on the other properties.
For example: when
Value changes, we need to update the
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
var control = d as ShortTimeSpanControl;
if (d == null)
var oldValue = (TimeSpan)args.OldValue;
var newValue = (TimeSpan)args.NewValue;
if (oldValue != newValue)
var e = new RoutedPropertyChangedEventArgs<TimeSpan>(oldValue, newValue, ValueChangedEvent);
Databinding and CoerceValueCallback with the Spinner Control
Whilst I was adding my spinner control to the time span control generic theme, I discovered some odd behaviour: the problem was that my
Value dependency property
in the timespan control was over-running its bounds, but only during the XAML databinding to the spinner control. My decrease command in the spinner control was:
protected void OnDecrease()
Value -= Change;
which causes the over/under-run, and the
CoerceValueCallback that should have prevented this was:
private static decimal LimitValueByBounds(decimal newValue, SpinnerControl control)
newValue = Math.Max(control.Minimum, Math.Min(control.Maximum, newValue));
newValue = Decimal.Round(newValue, control.DecimalPlaces);
private static object CoerceValue(DependencyObject obj, object value)
decimal newValue = (decimal)value;
SpinnerControl control = obj as SpinnerControl;
if (control != null)
newValue = LimitValueByBounds(newValue, control);
It seems that the data-binding update occurs first, performed by the WPF framework (when it calls
SetValue 'implicitly' on the dependency property),
allowing the under-run, and then the
CoerceValueCallback is called after the data-binding update. It turns out that this has been covered
in numerous places on the web, e.g., here,
but this item on the MS Connect site describes the problem, and the reasoning behind the behaviour.
I tried swapping out the
SpinnerControl with a
Slider and found that the
Slider behaves as expected,
implying that the problem is not really something to do with WPF, but rather how we approach the problem. The solution in my case is to also limit the bounds
OnIncrease commands that were causing the issue:
protected void OnDecrease()
Value = LimitValueByBounds(Value - Change, this);
By adding this trivial fix, we prevent the
Value from ever going below our minimum.
As we have four properties, and each can change, it makes sense to create four corresponding events:
These are just typical boilerplate custom control event implementations.
There is not a lot else to say about this control. It is really just a case of creating four
DependencyProperty fields, and four corresponding events.
All the validation work is delegated to the underlying
SpinnerControls. The only extra work that we have to perform is that since we are storing
TimeSpan in two places (
Seconds), we ensure that the data is kept
up to date equally in all the properties.
As mentioned at the beginning, this control is only supposed to represent a short and well defined
TimeSpan from 00:00:00 to 23:59:59.
This control could of course be extended to represent a full
Please leave any comments or suggestions below, and don't forget to vote up the article if it is helpful. Thanks!