# A Modified MonthCalendar Control in C#



## Kreij (Sep 5, 2008)

You have probably been thinking that old Uncle Kreij has been a bit quiet in the Programming
section lately. This should explain a bit about what I've been up to ....

I am writing a business management application for my wife's Massage Therapy business.
While working on the scheduling portion of the application, I wanted to use a
DateTimePicker (DTP) so that she could select dates to view in the scheduler. When you click
on the drop down part of the DTP, it displays a MonthCalendar control.
Unfortunately, the MonthCalendar control that is instantiated from the DTP does
not expose the methods and properties (like bolded dates) that the Normal MonthCalendar
control exposes.

So what is one to do?  Write your own custom control 
I figured that since I had to go through the trouble of writing a custom control, I might as well
add a bunch of funtionality to it.
My new MonthCalendar will allow the following:
..Bolded Dates
..Colored Dates
..Colored Date Backgrounds
..Colored Boxes surrounding the background.
..Any combination of the above.

Okay, first we need something to hold the date information that will be passed to the control
I chose to use an internal class (embedded in the UserControl). I could have used a struct type
but since I wanted it to have multiple constructors I felt that a class was more appropriate.
It is, however, completely valid to write a struct with multiple constructors.

```
[color=blue]internal class[/color] [color=teal]HighlightedDates[/color]
{
    [color=blue]public[/color] [color=teal]DateTime[/color] Date;
    [color=blue]public[/color] [color=teal]Point[/color] Position = [color=blue]new[/color] [color=teal]Point[/color](0, 0);
    [color=blue]public[/color] [color=teal]Color[/color] DateColor;
    [color=blue]public[/color] [color=teal]Color[/color] BoxColor;
    [color=blue]public[/color] [color=teal]Color[/color] BackgroundColor;
    [color=blue]public[/color] [color=teal]Boolean[/color] Bold;

    [color=green]// This constructor is used if you only want to make dates bold. All colors are set to "Empty"(null color)[/color]
    [color=blue]public[/color] HighlighedDates([color=teal]DateTime[/color] date);
    {
        [color=blue]this[/color].Date = date;
        [color=blue]this[/color].DateColor = [color=blue]this[/color].BoxColor = [color=blue]this[/color].BackgroundColor = [color=teal]Color[/color].Empty;
        [color=blue]this[/color].Bold = [color=blue]true[/color];
    }

    [color=green]// This constructor is used if you want colored and/or bolded dates[/color]
    [color=blue]public[/color] HighlighedDates([color=teal]DateTime[/color] date, [color=teal]Color[/color] dateColor, [color=teal]Boolean[/color] bold);
    {
        [color=blue]this[/color].Date = date;
        [color=blue]this[/color].DateColor = dateColor;
        [color=blue]this[/color].BoxColor = [color=blue]this[/color].BackgroundColor = [color=teal]Color[/color].Empty;
        [color=blue]this[/color].Bold = bold;
    }

    [color=green]// This constructor is used when you want to control everything[/color]
    [color=blue]public[/color] HighlightedDates([color=teal]DateTime[/color] date, [color=teal]Color[/color] dateColor, [color=teal]Color[/color] boxColor,
                [color=teal]Color[/color] backgroundColor, [color=teal]Boolean[/color] bold)
    {
        [color=blue]this[/color].Date = date;
        [color=blue]this[/color].DateColor = dateColor;
        [color=blue]this[/color].BoxColor = boxColor;
        [color=blue]this[/color].BackgroundColor = backgroundColor;
        [color=blue]this[/color].Bold = bold;
    }
}
```

Alrighty then, we have our class for date data. Now the fun begins.
We need to create a class that inherits the functionality of the MonthCalendar control
and override stuff so we can draw it the way we want. I named it MonCal. Pretty original, huh?

This class takes as input a typed List (List<T>) of the above class of date information.

