(Print this page)

The DateTimePicker control
Published date: Saturday, January 1, 2005
On: Moer and Éric Moreau's web site

It is often very hard for programmers in a culture different then the one used in the US to correctly handle date and time inputs. We (because I am one of these) have to handle different input mask for dates and numbers then the one that is recognized natively by programming languages (mm/dd/yy).

One of the easiest ways to handle that is to use a control that won’t let the user enter invalid dates. Microsoft gives us one control to do that: the DateTimePicker control. As it (too!) often occurs, the control is good for basic requirements but fall short on many features. As always, I will show you how to get this control one step further.

Special features

One of my users is using Quicken and discovered that date controls allowed him some letters as shortcuts to common dates. For example, M (first letter of the word month) set the control to the first day of the current while H (last letter of the word month) gives him the last day of the current month. Same for Y and R, which are the first and last letters of the year word. Finally, T is for today’s date.

These features are very easily implemented by handling the KeyPress event of the DateTimePicker control like it is shown in this code:

Dim dtPicker As DateTimePicker = CType(sender, DateTimePicker)
With dtPicker
    Select Case e.KeyChar.ToString.ToUpper
        Case "T"
            'today
            .Value = Date.Today
            e.Handled = True
        Case "M"
            'begin of the current month
            .Value = New Date(.Value.Year, .Value.Month, 1)
            e.Handled = True
        Case "H"
            'end of the current month
            .Value = New Date(.Value.Year, _
                    .Value.Month, _
                    Date.DaysInMonth(.Value.Year, .Value.Month))
            e.Handled = True
        Case "Y"
            'begin of the current year
            .Value = New Date(.Value.Year, 1, 1)
            e.Handled = True
        Case "R"
            'end of the current year
            .Value = New Date(.Value.Year, 12, 31)
            e.Handled = True
    End Select
End With
If you don’t set the e.Handled property to True, you will get a beep sound because these letters are normally not accepted in this control.

Handling NULL date values

As far as I am concerned, the major problem with the DateTimePicker control is the fact that it does not handle null values. We always have date fields in our databases that may contain null values. Also have you to delete a value from this control? If you are like, you already have “googled” to find a miracle method to circumvent that. I found many workarounds but none completely satisfied me!

So I decided to concoct my own! I promise not to use the ShowCheckBox property that no users like. Instead, null dates will be shown as an empty field just like any other controls is showing when no values are set. My magic ingredients: the CustomFormat property, the formatter and the parser events. Are you aware that you can set it to a single space?

I have created a small demo that you really need to download (because I won’t paste all the code here).

Figure 1: The DateTimePicker in action

This demo simply creates a DataSet and a DataTable and fills the four records you see in the grid. The grid (on the right) is only used as a way of seeing the content of the DataTable in a single view. On the left, you find a textbox to display the name and a DateTimePicker. You also get two navigation buttons and a label stating the current position.

To have my code working, you first need to set the Format property of the DateTimePicker to custom. I have added these lines to the Load event of my test form:

With Me.DateTimePicker1
    .Format = DateTimePickerFormat.Custom
    .CustomFormat = "yyyy/MM/dd"
End With

I use the DataBindings collection to bind the TextBox and the DateTimePicker to a DataTable.

Binding the TextBox control is straightforward:

Me.TextBox1.DataBindings.Add("Text", dsTest, "TestData.EmployeeName")

Binding the DateTimePicker requires a bit more code:

Dim MyBinding As New Binding("Value", dsTest, "TestData.HireDate")
AddHandler MyBinding.Format, AddressOf dtFormatter
AddHandler MyBinding.Parse, AddressOf dtParser
DateTimePicker1.DataBindings.Add(MyBinding)

If you look carefully, you will find that I am adding two handlers to two methods you never saw yet and these methods will do all the job of handling the null values to and from the data source.

The formatter event fires when data is moved from the data source to the DateTimePicker control. So if we read a null value, we need to transform this value in something acceptable for the control. Here is my code:

