![]() |
|
|
#1 |
|
Hardcore Monkey Moderator
Join Date: Feb 2007
Location: Cheeseland (Wisconsin, USA)
Posts: 12,133 (5.27/day)
Thanks: 591
Thanked 5,494 Times in 2,938 Posts
|
A Modified MonthCalendar Control in C#
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. Code:
internal class HighlightedDates
{
public DateTime Date;
public Point Position = new Point(0, 0);
public Color DateColor;
public Color BoxColor;
public Color BackgroundColor;
public Boolean Bold;
// This constructor is used if you only want to make dates bold. All colors are set to "Empty"(null color)
public HighlighedDates(DateTime date);
{
this.Date = date;
this.DateColor = this.BoxColor = this.BackgroundColor = Color.Empty;
this.Bold = true;
}
// This constructor is used if you want colored and/or bolded dates
public HighlighedDates(DateTime date, Color dateColor, Boolean bold);
{
this.Date = date;
this.DateColor = dateColor;
this.BoxColor = this.BackgroundColor = Color.Empty;
this.Bold = bold;
}
// This constructor is used when you want to control everything
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;
}
}
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. Code:
internal class MonCal : MonthCalendar
{
protected static int WM_PAINT = 0x000F;
private Rectangle dayBox;
private int dayTop = 0;
private SelectionRange range;
private List<HighlightedDates> highlightedDates = new List<HighlightedDates>();
public MonCal(List<HighlightedDates> HighlightedDates)
{
this.ShowTodayCircle = false;
this.highlightedDates = HighlightedDates;
range = GetDisplayRange(false);
SetDayBoxSize();
SetPosition(this.highlightedDates);
}
// 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
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);
}
// This method determines where in the 7 x 6 array of dates on the control our highlighted dates reside.
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);
}
});
}
// This overrides the message pump and traps the WM_PAINT call
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);
}
}
// Here is where we use our information to selectively draw what we want
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);
}
}
});
}
}
Now all we have to do is make a UserControl that contains and utilizes are classes. Code:
public partial class NFXMonthCalendar : UserControl
{
private List<HighlightedDates> highLightedDates;
public NFXMonthCalendar()
{
InitializeComponent();
// Dates would normally be passed in, in a List. For testing purposes I added the next declaration
highLightedDates.Add(new HighlightedDates(Convert.ToDateTime("9/1/2008"),
Color.Red, Color.Blue, Color.Pink, true));
MonCal mCal = new MonCal(highLightedDates);
this.Controls.Add(mCal);
}
}
// Add MonCal class here
// Add HighlightedDates class here
}
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 !!
__________________
Cloud (noun, singular): A dynamic arrangement of multiple potential single points of failure, with a user at one end and their data at the other. Get more tech news on a wide variety of topics at NextPowerUp
Last edited by Kreij; Sep 5, 2008 at 08:00 PM. Reason: Sumtimes I are stoopid |
|
|
|
| The Following 2 Users Say Thank You to Kreij For This Useful Post: |
|
|
#2 |
|
Hardcore Monkey Moderator
Join Date: Feb 2007
Location: Cheeseland (Wisconsin, USA)
Posts: 12,133 (5.27/day)
Thanks: 591
Thanked 5,494 Times in 2,938 Posts
|
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.
__________________
Cloud (noun, singular): A dynamic arrangement of multiple potential single points of failure, with a user at one end and their data at the other. Get more tech news on a wide variety of topics at NextPowerUp
|
|
|
|
|
|
#3 |
|
Join Date: Nov 2008
Posts: 2 (0.00/day)
Thanks: 1
Thanked 0 Times in 0 Posts
|
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. |
|
|
|
|
|
#4 |
|
Guest
Posts: n/a (0/day)
|
How old is uncle Kreij ?
|
|
|
|
#5 | |
|
"I go fast!1!11!1!"
Join Date: Oct 2008
Location: IA, USA
Posts: 10,583 (6.28/day)
Thanks: 1,755
Thanked 2,601 Times in 1,963 Posts
|
Quote:
Code:
// 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);
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. Heh, he needs to attach the *.cs files (hint, hint)... XD
__________________
Golden Rule of Programming: Never assume. try { SteamDownload(); } catch (Steamception ex) { RageQuit(); } Last edited by FordGT90Concept; Nov 8, 2008 at 02:20 AM. |
|
|
|
|
|
|
#6 |
|
Hardcore Monkey Moderator
Join Date: Feb 2007
Location: Cheeseland (Wisconsin, USA)
Posts: 12,133 (5.27/day)
Thanks: 591
Thanked 5,494 Times in 2,938 Posts
|
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.
__________________
Cloud (noun, singular): A dynamic arrangement of multiple potential single points of failure, with a user at one end and their data at the other. Get more tech news on a wide variety of topics at NextPowerUp
|
|
|
|
| The Following User Says Thank You to Kreij For This Useful Post: |
|
|
#7 | ||
|
"I go fast!1!11!1!"
Join Date: Oct 2008
Location: IA, USA
Posts: 10,583 (6.28/day)
Thanks: 1,755
Thanked 2,601 Times in 1,963 Posts
|
Quote:
![]() Quote:
__________________
Golden Rule of Programming: Never assume. try { SteamDownload(); } catch (Steamception ex) { RageQuit(); } |
||
|
|
|
|
|
#8 |
|
Hardcore Monkey Moderator
Join Date: Feb 2007
Location: Cheeseland (Wisconsin, USA)
Posts: 12,133 (5.27/day)
Thanks: 591
Thanked 5,494 Times in 2,938 Posts
|
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.
__________________
Cloud (noun, singular): A dynamic arrangement of multiple potential single points of failure, with a user at one end and their data at the other. Get more tech news on a wide variety of topics at NextPowerUp
|
|
|
|
|
|
#9 |
|
"I go fast!1!11!1!"
Join Date: Oct 2008
Location: IA, USA
Posts: 10,583 (6.28/day)
Thanks: 1,755
Thanked 2,601 Times in 1,963 Posts
|
You mentioned a DLL but I don't see anything attached.
__________________
Golden Rule of Programming: Never assume. try { SteamDownload(); } catch (Steamception ex) { RageQuit(); } |
|
|
|
|
|
#10 |
|
Hardcore Monkey Moderator
Join Date: Feb 2007
Location: Cheeseland (Wisconsin, USA)
Posts: 12,133 (5.27/day)
Thanks: 591
Thanked 5,494 Times in 2,938 Posts
|
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.
__________________
Cloud (noun, singular): A dynamic arrangement of multiple potential single points of failure, with a user at one end and their data at the other. Get more tech news on a wide variety of topics at NextPowerUp
|
|
|
|
|
|
#11 |
|
"I go fast!1!11!1!"
Join Date: Oct 2008
Location: IA, USA
Posts: 10,583 (6.28/day)
Thanks: 1,755
Thanked 2,601 Times in 1,963 Posts
|
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.
__________________
Golden Rule of Programming: Never assume. try { SteamDownload(); } catch (Steamception ex) { RageQuit(); } |
|
|
|
|
|
#12 |
|
Hardcore Monkey Moderator
Join Date: Feb 2007
Location: Cheeseland (Wisconsin, USA)
Posts: 12,133 (5.27/day)
Thanks: 591
Thanked 5,494 Times in 2,938 Posts
|
@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.
__________________
Cloud (noun, singular): A dynamic arrangement of multiple potential single points of failure, with a user at one end and their data at the other. Get more tech news on a wide variety of topics at NextPowerUp
|
|
|
|
|
|
#13 |
|
"I go fast!1!11!1!"
Join Date: Oct 2008
Location: IA, USA
Posts: 10,583 (6.28/day)
Thanks: 1,755
Thanked 2,601 Times in 1,963 Posts
|
Hmm...
I respect what you are trying to do but... I'll leave it at that.
__________________
Golden Rule of Programming: Never assume. try { SteamDownload(); } catch (Steamception ex) { RageQuit(); } |
|
|
|
|
|
#14 |
|
Join Date: Nov 2008
Posts: 2 (0.00/day)
Thanks: 1
Thanked 0 Times in 0 Posts
|
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. |
|
|
|
|
|
#15 |
|
Eligible for custom title
|
Knowledge Share. Top marks. +1
|
|
|
|
|
|
#16 |
|
Hardcore Monkey Moderator
Join Date: Feb 2007
Location: Cheeseland (Wisconsin, USA)
Posts: 12,133 (5.27/day)
Thanks: 591
Thanked 5,494 Times in 2,938 Posts
|
@ 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?
__________________
Cloud (noun, singular): A dynamic arrangement of multiple potential single points of failure, with a user at one end and their data at the other. Get more tech news on a wide variety of topics at NextPowerUp
|
|
|
|
|
|
#17 |
|
"I go fast!1!11!1!"
Join Date: Oct 2008
Location: IA, USA
Posts: 10,583 (6.28/day)
Thanks: 1,755
Thanked 2,601 Times in 1,963 Posts
|
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.
__________________
Golden Rule of Programming: Never assume. try { SteamDownload(); } catch (Steamception ex) { RageQuit(); } |
|
|
|
|
|
#18 |
|
Hardcore Monkey Moderator
Join Date: Feb 2007
Location: Cheeseland (Wisconsin, USA)
Posts: 12,133 (5.27/day)
Thanks: 591
Thanked 5,494 Times in 2,938 Posts
|
You're right. I could just target the compilation at .net 2.0.
Guess I'm just too lazy to make multiple versions.
__________________
Cloud (noun, singular): A dynamic arrangement of multiple potential single points of failure, with a user at one end and their data at the other. Get more tech news on a wide variety of topics at NextPowerUp
|
|
|
|
|
|
#19 |
|
"I go fast!1!11!1!"
Join Date: Oct 2008
Location: IA, USA
Posts: 10,583 (6.28/day)
Thanks: 1,755
Thanked 2,601 Times in 1,963 Posts
|
Heh, and I'm too lazy to comment and/or explain code. XD
__________________
Golden Rule of Programming: Never assume. try { SteamDownload(); } catch (Steamception ex) { RageQuit(); } |
|
|
|
|
|
#20 |
|
Join Date: Dec 2008
Posts: 1 (0.00/day)
Thanks: 1
Thanked 0 Times in 0 Posts
|
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. Code:
public HighlighedDates(DateTime date); public HighlighedDates(DateTime date, Color dateColor, Boolean bold); Code:
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);
}
}
});
Code:
private List<HighlightedDates> highLightedDates; Code:
private List<HighlightedDates> highLightedDates = new List<HighlightedDates>(); 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 |
|
|
|
|
|
#21 |
|
Hardcore Monkey Moderator
Join Date: Feb 2007
Location: Cheeseland (Wisconsin, USA)
Posts: 12,133 (5.27/day)
Thanks: 591
Thanked 5,494 Times in 2,938 Posts
|
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.
__________________
Cloud (noun, singular): A dynamic arrangement of multiple potential single points of failure, with a user at one end and their data at the other. Get more tech news on a wide variety of topics at NextPowerUp
|
|
|
|
| The Following User Says Thank You to Kreij For This Useful Post: |
|
|
#22 |
|
Join Date: Oct 2010
Posts: 1 (0.00/day)
Thanks: 0
Thanked 0 Times in 0 Posts
|
Source Code
|
|
|
|
![]() |
| Currently Active Users Viewing This Thread: 1 (0 members and 1 guests) | |
| Thread Tools | |
|
|
Similar Threads
|
||||
| Thread | Thread Starter | Forum | Replies | Last Post |
| Overclocking is Easy! Get Results! | Kursah | Overclocking & Cooling | 131 | May 12, 2010 12:14 AM |
| CoolIT Systems MTEC™ Control Center Featuring Predictive Cooling™ Technology | Darksaber | News | 3 | Jul 7, 2007 04:40 AM |
| ATI Catalyst 7.3 Released | Jimmy 2004 | News | 12 | Mar 29, 2007 12:26 PM |
| ATI Catalyst 6.12 Released | malware | News | 32 | Dec 18, 2006 08:21 PM |
| Online Petition: For ATi to bring back the old control panel as an option. | Moodles | Graphics Cards | 8 | Dec 27, 2005 08:43 PM |