Nullable DateTimePicker

I've recently come across an issue with the DateTimePicker component within the .net framework. The problem is basically that you cannot set the Value property of the object to nothing. This makes it impossible to bind to business objects where a null date field is needed (DateTime.MinValue or Nothing in VB). I thought I would document my solution in the hope that someone else may find it useful.

I spent some time hunting around on the internet and found several implementations of a NullableDateTimePicker which claimed to solve this problem. But they all seemed to be either overly complex or else flawed in some way that made them unsuitable for my requirements.

In the end I gave up on them and decided to write my own version of a NullableDateTimePicker. I'm sure it's not perfect. Handling the different date formats correctly will probably need some further work for example. But it meets my needs for the moment. The C# code listing is included below.

using System;
using System.Drawing;
using System.ComponentModel;
using System.Windows.Forms;
using System.Globalization;

public class NullableDateTimePicker : UserControl
{
    protected DateTimePicker Calendar = null;
    private bool _ignoreValueChange = false;
    private DateTimePickerFormat _format = DateTimePickerFormat.Short;
    private string _customFormat = string.Empty;
    private DateTime _value = DateTime.MinValue;

    public DateTimePickerFormat Format
    {
        get { return _format; }
        set
        {
            if (_format != value)
                _format = value;
        }
    }

    public string CustomFormat
    {
        get { return GetCustomFormat(); }
        set { _customFormat = value; }
    }

    public DateTime Value
    {
        get { return _value; }
        set
        {
            if (_value != value)
            {
                _value = value;
                if (_value == DateTime.MinValue)
                {
                    Calendar.CustomFormat = " ";
                }
                else
                {
                    Calendar.CustomFormat = this.CustomFormat;
                    _ignoreValueChange = true;
                    Calendar.Value = _value;
                    _ignoreValueChange = false;
                }
            }
        }
    }

    public NullableDateTimePicker()
        : base()
    {
        InitializeComponent();
    }

    private void InitializeComponent()
    {
        this.Calendar = new DateTimePicker();
        this.SuspendLayout();
        //
        // Calendar
        //
        this.Calendar.Dock = DockStyle.Fill;
        this.Calendar.Format = DateTimePickerFormat.Custom;
        this.Calendar.CustomFormat = " ";
        this.Calendar.Location = new Point(0, 0);
        this.Calendar.Name = "Calendar";
        this.Calendar.Size = new Size(91, 20);
        this.Calendar.TabIndex = 0;
        this.Calendar.ValueChanged += new System.EventHandler(this.Calendar_ValueChanged);
        this.Calendar.KeyUp += new KeyEventHandler(this.Calendar_KeyUp);
        //
        // NullableDateTimePicker
        //
        this.Controls.Add(this.Calendar);
        this.Margin = new Padding(0);
        this.Name = "NullableDateTimePicker";
        this.Size = new Size(91, 20);
        this.ResumeLayout(false);

    }

    public string GetCustomFormat()
    {
        string custom = string.Empty;
        if (this.Value == DateTime.MinValue)
        {
            custom = " ";
        }
        else
        {
            switch (this.Format)
            {
                case DateTimePickerFormat.Short:
                    custom = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
                    break;
                case DateTimePickerFormat.Long:
                    custom = CultureInfo.CurrentCulture.DateTimeFormat.LongDatePattern;
                    break;
                case DateTimePickerFormat.Time:
                    custom = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;
                    break;
                default:
                    custom = _customFormat;
                    break;
            }
        }

        return custom;
    }

    protected override void OnSizeChanged(EventArgs e)
    {
        base.OnSizeChanged(e);
        this.Height = 20;
    }

    private void Calendar_ValueChanged(object sender, EventArgs e)
    {
        if (!_ignoreValueChange)
            this.Value = Calendar.Value;
    }

    private void Calendar_KeyUp(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Delete)
            this.Value = DateTime.MinValue;
    }
}