Show calendar on click DateTimePicker control

4. May 2011

Something that’s common-place on the web is clicking a text box to pop-up a calendar. What if you wanted the calendar to pop-up when simply clicking on any area of a System.Windows.Forms.DateTimePicker control? You would expect the control to have a method to easily do this but when you notice there isn't even a public Click event things start looking bleak...

I found a way to get this to work by sending a "mouse button down/up" message to the control in the area where the regular display button is. By forcing this extra click in the right area it's as if you just clicked the button yourself. So I made an extension method with some parts borrowed which add an additional method on the System.Windows.Forms.DateTimePicker control.

We want to make a click happen here (in the red box):

datetimepicker

So we can use some native calls as well as basic math to find a good position to click it and send the message like so:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Brutal.Dev.Extenstions
{
  public static class DateTimePickerExtensions
  {
#if NETCF
    [DllImport("coredll")]
#else
    [DllImport("user32")]
#endif
    static extern int SendMessage(IntPtr hWnd, uint uMsg,
                                  int wParam, int lParam);

    private const int WM_LBUTTONDOWN = 0x0201;
    private const int WM_LBUTTONUP = 0x0202;

    public static void ShowCalendar(this DateTimePicker picker,
                                    MouseEventArgs clickEvent)
    {
      if (picker != null)
      {
        // Remove any existing event to prevent an infinite loop.
        var suppressor = new EventSuppressor(picker);
        suppressor.Suppress();

        // Get the position on the button.
        int x = picker.Width - 10;
        int y = picker.Height / 2;
        int lParam = x + y * 0x00010000;

        // Ignore if the calendar button was clicked
        if (clickEvent.X < picker.Width - 35)
        {
          SendMessage(picker.Handle, WM_LBUTTONDOWN, 1, lParam);
          SendMessage(picker.Handle, WM_LBUTTONUP, 1, lParam);
        }

        suppressor.Resume();
      }
    }
  }
}

Explanation

If you are using the .NET Compact Framework you need to import coredll instead for the P/Invoke for SendMessage so the compiler directive is there to support that.

The extra mouse click event info requirement is so that we can detect where the click happened and ignore it if it was actually on the calendar button (because otherwise it clicks again hiding the calendar – DOH!).

The EventSuppressor is something I picked up on this discussion to stop any other events from firing and also prevent infinite loops calling your mouse events over and over (the code for this in included in the sample code).

If you attach to the MouseDown or MouseUp event you can easily add one line to call the extension on you control and just like that, your calendar will pop-up:

using Brutal.Dev.Extenstions;

private void dateTimePicker1_MouseDown(object sender, MouseEventArgs e)
{    
  dateTimePicker1.ShowCalendar(e);
}

If you're a VB.NET kind of person, you can easily convert this code into VB.NET using this online tool which I’ve tried and it works pretty well.




.NET, C#, Windows Forms , ,

Comments (3) -

5/10/2011 11:15:43 AM #
This code works like magic.
Carlo
6/13/2011 7:25:59 AM #
As far as I know, this is doing the same;
(to add to contol '_Enter'
base.Focus();
SendKeys.SendWait("%{DOWN}");
6/14/2011 4:49:01 PM #
@Carlo: I did not know about the SendKeys class and the code does indeed work. Sometimes the simplest solutions elude us and I'm glad I learnt something new as well.

Since this is not a custom control though you can still send the keys in the MouseDown event and avoid the extra focus, the Enter event is a little strict and goes crazy (flickering) in on the compact framework for some reason.

You could use this simple code to get the calendar to show when clicking it.
private void dateTimePicker1_MouseDown(object sender, MouseEventArgs e)
{
  //dateTimePicker1.ShowCalendar(e);
  SendKeys.SendWait("%{DOWN}");
}

Add comment