Creating a MonthPicker in Win Forms

When you’re working in WPF there is a greate DateTimePicker in the WPF Toolkit that quite easily can be turned in to a MonthPicker. In WinForms this doesn’t exist and there is no easy way of turning the existing DateTimePicker into a user friendly MonthPicker.

I wanted the user of the application which I’m working on to easily just choose year and month. There is nothing more horrible than two combo boxes next to each other. It slow, it requires two actions for each selection and it also ugly. I wanted to display year and all months on one big panel or something similar.

Well, this is what I came up with. Since I’ve implemented the control into an existing project I won’t be uploading a DLL for the single control, but I will be sharing the code so that you can build it yourself by just copy pasting the code:

End result:

A MonthPicker control
A MonthPicker control

Design:

private void InitializeComponent()
{
    this.groupBox1 = new System.Windows.Forms.GroupBox();
    this.lblDecember = new System.Windows.Forms.Label();
    this.lblNovember = new System.Windows.Forms.Label();
    this.lblOctober = new System.Windows.Forms.Label();
    this.lblSeptember = new System.Windows.Forms.Label();
    this.lblAugust = new System.Windows.Forms.Label();
    this.lblJuly = new System.Windows.Forms.Label();
    this.lblJune = new System.Windows.Forms.Label();
    this.lblMay = new System.Windows.Forms.Label();
    this.lblApril = new System.Windows.Forms.Label();
    this.lblMarch = new System.Windows.Forms.Label();
    this.lblFebruary = new System.Windows.Forms.Label();
    this.lblJanuary = new System.Windows.Forms.Label();
    this.lblNextYear = new System.Windows.Forms.Label();
    this.lblPreviousYear = new System.Windows.Forms.Label();
    this.lblYear = new System.Windows.Forms.Label();
    this.groupBox1.SuspendLayout();
    this.SuspendLayout();
    // 
    // groupBox1
    // 
    this.groupBox1.Controls.Add(this.lblDecember);
    this.groupBox1.Controls.Add(this.lblNovember);
    this.groupBox1.Controls.Add(this.lblOctober);
    this.groupBox1.Controls.Add(this.lblSeptember);
    this.groupBox1.Controls.Add(this.lblAugust);
    this.groupBox1.Controls.Add(this.lblJuly);
    this.groupBox1.Controls.Add(this.lblJune);
    this.groupBox1.Controls.Add(this.lblMay);
    this.groupBox1.Controls.Add(this.lblApril);
    this.groupBox1.Controls.Add(this.lblMarch);
    this.groupBox1.Controls.Add(this.lblFebruary);
    this.groupBox1.Controls.Add(this.lblJanuary);
    this.groupBox1.Controls.Add(this.lblNextYear);
    this.groupBox1.Controls.Add(this.lblPreviousYear);
    this.groupBox1.Controls.Add(this.lblYear);
    this.groupBox1.Location = new System.Drawing.Point(4, 4);
    this.groupBox1.Name = "groupBox1";
    this.groupBox1.Size = new System.Drawing.Size(205, 147);
    this.groupBox1.TabIndex = 0;
    this.groupBox1.TabStop = false;
    // 
    // lblDecember
    // 
    this.lblDecember.Cursor = System.Windows.Forms.Cursors.Hand;
    this.lblDecember.Location = new System.Drawing.Point(129, 117);
    this.lblDecember.Name = "lblDecember";
    this.lblDecember.Padding = new System.Windows.Forms.Padding(2);
    this.lblDecember.Size = new System.Drawing.Size(70, 20);
    this.lblDecember.TabIndex = 14;
    this.lblDecember.Tag = "12";
    this.lblDecember.Text = "December";
    this.lblDecember.Click += new System.EventHandler(this.month_Click);
    // 
    // lblNovember
    // 
    this.lblNovember.Cursor = System.Windows.Forms.Cursors.Hand;
    this.lblNovember.Location = new System.Drawing.Point(63, 117);
    this.lblNovember.Name = "lblNovember";
    this.lblNovember.Padding = new System.Windows.Forms.Padding(2);
    this.lblNovember.Size = new System.Drawing.Size(62, 20);
    this.lblNovember.TabIndex = 13;
    this.lblNovember.Tag = "11";
    this.lblNovember.Text = "November";
    this.lblNovember.Click += new System.EventHandler(this.month_Click);
    // 
    // lblOctober
    // 
    this.lblOctober.Cursor = System.Windows.Forms.Cursors.Hand;
    this.lblOctober.Location = new System.Drawing.Point(6, 117);
    this.lblOctober.Name = "lblOctober";
    this.lblOctober.Padding = new System.Windows.Forms.Padding(2);
    this.lblOctober.Size = new System.Drawing.Size(51, 20);
    this.lblOctober.TabIndex = 12;
    this.lblOctober.Tag = "10";
    this.lblOctober.Text = "October";
    this.lblOctober.Click += new System.EventHandler(this.month_Click);
    // 
    // lblSeptember
    // 
    this.lblSeptember.Cursor = System.Windows.Forms.Cursors.Hand;
    this.lblSeptember.Location = new System.Drawing.Point(129, 95);
    this.lblSeptember.Name = "lblSeptember";
    this.lblSeptember.Padding = new System.Windows.Forms.Padding(2);
    this.lblSeptember.Size = new System.Drawing.Size(70, 20);
    this.lblSeptember.TabIndex = 11;
    this.lblSeptember.Tag = "9";
    this.lblSeptember.Text = "September";
    this.lblSeptember.Click += new System.EventHandler(this.month_Click);
    // 
    // lblAugust
    // 
    this.lblAugust.Cursor = System.Windows.Forms.Cursors.Hand;
    this.lblAugust.Location = new System.Drawing.Point(63, 95);
    this.lblAugust.Name = "lblAugust";
    this.lblAugust.Padding = new System.Windows.Forms.Padding(2);
    this.lblAugust.Size = new System.Drawing.Size(62, 22);
    this.lblAugust.TabIndex = 10;
    this.lblAugust.Tag = "8";
    this.lblAugust.Text = "August";
    this.lblAugust.Click += new System.EventHandler(this.month_Click);
    // 
    // lblJuly
    // 
    this.lblJuly.Cursor = System.Windows.Forms.Cursors.Hand;
    this.lblJuly.Location = new System.Drawing.Point(6, 95);
    this.lblJuly.Name = "lblJuly";
    this.lblJuly.Padding = new System.Windows.Forms.Padding(2);
    this.lblJuly.Size = new System.Drawing.Size(51, 22);
    this.lblJuly.TabIndex = 9;
    this.lblJuly.Tag = "7";
    this.lblJuly.Text = "July";
    this.lblJuly.Click += new System.EventHandler(this.month_Click);
    // 
    // lblJune
    // 
    this.lblJune.Cursor = System.Windows.Forms.Cursors.Hand;
    this.lblJune.Location = new System.Drawing.Point(129, 73);
    this.lblJune.Name = "lblJune";
    this.lblJune.Padding = new System.Windows.Forms.Padding(2);
    this.lblJune.Size = new System.Drawing.Size(70, 20);
    this.lblJune.TabIndex = 8;
    this.lblJune.Tag = "6";
    this.lblJune.Text = "June";
    this.lblJune.Click += new System.EventHandler(this.month_Click);
    // 
    // lblMay
    // 
    this.lblMay.Cursor = System.Windows.Forms.Cursors.Hand;
    this.lblMay.Location = new System.Drawing.Point(63, 73);
    this.lblMay.Name = "lblMay";
    this.lblMay.Padding = new System.Windows.Forms.Padding(2);
    this.lblMay.Size = new System.Drawing.Size(62, 22);
    this.lblMay.TabIndex = 7;
    this.lblMay.Tag = "5";
    this.lblMay.Text = "May";
    this.lblMay.Click += new System.EventHandler(this.month_Click);
    // 
    // lblApril
    // 
    this.lblApril.Cursor = System.Windows.Forms.Cursors.Hand;
    this.lblApril.Location = new System.Drawing.Point(6, 73);
    this.lblApril.Name = "lblApril";
    this.lblApril.Padding = new System.Windows.Forms.Padding(2);
    this.lblApril.Size = new System.Drawing.Size(51, 22);
    this.lblApril.TabIndex = 6;
    this.lblApril.Tag = "4";
    this.lblApril.Text = "April";
    this.lblApril.Click += new System.EventHandler(this.month_Click);
    // 
    // lblMarch
    // 
    this.lblMarch.Cursor = System.Windows.Forms.Cursors.Hand;
    this.lblMarch.Location = new System.Drawing.Point(129, 51);
    this.lblMarch.Name = "lblMarch";
    this.lblMarch.Padding = new System.Windows.Forms.Padding(2);
    this.lblMarch.Size = new System.Drawing.Size(70, 20);
    this.lblMarch.TabIndex = 5;
    this.lblMarch.Tag = "3";
    this.lblMarch.Text = "March";
    this.lblMarch.Click += new System.EventHandler(this.month_Click);
    // 
    // lblFebruary
    // 
    this.lblFebruary.Cursor = System.Windows.Forms.Cursors.Hand;
    this.lblFebruary.Location = new System.Drawing.Point(63, 51);
    this.lblFebruary.Name = "lblFebruary";
    this.lblFebruary.Padding = new System.Windows.Forms.Padding(2);
    this.lblFebruary.Size = new System.Drawing.Size(62, 22);
    this.lblFebruary.TabIndex = 4;
    this.lblFebruary.Tag = "2";
    this.lblFebruary.Text = "February";
    this.lblFebruary.Click += new System.EventHandler(this.month_Click);
    // 
    // lblJanuary
    // 
    this.lblJanuary.Cursor = System.Windows.Forms.Cursors.Hand;
    this.lblJanuary.Location = new System.Drawing.Point(6, 51);
    this.lblJanuary.Name = "lblJanuary";
    this.lblJanuary.Padding = new System.Windows.Forms.Padding(2);
    this.lblJanuary.Size = new System.Drawing.Size(51, 22);
    this.lblJanuary.TabIndex = 3;
    this.lblJanuary.Tag = "1";
    this.lblJanuary.Text = "January";
    this.lblJanuary.Click += new System.EventHandler(this.month_Click);
    // 
    // lblNextYear
    // 
    this.lblNextYear.AutoSize = true;
    this.lblNextYear.Location = new System.Drawing.Point(180, 16);
    this.lblNextYear.Name = "lblNextYear";
    this.lblNextYear.Size = new System.Drawing.Size(19, 13);
    this.lblNextYear.TabIndex = 2;
    this.lblNextYear.Text = ">>";
    this.lblNextYear.Click += new System.EventHandler(this.lblNextYear_Click);
    // 
    // lblPreviousYear
    // 
    this.lblPreviousYear.AutoSize = true;
    this.lblPreviousYear.Location = new System.Drawing.Point(6, 16);
    this.lblPreviousYear.Name = "lblPreviousYear";
    this.lblPreviousYear.Size = new System.Drawing.Size(19, 13);
    this.lblPreviousYear.TabIndex = 1;
    this.lblPreviousYear.Text = "<<";
    this.lblPreviousYear.Click += new System.EventHandler(this.lblPreviousYear_Click);
    // 
    // lblYear
    // 
    this.lblYear.AutoSize = true;
    this.lblYear.Location = new System.Drawing.Point(87, 16);
    this.lblYear.Name = "lblYear";
    this.lblYear.Size = new System.Drawing.Size(31, 13);
    this.lblYear.TabIndex = 0;
    this.lblYear.Text = "0000";
    this.lblYear.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
    // 
    // MonthPicker
    // 
    this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    this.Controls.Add(this.groupBox1);
    this.Name = "MonthPicker";
    this.Size = new System.Drawing.Size(214, 157);
    this.groupBox1.ResumeLayout(false);
    this.groupBox1.PerformLayout();
    this.ResumeLayout(false);
 
}
 
