Yet Another Nullable DateTimePicker Control

Contents

Introduction

The DateTimePicker control shipped with .NET has a very nice look and feel but lacks one important functionality: You can’t enter null values. That means that, if you bind your DateTimePicker control to a dataset, you run into trouble. Because in a database it’s normal to have nullable DateTime values. While searching the Internet, I found several solutions but I had problems with each of them:

  • Yet another DateTime…Slicker: This control from Nils Jonsson uses the CheckBox of the DateTimePicker to indicate null values. I don’t like this solution because I wanted a solution consistent with, for example, the TextBox. If you display a null value in a TextBox, you don’t use a CheckBox to indicate when it’s null. You just display nothing.
  • Nullable DateTimePicker: This control from Pham Minh Tri uses another approach. Null values are indicated with an empty DateTimePicker. That’s the way I like it, but I ran into several problems with this control where things didn’t work as expected. I got an ugly exception as soon as I placed the control on a TabPage and tried to use databinding. Whenever the control was initialized with a null value, this exception occurred: “An unhandled exception of type ‘System.ComponentModel.Win32Exception’ occurred in system.windows.forms.dll”. When it was initialized with a valid DateTime value, it worked.
  • Nullable DateTimePicker: This is a control from Alexander Shirshov. It’s quite similar to the one from Pham Minh Tri, but with fewer problems. But, the ugly exception when using it on a TabControl is exactly the same.

Well, I had controls with the right look and feel, but they didn’t work on TabPages and of course I needed them to work on TabPages because most of my controls are placed on TabPages. So, I started to investigate the exception. After a while, I was sure it has something to do with the change of the DateTimePicker.Format property to CustomFormat during initialization. And then, I found this explanation from Brett Zimmerann:

“It is the change in DateTimePicker.Format when you are switching between a null date and a non-null date (or vice versa). The change in the value of the Format causes the component to raise a notification, which is normally okay. But, in this case, the control is not visible yet and the Format notification fails internally.”

So, I came up with my own NullableDateTimePicker control based on the work of the other NullableDateTimePicker controls, but that avoids these problems.

Solution

The NullableDateTimePicker control is inherited from the DateTimePicker control to get all the nice things that it contains. The Format property of the base DateTimePicker is always set to DateTimePickerFormat.Custom and will never change (to avoid the exception when the control is used on a TabPage). To still have the possibility to change the format the same way as in the original DateTimPicker, the NullableDateTimePicker has to emulate everything around the Format and CustomFormat properties of the DateTimePicker. Here’s how the implementation of that looks.

Implementation

First, you need some new private fields to store different values used by the NullableDateTimePicker.

// true, when no date shall be displayed (empty DateTimePicker)
private bool _isNull;

// If _isNull = true, this value is shown in the DTP
private string _nullValue;

// The format of the DateTimePicker control
private DateTimePickerFormat _format = DateTimePickerFormat.Long;

// The custom format of the DateTimePicker control
private string _customFormat;

// The format of the DateTimePicker control as string
private string _formatAsString;
Formatting

As already mentioned, the Format and CustomFormat properties can’t be used from the DateTimePicker anymore, so you have to implement new properties replacing them:

public new String CustomFormat
{
  get { return _customFormat; }
  set
  {
    _customFormat = value;
  }
}

public new DateTimePickerFormat Format
{
  get { return _format; }
  set
  {
    _format = value;
    SetFormat();
    OnFormatChanged(EventArgs.Empty);
  }
}

When the setter of the Format property is called, the new DateTimePickerFormat value is stored in _value. Now, the format of the NullableDateTimePicker has to be changed to this new format. This is done in the SetFormat() method.

Because the parent DateTimePickers Format property is always set to DateTimePickerFormat.Custom, you have to map the Format value of your class to a string representing this format. This string then can be set as the CustomFormat of the base DateTimePicker control. But, how do you map a DateTimePickerFormat value to its string representation? You have to get the current CultureInfo. From CultureInfo, you get a DateTimeFormatInfo that gives you the correct format strings for the different DateTimePickerFormats.

private void SetFormat()
{
  CultureInfo ci = Thread.CurrentThread.CurrentCulture;
  DateTimeFormatInfo dtf = ci.DateTimeFormat;
  switch (_format)
  {
    case DateTimePickerFormat.Long:
      FormatAsString = dtf.LongDatePattern;
      break;
    case DateTimePickerFormat.Short:
      FormatAsString = dtf.ShortDatePattern;
      break;
    case DateTimePickerFormat.Time:
      FormatAsString = dtf.ShortTimePattern;
      break;
   case DateTimePickerFormat.Custom:
     FormatAsString = this.CustomFormat;
     break;
  }
}

In the method above, you assign the string representation of the format to the FormatAsString property, which is a private property in your control.