If Not e.DesiredType Is GetType(DateTime) Then
    Return
End If

Dim b As Binding = CType(sender, Binding)
Dim dtPicker As DateTimePicker = CType(b.Control, DateTimePicker)
If e.Value.GetType Is GetType(System.DBNull) Then
    dtPicker.CustomFormat = " "
    dtPicker.Tag = "NULL"
    e.Value = Date.Today
Else
    dtPicker.CustomFormat = "yyyy/MM/dd"
    dtPicker.Tag = ""
End If

You can see here if the value read from the data source is null, I set the CustomFormat property to a space (who said it has to be a valid date format!). This allows us to display an empty field. We also set the Tag property to a constant value we will use later to recognize that the field is null and we set the e.Value property to a valid date otherwise the binding will fail.

If the value read from the data source is valid, we set the CustomFormat property to a valid format and we also empty the Tag property.

The Parser event is the exact opposite of the Formatter event. It fires when data is moved from the control to the data source. Its job is to set the value of the DataSource to null if the Tag property contains the NULL constant value.

If Not e.DesiredType Is GetType(DateTime) Then
    Return
End If
If Not e.Value.GetType Is GetType(DateTime) Then
    Return
End If

Dim b As Binding = CType(sender, Binding)
If CType(b.Control, DateTimePicker).Tag.ToString = "NULL" Then
    e.Value = System.DBNull.Value
End If

So now we are able to go through all four records of the demo application DataTable (remember that one contains a null date) without problem. When the current record will be the third one, the DateTimePicker will simply display an empty field. The last two things we need to do now are to allow the user to select a valid date when the current value is null and to delete a value.

The DropDown event of the DateTimePicker fires when the user clicks the down arrow to select a date. We will use this event to reset some properties like the CustomFormat (to a valid format) and the Tag (to indicate that the value will not be null) with this code.

Dim dtPicker As DateTimePicker = CType(sender, DateTimePicker)
With dtPicker
    If .Tag.ToString = "NULL" Then
        .CustomFormat = "yyyy/MM/dd"
        .Value = Date.Today
        .Tag = ""
    End If
End With

Last but not least is when the user wants to delete a date. I use the KeyDown event to trap the Delete key and set the Tag property (to NULL so that the parser event will send NULL to the data source) and set the CustomFormat to a space (so that the field will display an empty field).

Dim dtPicker As DateTimePicker = CType(sender, DateTimePicker)
With dtPicker
    If e.KeyCode = Keys.Delete Then
        'delete the value (set to NULL)
        .CustomFormat = " "
        .Tag = "NULL"
        .Value = dtPicker.MinDate
        e.Handled = True
    End If
End With

Note: The downloadable source code contains other code into this event to handle up and down arrow used as another shortcut to quickly add or subtract 1 day to the current date.

That’s everything you need to code to (correctly I hope) handle null dates with the DateTimePicker control. Give it a try.

What about the time?

To let users input time in the DateTimePicker control, you need to set the Format property to “Time” (or “Custom”). You probably found that already. But you probably also found that the down arrow is not showing a clock or any other similar control to let the user select time, it displays a calendar!

I have added these lines to the Load event of my test form:

With Me.DateTimePicker2
    .Format = DateTimePickerFormat.Time
    .ShowUpDown = True
End With

The trick here is to set the ShowUpDown property. This property won’t show a clock but it won’t display a misleading calendar. It will let user add or subtract 1 interval to the currently selected part of the time.

If you want to display a control to let the user select time much like he select a date from the drop downed calendar, I invite you to have a look at this project.

Conclusion

Once again, with some code, you can get decent workaround to common problems. The best thing would be that Microsoft already did that but it seems that they want to let us some fun!

Instead of repeating this same code on all your forms, I hope you will create an extender class (like the one we have already created for the TabControl for example). This way, re-using and adding new features will be very easy.

I hope you appreciated the topic and see you next month.


(Print this page)