#endregion
 
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.Label lblMarch;
private System.Windows.Forms.Label lblFebruary;
private System.Windows.Forms.Label lblJanuary;
private System.Windows.Forms.Label lblNextYear;
private System.Windows.Forms.Label lblPreviousYear;
private System.Windows.Forms.Label lblYear;
private System.Windows.Forms.Label lblDecember;
private System.Windows.Forms.Label lblNovember;
private System.Windows.Forms.Label lblOctober;
private System.Windows.Forms.Label lblSeptember;
private System.Windows.Forms.Label lblAugust;
private System.Windows.Forms.Label lblJuly;
private System.Windows.Forms.Label lblJune;
private System.Windows.Forms.Label lblMay;
private System.Windows.Forms.Label lblApril;
}

Code:

public partial class MonthPicker : UserControl
{
    public delegate void MonthPickerChangeHandler(MonthPickerChangeEventArgs e);
 
    private Font m_NotSelected;
    private Font m_Selected;
    private Label[] m_MonthLabels;
 
    public int Year { get; set; }
    public int Month { get; set; }
    public DateTime Value
    {
        get
        {
            return new DateTime(Year, Month, 01);
        }
        set
        {
            Year = value.Year;
            Month = value.Month;
        }
    }
 
    [Description("Occurs whenever a user changes date")]
    public event MonthPickerChangeHandler Change;
 
