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

A nullable DateTimePicker that is easy to write and easy to use

0.00/5 (No votes)
15 May 2008 1  
With Visual Studio 2008, it is easy to create a nullable version of Microsoft's DateTimePicker.

Introduction

The same old story: I needed a date-time picker that could display a blank value, and I did not want to have to train my users that a grayed-out date with no check in the box really means there is no date. After looking through the many controls here at CodeProject, I got a bit dismayed. I made my own stab at it using VB and Visual Studio 2008, and I got something I liked without having to do a lot of work.

Calendar2.JPG

Using the code

The base control itself is pretty straightforward: a UserControl containing a MaskedEditBox and a Panel. The MaskedEditBox is masked for a short date, and the user can type in a date or clear the control. Docked on the inside right edge of the MaskedEditBox is a Panel control that acts as a button. If the user clicks on it, an extended version of VS 2008's MonthCalendar control pops up. If the MaskedEditBox has a date, that date will be pre-selected on the pop-up. Selecting a date will copy that date into the MaskedEditBox, or the user can click on the "Clear date" button which clears the MaskedEditBox. Either choice will close the pop-up. The user also has the option of closing the pop-up without changing the date, by clicking on the "Close" button.

The code defining the pop-up is embedded in the control as a private class. Writing it was very straightforward: I created a regular Form, added a MonthCalendar and two Label controls, set various properties to my liking, then copied the InitializeComponent code into the class' New method. This was the result, after cleaning it up a bit:

Protected Class Calendar
          Inherits System.Windows.Forms.Form

    Private MyPicker As NullableDateTimePicker
    Private WithEvents Label1 As Label
    Private WithEvents Label2 As Label
    Private WithEvents MonthCalendar1 As MonthCalendar

    Public Sub New(ByRef Picker As NullableDateTimePicker)
        MyPicker = Picker

        Me.MonthCalendar1 = New MonthCalendar
        Me.Label1 = New Label
        Me.Label2 = New Label

        Me.SuspendLayout()
        '
        'MonthCalendar1
        '
        Me.MonthCalendar1.Location = New Point(0, 0)
        Me.MonthCalendar1.Margin = New Padding(0)
        Me.MonthCalendar1.MaxSelectionCount = 1
        Me.MonthCalendar1.Name = "MonthCalendar1"
        Me.MonthCalendar1.ShowTodayCircle = False
        Me.MonthCalendar1.TabIndex = 0
        '
        'Label1
        '
        Me.Label1.Font = New Font("Microsoft Sans Serif", 8.25!, _
            FontStyle.Underline, GraphicsUnit.Point, CType(0, Byte))
        Me.Label1.ForeColor = SystemColors.HotTrack
        Me.Label1.Location = New Point(2, 163)
        Me.Label1.Name = "Label1"
        Me.Label1.Size = New Size(55, 13)
        Me.Label1.TabIndex = 1
        Me.Label1.Text = "Clear date"
        '
        'Label2
        '
        Me.label2.AutoSize = True
        Me.label2.Font = New Font("Microsoft Sans Serif", 8.25!, _
            FontStyle.Underline, GraphicsUnit.Point, CType(0, Byte))
        Me.label2.ForeColor = System.Drawing.SystemColors.HotTrack
        Me.label2.Location = New System.Drawing.Point(192, 163)
        Me.label2.Name = "Label2"
        Me.label2.Size = New System.Drawing.Size(33, 13)
        Me.label2.TabIndex = 2
        Me.label2.Text = "Close"
        '
        'CalendarPopup
        '
        Me.AutoScaleDimensions = New SizeF(6.0!, 13.0!)
        Me.AutoScaleMode = AutoScaleMode.Font
        Me.ClientSize = New Size(228, 184)
        Me.ControlBox = False
        Me.Controls.Add(Me.Label1)
        Me.Controls.Add(Me.Label2)
        Me.Controls.Add(Me.MonthCalendar1)
        Me.FormBorderStyle = FormBorderStyle.FixedToolWindow
        Me.MaximizeBox = False
        Me.MinimizeBox = False
        Me.Name = "CalendarPopup"
        Me.ShowIcon = False
        Me.ShowInTaskbar = False
        Me.StartPosition = FormStartPosition.Manual
        Me.ResumeLayout(False)
    End Sub

    Public Property SelectedDate() As Date?
        Get
            If Information.IsDate(MonthCalendar1.SelectionStart) Then
                Return MonthCalendar1.SelectionStart
            Else
                 Return Nothing
            End If
        End Get
        Set(ByVal value As Date?)
            If Information.IsDate(value) Then
                MonthCalendar1.SetDate(Convert.ToDateTime(value))
            Else
                MonthCalendar1.SetDate(DateTime.Now)
            End If
        End Set
    End Property

    Private Sub Label1_Click(ByVal sender As Object, ByVal e As EventArgs) _
    Handles Label1.Click
        'Clear
        MyPicker.Value = Nothing
        Me.Close()
    End Sub

    Private Sub Label2_Click(ByVal sender As Object, ByVal e As EventArgs) _
    Handles Label2.Click
        'Close
        Me.Close()
    End Sub

    Private Sub MonthCalendar1_DateSelected(ByVal sender As Object, _
                ByVal e As DateRangeEventArgs) _
                Handles MonthCalendar1.DateSelected
        MyPicker.Value = e.Start
        Me.Close()
    End Sub
