Des Teufels Steckverbinder Meine Bewerbung als Wirtschaftsweiser

Silverlight: Making a DateTimePicker

Published on Saturday, January 8, 2011 5:11:00 PM UTC in Programming

A question that frequently surfaces on the Silverlight forums is the one after a DateTimePicker control. The Silverlight Toolkit has both a date picker and a time picker, but not the combination of both. The issue tracker of the Toolkit project on Codeplex lists that control among the Top-20 voted features. But when you ask about it, the answer usually is that it's so simple to build one yourself that it's not worth creating a dedicated control for that. However, when I looked at the source code of an attempted DateTimePicker control someone sent me in desperation, I realized that you can get lost in undesired behavior and unforeseeable problems with mutually triggering events easily. So here is a short post that shows a straight-forward way to create a functioning simple DateTimePicker control. You can download the complete sample at the end of this article.

Creating the user control

We will build the DateTimePicker as user control, as we obviously want to make use of the already existing date picker and time picker controls of the Silverlight Toolkit:

image

Layout the two controls in your user control as you wish, and set all its properties according to your requirements. If you want to make the DateTimePicker more flexible for reuse, you will need to extend this example and expose the required properties so they can be set from the outside. For this example, all we want to do is create the required logic though.

<Grid x:Name="LayoutRoot"
        Background="White">
    <sdk:DatePicker Height="23"
                    HorizontalAlignment="Left"
                    Name="DatePicker"
                    VerticalAlignment="Top"
                    Width="120" />
    <toolkit:TimePicker Height="22"
                        HorizontalAlignment="Left"
                        Margin="126,0,0,0"
                        Name="TimePicker"
                        VerticalAlignment="Top" />
</Grid>

Adding a dependency property

One basic step we need to perform is to add a dependency property for the date time value. This is required so we can use the control in data binding scenarios. We also want to give the user of the control the possibility to work with a single property and implement the required logic to handle the two individual controls ourselves internally.

#region SelectedDateTime dependency property

public DateTime? SelectedDateTime
{
    get
    {
        return (DateTime?)GetValue(SelectedDateTimeProperty);
    }
    set
    {
        SetValue(SelectedDateTimeProperty, value);
    }
}

public static readonly DependencyProperty SelectedDateTimeProperty =
    DependencyProperty.Register("SelectedDateTime",
    typeof(DateTime?),
    typeof(DateTimePicker),
    new PropertyMetadata(null, new PropertyChangedCallback(SelectedDateTimeChanged)));

private static void SelectedDateTimeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
    DateTimePicker me = sender as DateTimePicker;

    if (me != null)
    {
        me.DatePicker.SelectedDate = (DateTime?)e.NewValue;
        me.TimePicker.Value = (DateTime?)e.NewValue;
    }
}

#endregion

In the code above, when the value of the dependency property changes, we set both the value of the date picker as well as the time picker to the new value. This will work for bindings as well as when someone updates the value in code, and concludes the necessary steps for updates from "the outside". All we need to do now is handle the changes that happen when the user picks a date or time.

Processing date and time changes

This is the part where people apparently struggle. On the one hand you have to do some synchronization, which means you have to transfer changes the user makes into our dependency property and the respective other control. On the other hand you have to do some additional processing and need to avoid that this causes an endless chain of events that mutually trigger each other.

public DateTimePicker()
{
    InitializeComponent();

    DatePicker.SelectedDateChanged += new EventHandler<SelectionChangedEventArgs>(DatePicker_SelectedDateChanged);
    TimePicker.ValueChanged += new RoutedPropertyChangedEventHandler<DateTime?>(TimePicker_ValueChanged);
}

First the straight-forward part. If the user changes the selected time value, we simply update the value of our dependency property and the date picker without any need for additional checks:

private void TimePicker_ValueChanged(object sender, RoutedPropertyChangedEventArgs<DateTime?> e)
{
    if (DatePicker.SelectedDate != TimePicker.Value)
    {
        DatePicker.SelectedDate = TimePicker.Value;
    }

    if (SelectedDateTime != TimePicker.Value)
    {
        SelectedDateTime = TimePicker.Value;
    }
}

The problematic part is when someone changes the date, as this also updates the time part. I just paste the code first and discuss it afterwards.

private void DatePicker_SelectedDateChanged(object sender, SelectionChangedEventArgs e)
{
    // correct the new date picker date by the time picker's time
    if (DatePicker.SelectedDate.HasValue && TimePicker.Value.HasValue)
    {
        // get both values
        DateTime datePickerDate = DatePicker.SelectedDate.Value;
        DateTime timePickerDate = TimePicker.Value.Value;

        // compare relevant parts manually
        if (datePickerDate.Hour != timePickerDate.Hour
            || datePickerDate.Minute != timePickerDate.Minute 
            || datePickerDate.Second != timePickerDate.Second)
        {
            // correct the date picker value
            DatePicker.SelectedDate = new DateTime(datePickerDate.Year, 
                datePickerDate.Month, 
                datePickerDate.Day, 
                timePickerDate.Hour, 
                timePickerDate.Minute, 
                timePickerDate.Second);

            // return, because this event handler will be executed a second time
            return;
        }
    }

    // now transfer the date picker's value to the time picker
    // and dependency property
    if (TimePicker.Value != DatePicker.SelectedDate)
    {
        TimePicker.Value = DatePicker.SelectedDate;
    }

    if (SelectedDateTime != DatePicker.SelectedDate)
    {
        SelectedDateTime = DatePicker.SelectedDate;
    }
}

If both the date picker and time picker have values, this means that either a time value has been set before, or that the user already has selected a time value manually. What the following lines then do is compare the time component of the date picker's value to the time picker value. If they are different, we give the time picker's time preference and correct the date picker's value by that.

The noteworthy part is that after that correction we leave the event handler. We've just changed the date picker value which means that very same event handler will be executed a second time anyway (this time the check will result in no difference and skip to the following parts); to avoid unnecessary multiple execution of the following code, we simply exit here.

The last parts do the same as with the time picker, transfer the value into the other control and update the dependency property.

Usage

The user control can be used like this:

<StackPanel HorizontalAlignment="Center"
            Margin="50">
    <local:DateTimePicker x:Name="MyDateTimePicker"
                            SelectedDateTime="{Binding DateTime, Mode=TwoWay}" />
    <TextBlock Text="{Binding DateTime}"
                Margin="50" />
</StackPanel>

Our custom dependency property is bound to a DateTime value of the data context with a two-way binding. I've added a text block below the control to demonstrate it is actually working :-). This results in something like this when you execute the sample:

image

Summary

There you go, a working DateTimePicker control. I'm aware that the code probably could've been simplified slightly if we had used the date picker as a reference and to store the defining value instead of maintaining a separate property, but I wanted to make a clear implementation instead (and the involved problems would've been the same anyway). I hope that this example shows that building the control really isn't hard at all, but you have to carefully think about the involved steps before. Download the source code here:

DateTimePicker sample

Tags: Controls · Silverlight