    protected void OnChange()
    {
        if (Change != null)
            Change(new MonthPickerChangeEventArgs(Year, Month));
    }
 
    public MonthPicker()
    {
        InitializeComponent();
 
        m_MonthLabels = new Label[]
        {
            lblJanuary,
            lblFebruary,
            lblMarch,
            lblApril,
            lblMay,
            lblJune,
            lblJuly,
            lblAugust,
            lblSeptember,
            lblOctober,
            lblNovember,
            lblDecember
        };
 
        m_NotSelected = new Font("Sans Serif", 8.25F, FontStyle.Regular);
        m_Selected = new Font("Sans Serif", 8.25F, FontStyle.Bold);
        Month = DateTime.Now.Month;
        Year = DateTime.Now.Year;
        lblYear.Text = Year.ToString();
        SetMonthLabelSelected(Month);
    }
 
    private void lblNextYear_Click(object sender, EventArgs e)
    {
        Year = (int.Parse(lblYear.Text) + 1);
        Month = 1; //Make sure next month is January
        lblYear.Text = Year.ToString();
        OnChange();
        SetMonthLabelSelected(Month);
    }
 
    private void lblPreviousYear_Click(object sender, EventArgs e)
    {
        Year = (int.Parse(lblYear.Text) - 1);
        Month = 12; //Make sure next month is December
        lblYear.Text = Year.ToString();
        OnChange();
        SetMonthLabelSelected(Month);
    }
 