private string FormatAsString
{
  get { return _formatAsString; }
  set
  {
    _formatAsString = value;
    base.CustomFormat = value;
  }
}

In the setter of the FormatAsString property, you assign the format string to base.CustomFormat to finally change the format of the parent DateTimePicker class.

Value

Up to now, you have hidden the format issues behind your new properties with the same names. So, for the user of the control, everything is still the same. Now, go one step further now and implement a new Value property, allowing you to not only set DateTime values but also null values. The getter just returns the Value of the base control or null if the control shows a null value. The setter sets the control to null if the value is null or DBNull.Value by calling SetToNullValue(). If the value is a DateTime value, it sets the Value of the base control and calls SetToDateTimeFormat() to correctly show the DateTime value.

public new Object Value
{
  get
  {
    if (_isNull)
      return null;
    else
      return base.Value;
  }
  set
  {
    if (value == null || value == DBNull.Value)
    {
      SetToNullValue();
    }
    else
    {
      SetToDateTimeValue();
      base.Value = (DateTime)value;
    }
  }
}

If the control showed a null value and now has to show a DateTime value, the SetToDateTimeValue() method sets the format to the currently used DateTimePickerFormat by calling SetFormat() and then calls OnValueChanged() to fire the ValueChanged event.

private void SetToDateTimeValue()
{
  if (_isNull)
  {
    SetFormat();
    _isNull = false;
    base.OnValueChanged(new EventArgs());
  }
}

SetToNullValue displays the NullValue in the control instead of a DateTime value. NullValue is a string property that can be set by the developer. So, you have the ability to either show an empty DateTimePicker, or you can display some specific string like “<Select date please>” or whatever you want.

private void SetToNullValue()
{
  _isNull = true;
  base.CustomFormat = (_nullValue == null || _nullValue == String.Empty)
                      ? " " : "'" + NullValue + "'";
}

public String NullValue
{
  get { return _nullValue; }
  set { _nullValue = value; }
}
Events

When the DateTimePicker shows a null value and the user drops down the control to select a DateTime value, you have to set the Format back to the correct DateTimePickerFormat.

protected override void OnCloseUp(EventArgs e)
{
  if (Control.MouseButtons == MouseButtons.None && _isNull)
  {
    SetToDateTimeValue();
    _isNull = false;
  }
  base.OnCloseUp (e);
}

What’s missing up to now is a possibility for the user to set the value of the DateTimePicker to null. It works by databinding, not by user interaction. The DateTimePicker shall be made nullable by pressing the Delete key. For that, override the OnKeyUp event and set the Value to the NullValue.

protected override void OnKeyUp(KeyEventArgs e)
{
  if (e.KeyCode == Keys.Delete)
  {
    this.Value = _dateTimeNullValue;
    OnValueChanged(EventArgs.Empty);
  }
  base.OnKeyUp(e);
}
Constructor

Finally, implement the constructor. In the constructor, you set the Format property of the base class to DateTimePickerFormat.Custom and your own Format property to DateTimePickerFormat.Long (Long is the default value of the DateTimePicker control).

public NullableDateTimePicker() : base()
{
  base.Format = DateTimePickerFormat.Custom;
  NullValue   = " ";
  this.Format = DateTimePickerFormat.Long;
}

Usage

The usage is exactly the same as with .NET’s DateTimePicker. The only difference is when accessing the Value property because it’s of type Object instead of type DateTime. Thus, you have to cast it to DateTime (but check for null before).

NullableDateTimePicker _dtp = new NullableDateTimePicker();
_dtp.NullValue = "Select Date";   // Set the null value of the
                                  // control (default is an empty string)

//  use of the NullableDateTimePicker without databinding:
DateTime _date = new DateTime();
if (_dtp.Value != null)
  _date = (DateTime)_dtp.Value;

// use of the NullableDateTimePicker with databinding (Bind it to
// a DataSet):
DataSet _ds = new DataSet();
DataTable _dt = _ds.Tables.Add("Table");
_dt.Columns.Add("DateTimeColumn", typeof(DateTime));
_dt.Columns[0].AllowDBNull = true;

_dtp.DataBindings.Add("Value", _ds, "Table.DateTimeColumn");

For Users of CSLA.NET

I’m using CSLA.NET, an open source framework, for developing multi-tier applications. CSLA.NET comes with its own DateTime type a so called SmartDate. SmartDate converts DBNull.Values to either DateTime.MinValue or DateTime.MaxValue. So when working with CSLA.NET the NullableDateTimePicker as described above is not interesting, because when working with the SmartDate class the DateTimePicker should show the NullValue when the DateTime value of the SmartDate is either DateTime.MinValue or DateTime.MaxValue.

I’ve written another NullableDateTimePicker handling this situation. I wrote an article about it in my personal blog. Goto nullable DateTimePicker for CSLA.NET.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read