• Welcome to TechPowerUp Forums, Guest! Please check out our forum guidelines for info related to our community.

A Custom DateTimePicker Control in C#

Kreij

Senior Monkey Moderator
Joined
Feb 6, 2007
Messages
13,817 (2.20/day)
Location
Cheeseland (Wisconsin, USA)
Welcome back to Uncle Kreij's Custom Control Series.
This time we are going to create a custom DateTimePicker that uses our Modified MonthCalendar.

This time I am not going to inherit the functionality of the default DateTimePicker, but instead create one from scratch
by using a MaskedTextBox and a Button.

So ... create a UserControl is VS and we can get started.

First we are going to need three controls in our UserControl.
A MaskedTextBox, Button, and our Modified MonthCalender
Code:
    [color=blue]private[/color] [color=teal]Button[/color] DropButton;
    [color=blue]private[/color] [color=teal]MaskedTextBox[/color] maskedTextBox1;
    [color=blue]private[/color] [color=teal]MonCal[/color] monCal = [color=blue]null[/color];

Now we need to instantiate and initialize our MaskedTextBox

Code:
    maskedTextBox1 = [color=blue]new[/color] [color=teal]MaskedTextBox[/color]();
    maskedTextBox1.Location = [color=blue]new[/color] [color=teal]Point[/color](0, 0);
    maskedTextBox1.Mask = [color=red]"00/00/0000"[/color];
    maskedTextBox1.Name = [color=red]maskedTextBox1"[/color];
    maskedTextBox1.Size = [color=blue]new[/color] [color=teal]Size[/color](139, 20);
    maskedTextBox1.TabIndex = 0;
    maskedTextBox1.ValidatingType = typeof([color=teal]DateTime[/color]);

The points of interest in the above code are the Mask and the ValidatingType.
These ensure that the text will always look like a short date string and the input must
validate as a type of DateTime.

Now we need to instantiate and initialize our Button. The button will be a child control of the
MaskedTextBox and used to make the modified MonthCalendar appear.
We will override its "Click" method so we can instantiate the MonthCalendar.
We will add the button to the MaskedTextBox control collection and then add
the MaskedTextBox to the UserControl control collection.

Code:
    DropButton = [color=blue]new[/color] [color=teal]Button[/color]();
    DropButton.Size = [color=blue]new[/color] [color=teal]Size[/color](16, 16);
    DropButton.FlatStyle = [color=teal]FlatStyle[/color].Standard;
    DropButton.BackColor = [color=teal]Color[/color].White;
    DropButton.BackgroundImage = [color=teal]Image[/color].FromFile([color=red]@"./CalendarIcon.ico"[/color]);
    DropButton.BackgroundImageLayout = [color=teal]ImageLayout[/color].Tile;
    DropButton.Location = [color=blue]new[/color] [color=teal]Point[/color](maskedTextBox1.Width - 20, 0);
    DropButton.Click += [color=blue]new[/color] [color=teal]EventHandler[/color](DropButton_Click);
    DropButton.Cursor = [color=teal]Cursors[/color].Arrow;
    maskedTextBox1.Controls.Add(DropButton);
    maskedTextBox1.Text = [color=teal]DateTime[/color].Now.ToString([color=red]"MM/dd/yyy"[/color]);

    [color=blue]this[/color].Controls.Add(maskedTextBox1);

Okay. Before we move on we need to do a couple of things.
1) Copy the MonCal class into the UserControl (Just the class, not the whole UserControl)
2) Copy the HighlightedDates class into the namespace (not into the UserControl)
(You could optionally put it in a seperate file in the same namespace if you like)

We now must make a couple of modifications to the MonCal class.
As it stands the class does not do anything other than display itself.
We want it to return the date that the user clicks on before it gets disposed of.

So in the varable declaration portion of the class, add the following;
Code:
    [color=blue]public delegate void[/color] [color=teal]MonCalControlHandler[/color]([color=blue]object[/color] sender, [color=teal]MonCalEventArgs[/color] e);
    [color=blue]public event[/color] [color=teal]MonCalControlHandler[/color] monCalControlHandler;

Add the following class in the namespace (but not inside any other class)
Code:
    [color=blue]public class[/color] [color=teal]MonCalEventArgs[/color] : [color=teal]EventArgs[/color]
    {
        [color=blue]private[/color] [color=teal]DateTime[/color] selectedDate;

        [color=blue]public[/color] MonCalEventArgs([color=teal]DateTime[/color] sentSelectedDate)
        {
            selectedDate = sentSelectedDate;
        }

        [color=blue]public string[/color] SelectedDate { [color=blue]get[/color] { [color=blue]return[/color] selectedDate.ToString([color=red]"MM/dd/yyyy"[/color]); } }
    }

What we have done is create a delegate that will raise an event when invoked.
That event can then be consumed by the calling method.
The event returns the date that the user selected as its event argument.