    private void month_Click(object sender, EventArgs e)
    {
        Label label = sender as Label;
 
        if (label == null)
            return;
 
        Month = int.Parse(label.Tag.ToString());
        SetMonthLabelSelected(Month);
 
        OnChange();
    }
 
    /// <summary>
    /// Set the selected month label to the Selected Font, the
    /// rest to the Not Selected Font
    /// </summary>
    private void SetMonthLabelSelected(int month)
    {
        foreach (Label label in m_MonthLabels)
        {
            if (label.Tag.ToString() != month.ToString())
                label.Font = m_NotSelected;
            else
                label.Font = m_Selected;
        }
 
    }
}
 
public class MonthPickerChangeEventArgs
{
    public int Year { get; set; }
    public int Month { get; set; }
 
    public MonthPickerChangeEventArgs(int year, int month)
    {
        Year = year;
        Month = month;
    }
}

As a comment; I have three properties, Year, Month and Value to be used from the outside. Year and Month are quite self explanatory and Value is simply if the user wants it in DateTime format and if so, we supply it with the day always set to the first of the month.
We throw an change event to listen to which will fire every time either the month or the year is changed.

Hopefully this will help you if you’ve read all this 🙂

Adding comments to custom controls in the win form designer

As I was making a custom win form control I saw that my standard XML-comments wasn’t showing for my events in the win form designer. As I don’t use win forms that much I was a bit baffled, but thought that it ought to be possible to display standard help information just as the normal native controls do on their events.
And it was, but googling the issue was hard.

You have to use attributes to attach the information to your control or event in order for the Visual Studio designer to care. The attributes allows you to add a comment, a category and more to better display your component in the designer, here’s an example of an event with a comment that displays in the designer:

[Description("Occurs whenever a user changes date")]
public event MonthPickerChangeHandler Change;

This will display in the designer like this:

The comment is displayed in the designer
The comment is displayed in the designer

A list of the various attributes to use can be found here: http://msdn.microsoft.com/en-us/library/tk67c2t8(v=vs.100).aspx