The interesting part is that there is no easy way to determine exactly where we want to draw
on the control. The MonthCalendar control is divided into areas, but these areas cannot be accessed
through methods of anykind. Thankfully, we can determine where we are on the control by doing a HitTest
which when given a point on the control will return the area. There are three areas that are of interest to us.
The Date area, PrevMonthDate area, and NextMonthDate area. These are the areas which contain the actual
dates in the control. The rest of the areas are titles, button and other stuff.

The way we have to do the drawing is to override the WndProc method (which is a message pump) and
test for the WM_PAINT message. When we see it, trap on it and call our own custom OnPaint method.
Sounds like fun, right?

So let's do some code.

```
[color=blue]internal class[/color] [color=teal]MonCal[/color] : [color=teal]MonthCalendar[/color]
{
    [color=blue]protected static int[/color] WM_PAINT = 0x000F;
    [color=blue]private[/color] [color=teal]Rectangle[/color] dayBox;
    [color=blue]private int[/color] dayTop = 0;
    [color=blue]private[/color] [color=teal]SelectionRange[/color] range;

    [color=blue]private[/color] [color=teal]List[/color]<[color=teal]HighlightedDates[/color]> highlightedDates = [color=blue]new[/color] [color=teal]List[/color]<[color=teal]HighlightedDates[/color]>();

    [color=blue]public[/color] MonCal([color=teal]List[/color]<[color=teal]HighlightedDates[/color]> HighlightedDates)
    {
        [color=blue]this[/color].ShowTodayCircle = [color=blue]false[/color];               
        [color=blue]this[/color].highlightedDates = HighlightedDates;
        range = GetDisplayRange([color=blue]false[/color]);
        SetDayBoxSize();
        SetPosition([color=blue]this[/color].highlightedDates);
    }

    [color=green]// This method figures out the size of the entire date area portion of the control 
     //   and then divides it up o create a Rectagle for painting to individual dates[/color]
    [color=blue]private void[/color] SetDayBoxSize()
    {
        [color=blue]int[/color] bottom = [color=blue]this[/color].Height;

        [color=blue]while[/color] (HitTest(1, dayTop).HitArea != [color=eal]HitArea[/color].Date &&
            HitTest(1, dayTop).HitArea != [color=teal]HitArea[/color].PrevMonthDate) dayTop++;

        [color=blue]while[/color] (HitTest(1, bottom).HitArea != [color=teal]HitArea[/color].Date &&
            HitTest(1, bottom).HitArea != [color=teal]HitArea[/color].NextMonthDate) bottom--;

        dayBox = [color=blue]new[/color] [color=teal]Rectangle[/color]();
        dayBox.Size = [color=blue]new[/color] [color=teal]Size[/color]([color=blue]this[/color].Width / 7, (bottom - dayTop) / 6);
    }

    [color=green]// This method determines where in the 7 x 6 array of dates on the control our highlighted dates reside.[/color]
    [color=blue]private void[/color] SetPosition([color=teal]List[/color]<[color=teal]HighlightedDates[/color]> hlDates)
    {
        [color=blue]int[/color] row = 0, col = 0;

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

    [color=green]// This overrides the message pump and traps the WM_PAINT call[/color]
    [color=blue]protected override void[/color] WndProc([color=blue]ref[/color] [color=teal]Message[/color] m)
    {
        [color=blue]base[/color].WndProc([color=blue]ref[/color] m);
        [color=blue]if[/color] (m.Msg == WM_PAINT)
        {
            [color=teal]Graphics[/color] g = [color=teal]Graphics[/color].FromHwnd([color=blue]this[/color].Handle);
            [color=teal]PaintEventArgs[/color] pea =
                [color=blue]new[/color] [color=teal]PaintEventArgs[/color](g, [color=blue]new[/color] [color=teal]Rectangle[/color](0, 0, [color=blue]this[/color].Width, [color=blue]this[/color].Height));
            OnPaint(pea);
        }
    }

    [color=green]// Here is where we use our information to selectively draw what we want[/color]
    [color=blue]protected override void[/color] OnPaint([color=teal]PaintEventArgs[/color] e)
    {
        [color=blue]base[/color].OnPaint(e);

        [color=eal]Graphics[/color] g = e.Graphics;
        [color=teal]Rectangle[/color] backgroundRect;

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

            [color=blue]if[/color] (date.BackgroundColor != [color=teal]Color[/color].Empty)
            {
                [color=blue]using[/color] ([color=teal]Brush[/color] brush = [color=blue]new[/color] [color=teal]SolidBrush[/color](date.BackgroundColor))
                {
                    g.FillRectangle(brush, backgroundRect);
                }

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

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

Hey!  That wasn't so bad.
Now all we have to do is make a UserControl that contains and utilizes are classes.


```
[color=blue]public partial class[/color] [color=teal]NFXMonthCalendar[/color] : [color=teal]UserControl[/color]
{
    [color=blue]private[/color] [color=teal]List[/color]<[color=teal]HighlightedDates[/color]> highLightedDates;

    [color=blue]public[/color] NFXMonthCalendar()
    {
        InitializeComponent();
         
        [color=green]// Dates would normally be passed in, in a List. For testing purposes I added the next declaration[/color]
        highLightedDates.Add([color=blue]new[/color] [color=teal]HighlightedDates[/color]([color=teal]Convert[/color].ToDateTime([color=red]"9/1/2008"[/color]), 
                    [color=teal]Color[/color].Red, [color=teal]Color[/color].Blue, [color=teal]Color[/color].Pink, [color=blue]true[/color]));

            [color=teal]MonCal[/color] mCal = [color=blue]new[/color] [color=teal]MonCal[/color](highLightedDates);
            [color=blue]this[/color].Controls.Add(mCal);
        }
    }

    [color=green]// Add MonCal class here[/color]

    [color=green]// Add HighlightedDates class here[/color]
}
```

Since the UserControl was generated in VS, there is some garbage collection and control iniialization
that is not included here.

Here is the ouput in the VS Control tester. 
9-1-2008 is our highlighted date. 9-5-2008 is todays date and automatically highlighted by the base MonthCalendar functionaliy,






I have not gotten too detailed on how everything works, so if anyone has any questions or comments or code corrections, feel free to post.

I hope you enjoyed my little tutorial on modifying a MonthCalendar.
Next I will generate a custom DTP that will use this modified MonthCalendar as its drop down.

Have fun coding !!


----------



## Kreij (Sep 5, 2008)

*Update:*

Sometimes yer old Uncle Kreij just ain't thinkin' ...

The original code above used an array of type HighlightedDates as input.
This worked fine, the only problem is that an array in C# is static in size.
That means that means that when you initialize the array you have to know how many array elements you are going to need or you need to initialize the array with the data.

For instance;
string[] myStringArray = new string[5];  (an array that will hold 5 strings)
or
string[] myStringArray = new string[]{ "hello", "how", "are", "you" }; (4 string array)

Since I do not know how many dates will need to be highlighted (that information will be taken from the scheduling database) it makes much more sense to use a typed list (List<T>) that can grow dynamically. You can then just use the List<T>.Add method to insert another entry into the list.  The List<T>.ForEach(action) method also makes it pretty easy to iterate through the List.

The code in the original post has been changed to reflect this.


----------



## cishumate (Nov 7, 2008)

I am really trying to learn this C# stuff. So I have to ask the dumb question. How do I get your control on my form and pass it a few dates to highlight? 

I thank you in advance for your reply and for creating the control that I so desperately need.


----------



## wolf2009 (Nov 7, 2008)

How old is uncle Kreij ?


----------



## FordGT90Concept (Nov 8, 2008)

Kreij said:


> The original code above used an array of type HighlightedDates as input.
> This worked fine, the only problem is that an array in C# is static in size.
> That means that means that when you initialize the array you have to know how many array elements you are going to need or you need to initialize the array with the data.
> 
> ...


In complex code, I usually require a static-length array for input because they are easier on the memory.  By doing so, it just requires users to use a few extra lines.  Something like this:

```
// Make the strongly-typed collection.
List<string> templist = new List<string>();
templist.Add("hello");
templist.Add("how");
templist.Add("are");
templist.Add("you");