To invoke the event we will override the OnDateSelected event of the MonthCalendar and
raise our new event before disposing of the Calendar. (This goes inside the MonCal class)
Code:
[color=blue]protected override void[/color] OnDateSelected([color=teal]DateRangeEventArgs[/color] drevent)
{
    [color=blue]base[/color].OnDateSelected(drevent);
    [color=teal]MonCalEventArgs[/color] args = [color=blue]new[/color] [color=teal]MonCalEventArgs[/color](drevent.Start);
    monCalControlHandler([color=blue]this[/color], args);
    [color=blue]this[/color].Dispose();
}

Now we can get back to our modified DateTimePicker.
If you recall, we created an override for the button's click event.
We can now write the code to handle the click.
Code:
[color=blue]void[/color] DropButton_Click([color=blue]object[/color] sender,[color=teal]EventArgs[/color] e)
{
    [color=green]// The folowing 2 lines are just hardcoded data so we can see the control function.
    // This would normally be passed in as a List of HighlightedDates[/color]
    [color=teal]List[/color]<[color=teal]HighlightedDates[/color]> hlDates = [color=blue]new[/color] [color=teal]List[/color]<[color=teal]HighlightedDates[/color]>();
    hlDates.Add([color=blue]new[/color] [color=teal]HighlightedDates[/color]([color=teal]Convert[/color].ToDateTime(maskedTextBox1.Text.Trim()),
        [color=teal]Color[/color].Red, [color=teal]Color[/color].Blue, [color=teal]Color[/color].Pink, [color=blue]true[/color]));

    monCal = [color=blue]new[/color] [color=teal]MonCa[/color]l(hlDates);
    monCal.monCalControlHandler += 
        [color=blue]new[/color] [color=teal]MonCal[/color].[color=teal]MonCalControlHandler[/color](monCal_monCalControlHandler);
    monCal.Location = [color=blue]new[/color] [color=teal]Point[/color](20, 20);
    [color=blue]this[/color].Controls.Add(monCal);
}

You see that we create an instance of the Calendar and then override the event handler that we made in its class.
We can now write the method that handles consuming the event.
Code:
[color=blue]void[/color] monCal_monCalControlHandler([color=blue]object[/color] sender, [color=teal]MonCalEventArgs[/color] e)
{
    maskedTextBox1.Text = e.SelectedDate;
    [color=blue]this[/color].Controls.Remove(monCal);
    monCal = [color=blue]null[/color];
}

All we are doing here is setting the MaskedTextBox's text to the date returned from the calendar,
removing the calendar from the UserControl and setting the calendar variable to null (so we are not storing an invalid address for the disposed calendar).

That's it ! Piece of cake, right ?

Here is what it looks like when you launch the control


Here is after you click the drop down button


