Writing a NULLable DateTimePicker in C#

Gorkem YukselDirector, IT Development
CERTIFIED EXPERT
Published:
There are many posts on the internet about creating a simple and easy nullable DateTimePicker for use in your .Net applications. However, I have noticed that most have gone far beyond the "simple" aspect of this and ventured into the more "complex" avenues.

This brings me to my post on a very simple and basic nullable DateTimePicker object.  It is really easy to modify to suit your needs if the date formats do not match what you need.  It is based on the simple notion that if any date is less than the year 1900, it will treat it as a NULL value.

The trick to this control however, is NOT using the inherited Value property of the control, but to use the new m_Value property.  It is cast as a DateTime? which allows you a little bit more flexibility.

We start the nullable calendar with inheriting from the DateTimePicker native control, and setting some of our customizable private members.  

The mValue variable is used internally to hold the actual date/time that we want to pass along.  CustomDateFormat is the format of the date as it will be displayed in this control -- you can change this to be any compatible date format.  NoDateFormat is the format of the date/text as it will be displayed if the mValue member is null.  Lastly, the "null" date value that we will parse for is set to 1753-01-01.  This will indicate to our control that the date value should appear as NULL.

public class SuperCalendar : DateTimePicker
                          {
                              private DateTime? mValue = null;  
                              private string CustomDateFormat = "MMM dd / yyyy";
                              private string NoDateFormat = "--- -- / ----";
                              private DateTime NullDate = DateTime.Parse("1753-01-01");
                      
                              public SuperCalendar() : base() { }

Open in new window


I have used a property called 'm_value' as the actual date property we will use in our application.  You can give this any name you want, I have decided to use 'm_value' for my purposes, but really anything will do.  The premise behind this is quite simple.  We allow a GET and SET on this property.  The GET will simply get the value of our private mValue member.  The SET will accept NULL as a value and will either set the Value property with the NULL date identifier or an actual date.

        public DateTime? m_value
                              {
                                  get
                                  {
                                      return mValue;
                                  }
                                  set
                                  {
                                      if (value != null)
                                      {
                                          this.Value = value.Value;
                                      }
                                      else
                                      {
                                          this.Value = NullDate;
                                      }
                                  }
                              }

Open in new window


The magic of this control happens by overriding the OnDropDown, OnCloseUp and WndProc events/functions.  Overriding these allows us to set the displayed date on the calendar dropdown and also obtain the selected date (if any).  So this is somewhat the tricky part of the control.  You have to understand that the BASE.VALUE and THIS.VALUE are handled separately.  Knowing this, the calendar dropdown uses the Base.Value property to set the displayed date.  Because the date can be null, we don't want to display 1753-01-01 as the starting date - this can be somewhat irritating for users, so we set the Base.Value to the current date.

By overriding the WndProc function, we can capture the moment where a user selects a date from the dates shown.  Trapping this allows us to set the This.Value with the newly selected date from base.Value.

        protected override void OnDropDown(EventArgs eventargs)
                              {
                                  if (Value.Year < 1900)
                                  {
                                      base.Value = DateTime.Now;
                                  } else {
                                      base.Value = mValue;
                                  }
                                  base.OnDropDown(eventargs);
                              }
                      
                              protected override void OnCloseUp(EventArgs eventargs)
                              {
                                 base.OnCloseUp(eventargs);
                              }
                      
                              protected override void WndProc(ref Message m)
                              {
                                  if (m.Msg == 0x4e)                        
                                  {
                                      NMHDR nm = (NMHDR)m.GetLParam(typeof(NMHDR));
                                      if (nm.Code == -746 || nm.Code == -722)
                                      {
                                          this.Value = base.Value;
                                      }
                                  }
                                  base.WndProc(ref m);
                              }

Open in new window


Now for the last step in our Nullable DateTimePicker.  We have now received our new date or set it from our application.  In any event, we now have to override the Value property so we can control the nullibility of our date.  Basically, in this property, we are returning 1753-01-01 if our mValue member is null and the actual date if we have a real date.  When setting this value, we are checking to see if the date passed in is less than the year 1900.  If so, we are determining that it should be a null date and thereby setting our private mValue member as NULL and the CustomFormat to our NoDateFormat, otherwise, we are setting our mValue with a real date and setting our CustomFormat to be our CustomDateFormat.  

public new DateTime Value
                              {
                                  get
                                  {
                                      if (mValue == null)
                                      {
                                          return NullDate;                    
                                      }
                                      else
                                      {
                                          return mValue.Value;
                                      }
                                  }
                                  set
                                  {
                                      if (value.Year < 1900)
                                      {
                                          mValue = null;
                                          CustomFormat = NoDateFormat;
                                      }
                                      else
                                      {
                                          mValue = value;
                                          base.Value = value;
                                          CustomFormat = CustomDateFormat;
                                      }
                                  }
                              }

Open in new window


This is it! Just a few overridden functions/methods and you now have a fully functional Nullable DateTimePicker - aptly named "SuperCalendar".

I have provided the full source below for reference.  I hope this post is useful and a good starting point to more complex iterations of this "Super" control.

public class SuperCalendar : DateTimePicker
                          {
                              private DateTime? mValue = null;
                              private string CustomDateFormat = "MMM dd / yyyy";
                              private string NoDateFormat = "--- -- / ----";
                              private DateTime NullDate = DateTime.Parse("1753-01-01");
                      
                              public SuperCalendar() : base() { }
                      
                              public DateTime? m_value
                              {
                                  get
                                  {
                                      return mValue;
                                  }
                                  set
                                  {
                                      if (value != null)
                                      {
                                          this.Value = value.Value;
                                      }
                                      else
                                      {
                                          this.Value = NullDate;
                                      }
                                  }
                              }
                      
                              protected override void OnDropDown(EventArgs eventargs)
                              {
                                  if (Value.Year < 1900)
                                  {
                                      
                                  }
                                  base.Value = DateTime.Now;
                                  base.OnDropDown(eventargs);
                              }
                      
                              protected override void OnCloseUp(EventArgs eventargs)
                              {
                                 base.OnCloseUp(eventargs);
                              }
                      
                              protected override void WndProc(ref Message m)
                              {
                                  if (m.Msg == 0x4e)                        
                                  {
                                      NMHDR nm = (NMHDR)m.GetLParam(typeof(NMHDR));
                                      if (nm.Code == -746 || nm.Code == -722)
                                      {
                                          this.Value = base.Value;
                                      }
                                  }
                                  base.WndProc(ref m);
                              }
                      
                              public new DateTime Value
                              {
                                  get
                                  {
                                      if (mValue == null)
                                      {
                                          return NullDate;                    
                                      }
                                      else
                                      {
                                          return mValue.Value;
                                      }
                                  }
                                  set
                                  {
                                      if (value.Year < 1900)
                                      {
                                          mValue = null;
                                          CustomFormat = NoDateFormat;
                                      }
                                      else
                                      {
                                          mValue = value;
                                          base.Value = value;
                                          CustomFormat = CustomDateFormat;
                                      }
                                  }
                              }
                      
                              protected override void OnKeyDown(KeyEventArgs e)
                              {
                                  base.OnKeyDown(e);
                      
                                  if (e.KeyCode == Keys.Delete)
                                      this.Value = DateTime.Parse("1753-01-01");
                      
                              }
                      
                              [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
                              private struct NMHDR
                              {
                                  public IntPtr HwndFrom;
                                  public int IdFrom;
                                  public int Code;
                              }
                          }

Open in new window

0
10,352 Views
Gorkem YukselDirector, IT Development
CERTIFIED EXPERT

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.