// Convert the dynamic List to a static array.
string[] array = templist.ToArray();

// Initialize the class with the array.
MonthCalendar cal = new MonthCalendar(array);
```

The above code is not test and is provided just as an example of how easy it is to turn dynamic to static.

It should also be noted that Lists can occassionally cause issues when used in multithreading.  I usually use a static array when changing the length of the array could cause a catastrophic failure while in execution.  I use Lists most of the time because the performance of them is actually quite respectable.  Not excellant, but respectable.  I loaded up over 22 MiB into Lists up to 6 dimensions deep and it wades through the whole lot in under a minute.




cishumate said:


> How do I get your control on my form and pass it a few dates to highlight?


Heh, he needs to attach the *.cs files (hint, hint)... XD


----------



## Kreij (Nov 8, 2008)

True, but I really had no need for the static array at all. The dynamic list served the purpose much better, and you can always set the initial size of the list to a lower value if you do not think you will need a lot of space.

@cishumate : When you create the control in a control library, you just have to add the DLL in your project, and then include the namespace in the "using" statements at the top of your form. If you need more details let me know.

@Wolf2009 : I'll be 50 years old next year. Come on over to WI and see if you can cut more wood with a chainsaw, out ride me on an ATV or drink more beer.


----------



## FordGT90Concept (Nov 8, 2008)

Kreij said:


> True, but I really had no need for the static array at all. The dynamic list served the purpose much better, and you can always set the initial size of the list to a lower value if you do not think you will need a lot of space.


Lists have more overhead no matter what.  I almost always use Lists because I won't want to establish a fixed maximum for anything.  It's a bad habit of mine. 




Kreij said:


> @Cishumate : When you create the control in a control library, you just have to add the DLL in your project, and then include the namespace in the "using" statements at the top of your form. If you need more details let me know.


I don't see any attachments.


----------



## Kreij (Nov 8, 2008)

Yeah, the lists have more overhead, but if your application is not so memory intesive that it requires it they are much more versatile. But also, the conversion of a list to an array (and back if needed) is also quite expensive.

As far as an attachment, I did not include the source for my project.
It's up to you guys to make your own stuff and learn from it. I'm always willling to help in any way that I can, but if I just post plug-in code people don't learn squat. 
My code is not proprietary, and I could post it, but use your own minds people !!
No one learns when given all the answers, but they do learn when given a start.


----------



## FordGT90Concept (Nov 8, 2008)

Kreij said:


> As far as an attachment, I did not include the source for my project.


You mentioned a DLL but I don't see anything attached.


----------



## Kreij (Nov 8, 2008)

You are correct. 
People can create a control library and compile it as a DLL.
Then all they have to do is include the DLL in their project.
I'm just posting code for learning, not finished solutions.


----------



## FordGT90Concept (Nov 8, 2008)

So, why not release the source or binary in order to save those that need the finished product without taking the time to author their own?

A working example with either thorough commenting or an accompanied tutorial is a better method of learning than a tutorial alone.


----------



## Kreij (Nov 8, 2008)

@Ford : I completely understand what you are saying, but much of the code that I post here has limited testing and I do not want to drop something on people only for them to say, "that sucks".
I present code that is proof of a concept that I am working on and works in a limited environment and state.
My goal on this section of the forums is not to give out authored code, but to present a concept I've worked on, show problems that I've solved and get people to understand that coding isn't a chore, it's an adventure (cue cool adventure music).

Thanks for you input and I never considered your posts as fighting words.
I'm always happy to work with anyone on anything to assist them.


----------



## FordGT90Concept (Nov 8, 2008)

Hmm...

I respect what you are trying to do but...

I'll leave it at that.


----------



## cishumate (Nov 8, 2008)

Thank you Kreij, I have never done a control before. I will see if there is a project in my Visual Studio 2008 that lets me do that.

I have had great results using Lists to store info in a custom object quickly and easily. I write programs for a small company and so far programming by the seat of my pants has done me well. Glad to hear you have some experience behind you.


----------



## lemonadesoda (Nov 8, 2008)

Knowledge Share. Top marks. +1


----------



## Kreij (Nov 8, 2008)

@ Cish : There is a project for creating a control library in VS2008. That is what I use to write code. BTW, seat of the pants programming is the most fun  I'm not a professional coder and do it for a small company as part of my IT Manager position. I am currently writing a full ERP system (quoting, work orders, purchase orders, labor tracking, etc. etc.) for them. It's lots of fun as long as they don't give you a deadline (which I told them was out of the question since I manage the networks also).

@Ford : Another reason that I post snippets and not full projects is that my I cannot be sure what level of framework people are using. I am running on .Net 3.5 and a lot of people simply do not pull the latest frameworks (I do for coding reasons). That would cause the code to fuss at them and they would have to download anything they do not have just to get things to run.

@Lemon : Thanks. If we gain knowledge and are unwilling to share it, how does that benefit the next generation that will follow?


----------



## FordGT90Concept (Nov 8, 2008)

I use .NET 3.5 as well but, if I'm concerned about compatibility, I'll use .NET 2.0.  I have yet to use anything .NET 3.5 has added on top of .NET 2.0 so it really doesn't make much difference to me.  .NET 1.1 is virtually dead.


----------



## Kreij (Nov 8, 2008)

You're right. I could just target the compilation at .net 2.0.
Guess I'm just too lazy to make multiple versions.


----------



## FordGT90Concept (Nov 8, 2008)

Heh, and I'm too lazy to comment and/or explain code. XD


----------



## Smurfman87 (Dec 19, 2008)

*Code Question*

Kreij,
I have been messing around with your code example, and basically this seems to be exactly what I am trying to acomplish.

So I built a simple form control in C# (VS2008) and tried to re-create your example.

I am not sure why but I am getting wierd results.  One thing is that I created a testForm in my project to add the NFXMonthCalendar to the form.  It adds it... but the Date you have hard coded of Dec 1, 2008 is always present on the top left date of the calendar control, it seems too that it is behaving more like the arror button that moves back and forth.

I even converted the code to VB and I still get the same results.

In both cases, I did have to do some modifications to make the code work too.

I wonder if you can clairify if the code has some bugs in it and if I modified it correctly.

For example:

(1) The semi-colon would not compile, and even when removed I had some compile issues so I was forced to remove these two sections and leave only the third.  In VB I was able to make it work.

```
public HighlighedDates(DateTime date);