Since I chopped it into pieces to explain things, here is the full code listing for the control (minus the autogenerated UserControl code.)
(no color coding as I am tired of typing)
Code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace NFXControls
{
    public partial class NFXDateSelector : UserControl
    {
        private Button DropButton;
        private MaskedTextBox maskedTextBox1;
        private MonCal monCal = null;

        public NFXDateSelector()
        {
            InitializeComponent();
            this.SuspendLayout();

            maskedTextBox1 = new MaskedTextBox();
            maskedTextBox1.Location = new Point(0, 0);
            maskedTextBox1.Mask = "00/00/0000";
            maskedTextBox1.Name = "maskedTextBox1";
            maskedTextBox1.Size = new Size(139, 20);
            maskedTextBox1.TabIndex = 0;
            maskedTextBox1.ValidatingType = typeof(DateTime);

            DropButton = new Button();
            DropButton.Size = new Size(16, 16);
            DropButton.FlatStyle = FlatStyle.Standard;
            DropButton.BackColor = Color.White;
            DropButton.BackgroundImage = Image.FromFile(@"./CalendarIcon.ico");
            DropButton.BackgroundImageLayout = ImageLayout.Tile;
            DropButton.Location = new Point(maskedTextBox1.Width - 20, 0);
            DropButton.Click += new EventHandler(DropButton_Click);
            DropButton.Cursor = Cursors.Arrow;
            maskedTextBox1.Controls.Add(DropButton);
            maskedTextBox1.Text = DateTime.Now.ToString("MM/dd/yyy");

            this.Controls.Add(maskedTextBox1);

            this.ResumeLayout();
        }

        void DropButton_Click(object sender, EventArgs e)
        {
            List<HighlightedDates> hlDates = new List<HighlightedDates>();
            hlDates.Add(new HighlightedDates(Convert.ToDateTime(maskedTextBox1.Text.Trim()),
                Color.Red, Color.Blue, Color.Pink, true));

            monCal = new MonCal(hlDates);
            monCal.monCalControlHandler += 
                new MonCal.MonCalControlHandler(monCal_monCalControlHandler);
            monCal.Location = new Point(20, 20);
            this.Controls.Add(monCal);
        }

        void monCal_monCalControlHandler(object sender, MonCalEventArgs e)
        {
            maskedTextBox1.Text = e.SelectedDate;
            this.Controls.Remove(monCal);
            monCal = null;
        }

        internal class MonCal : MonthCalendar
        {
            protected static int WM_PAINT = 0x000F;
            private Rectangle dayBox;
            private int dayTop = 0;
            private SelectionRange range;

            private List<HighlightedDates> highlightedDates;

            public delegate void MonCalControlHandler(object sender, MonCalEventArgs e);
            public event MonCalControlHandler monCalControlHandler;

            public MonCal(List<HighlightedDates> HighlightedDates)
            {
                this.ShowTodayCircle = false;
                this.highlightedDates = HighlightedDates;
                range = GetDisplayRange(false);
                SetDayBoxSize();
                SetPosition(this.highlightedDates);

            }

            private void SetDayBoxSize()
            {
                int bottom = this.Height;

                while (HitTest(1, dayTop).HitArea != HitArea.Date &&
                    HitTest(1, dayTop).HitArea != HitArea.PrevMonthDate) dayTop++;

                while (HitTest(1, bottom).HitArea != HitArea.Date &&
                    HitTest(1, bottom).HitArea != HitArea.NextMonthDate) bottom--;

                dayBox = new Rectangle();
                dayBox.Size = new Size(this.Width / 7, (bottom - dayTop) / 6);
            }

            private void SetPosition(List<HighlightedDates> hlDates)
            {
                int row = 0, col = 0;

                hlDates.ForEach(delegate(HighlightedDates date)
                {
                    if (date.Date >= range.Start && date.Date <= range.End)
                    {
                        TimeSpan span = date.Date.Subtract(range.Start);
                        row = span.Days / 7;
                        col = span.Days % 7;
                        date.Position = new Point(row, col);
                    }
                });
            }

            protected override void WndProc(ref Message m)
            {
                base.WndProc(ref m);
                if (m.Msg == WM_PAINT)
                {
                    Graphics g = Graphics.FromHwnd(this.Handle);
                    PaintEventArgs pea =
                        new PaintEventArgs(g, new Rectangle(0, 0, this.Width, this.Height));
                    OnPaint(pea);
                }
            }

            protected override void OnPaint(PaintEventArgs e)
            {
                base.OnPaint(e);

                Graphics g = e.Graphics;
                Rectangle backgroundRect;

                highlightedDates.ForEach(delegate(HighlightedDates date)
                {
                    backgroundRect = new Rectangle(
                                date.Position.Y * dayBox.Width + 1,
                                date.Position.X * dayBox.Height + dayTop,
                                dayBox.Width, dayBox.Height);

                    if (date.BackgroundColor != Color.Empty)
                    {
                        using (Brush brush = new SolidBrush(date.BackgroundColor))
                        {
                            g.FillRectangle(brush, backgroundRect);
                        }
                    }

                    if (date.Bold || date.DateColor != Color.Empty)
                    {
                        using (Font textFont =
                            new Font(Font, (date.Bold ? FontStyle.Bold : FontStyle.Regular)))
                        {
                            TextRenderer.DrawText(g, date.Date.Day.ToString(), textFont,
                                backgroundRect, date.DateColor,
                                TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
                        }
                    }

                    if (date.BoxColor != Color.Empty)
                    {
                        using (Pen pen = new Pen(date.BoxColor))
                        {
                            Rectangle boxRect = new Rectangle(
                             date.Position.Y * dayBox.Width + 1,
                                date.Position.X * dayBox.Height + dayTop,
                                dayBox.Width, dayBox.Height);
                            g.DrawRectangle(pen, boxRect);
                        }
                    }
                });
            }

            protected override void OnDateSelected(DateRangeEventArgs drevent)
            {
                base.OnDateSelected(drevent);
                MonCalEventArgs args = new MonCalEventArgs(drevent.Start);
                monCalControlHandler(this, args);
                this.Dispose();
            }
        }
    }

    public class MonCalEventArgs : EventArgs
    {
        private DateTime selectedDate;

        public MonCalEventArgs(DateTime sentSelectedDate)
        {
            selectedDate = sentSelectedDate;
        }

        public string SelectedDate { get { return selectedDate.ToString("MM/dd/yyyy"); } }
    }

    public class HighlightedDates
    {
        public DateTime Date;
        public Point Position = new Point(0, 0);
        public Color DateColor;
        public Color BoxColor;
        public Color BackgroundColor;
        public Boolean Bold;

        public HighlightedDates(DateTime date)
        {
            this.Date = date;
            this.DateColor = this.BoxColor = this.BackgroundColor = Color.Empty;
            this.Bold = true;
        }

        public HighlightedDates(DateTime date, Color dateColor, Boolean bold)
        {
            this.Date = date;
            this.BackgroundColor = this.BoxColor = Color.Empty;
            this.DateColor = dateColor;
            this.Bold = bold;
        }

        public HighlightedDates(DateTime date, Color dateColor, Color boxColor,
            Color backgroundColor, Boolean bold)
        {
            this.Date = date;
            this.DateColor = dateColor;
            this.BoxColor = boxColor;
            this.BackgroundColor = backgroundColor;
            this.Bold = bold;
        }
    }  
}

Hopefully someone will find this code useful, and as always questions and comments are welcome.
Happy Coding !!
 
Last edited:
Top