End Class

The NullableDateTimePicker has a private variable, Cal, which serves as the control's instance of the pop-up form. When the control's button is clicked, Cal is instantiated, if necessary, set to either the value in the masked edit box (if it is a valid date) or the current date (if it is not), and displayed as a modal form.

Calendar1.JPG

Private Sub Button1_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) _
            Handles Button1.MouseDown
    If Cal Is Nothing Then Cal = New Calendar(Me)
    If Cal.Visible Then Exit Sub

    If Information.IsDate(MaskedTextBox1.Text) Then
        Cal.SelectedDate = Convert.ToDateTime(MaskedTextBox1.Text)
    Else
        Cal.SelectedDate = Nothing
    End If

    Cal.Location = Button1.PointToScreen(New Point(0, 19)) 
    Cal.ShowDialog()
End Sub

Getting the date out of the control is pretty straightforward. I have implemented HasDate, which indicates whether or not the control is displaying a valid date, Text, which returns the Text property of the masked edit box, and Value which returns a Nullable(Of Date) value (abbreviated in VB 2008 as Date?).

Public ReadOnly Property HasDate() As Boolean
    Get
        Return Information.IsDate(MaskedTextBox1)
    End Get
End Property

Public Overrides Property Text() As String
    Get
        Return MaskedTextBox1.Text
    End Get
    Set(ByVal value As String)
        MaskedTextBox1.Text = value
    End Set
End Property

Public Property Value() As Date?
    Get
        If Me.HasDate Then
            Return Convert.ToDateTime(MaskedTextBox1.Text)
        Else
            Return Nothing
        End If
    End Get
    Set(ByVal value As Date?)
        If Information.IsDate(value) Then
            MaskedTextBox1.Text = _
              Convert.ToDateTime(value).ToString("MM/dd/yyyy")
        Else
            MaskedTextBox1.Text = ""
        End If
    End Set
End Property

Room for improvement

This control is pretty basic, but it does what I need: allows the user to easily select a date, to manually enter a date, and to represent the absence of a date. Making it data-bound should be pretty easy. Properties to change the appearance of the calendar pop-up would also be handy.

Points of interest

Note the use of Information.IsDate above. Information is a static class found in the Microsoft.VisualBasic namespace. I wrote the code this way to help C# programmers who might want to translate this control; I don't think C# has anything that is really equivalent to IsDate. To use this function (and some other useful tools), add a reference to the namespace in your project.

As noted above, Visual Basic 2008 has added a shorthand notation for Nullable(Of T) where T is a value type. This is indicated by adding a ? after either the variable name or the variable type in the declaration. Thus, my use of Date? is effectively the same as using Nullable(Of Date); in fact, I believe, either way compiles the same.

History

  • 2008-05-15 - Added a second label to the pop-up control which allows the user to close the form without changing the displayed value, and changed the text of the article to reflect this. I also expanded a bit on the new Nullable shorthand notation in VB.

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