public HighlighedDates(DateTime date, Color dateColor, Boolean bold);
```

(2) There seemed to be a missing semi-colon here, so I added one...

```
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);
                }

************missing semi-colon ? **********

            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);
                }
            }
        });
```

(3) This statement was causing a null exception when trying to add the control to the form.


```
private List<HighlightedDates> highLightedDates;
```

I changed to 


```
private List<HighlightedDates> highLightedDates = new List<HighlightedDates>();
```

This got me further along, but again with wierd things going on.

Also, it seems that even with adding the control to the form, nothing is exposed, meaning I can't change the properties of the MonthCalendar in this case MonCal, things like the size ie (4,3) for a full year to show.  Also once added, I can't access it to be able to add dates to it.

If you can assist it would be greatly appreciated.  I loved how simple the example was, which helping in making a similar VB example.  I found one other one but it was way too complicated a control.

Thanks
J


----------



## Kreij (Dec 30, 2008)

Yes, there appear to be a couple of typos in the code I posted. I did this one manually and did not copy and paste.

(1) The first two constructors should not have semicolons after the parenthesis. The constructors should work if you remove the semicolons.

(2) Yup, a bracket is missing. There should be no semicolon, but there shoudl be a closing bracket.

Not sure about the null exception, I did not get that. I will have to go back and look at the code as I have not done anything with this lately. I did not post every single line of code so that could be an initializer or something that I did not include to keep the post from getting too long.

Not sure why you cannot access the properties either.

I'll take a look at it when I get a chance.


----------



## m64_khosravi (Oct 29, 2010)

*Source Code*

Can I have a sample of your sourec Code please?
Email: m64_khosravi@yahoo.com
Thanks so much


----------

