SYSK 227: Monthly Calendar control in ASP.NET
Did you ever need a calendar control that allows you to color-code certain days based on your business logic and display details on mouse-over event? Here is an example of what I’m talking about:
So, perhaps the yellow color means “standard” appointments, and red indicates very important events… You decide based on your business rules and needs. This control also raises OnDateClick event so you can, for example, display additional information. The OnDateClick event is only registered for dates with info, e.g. in the above example the only clickable days are Friday the 3rd and Sunday the 12th.
Interested? Here is the code… with my standard disclaimer: use at your own risk, has not been rigorously tested, etc.
1. Create a class library
2. Add the following class:
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace Web.UI.CustomControls
{
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal),
AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal)]
public class MonthlyCalendar : System.Web.UI.WebControls.Table, INamingContainer
{
[Serializable]
public delegate void DateClickHandler(MonthlyCalendar source, DateTime date);
private static readonly object EventDateClicked = new object();
public MonthlyCalendar()
{
// Set defaults
base.BorderStyle = BorderStyle.Solid;
base.CellPadding = 0;
base.CellSpacing = 0;
base.BorderColor = System.Drawing.Color.DarkGray;
base.BorderWidth = 1;
}
public event DateClickHandler OnDateClick
{
add { Events.AddHandler(EventDateClicked, value); }
remove { Events.RemoveHandler(EventDateClicked, value); }
}
protected override void CreateChildControls()
{
Controls.Clear();
base.CreateChildControls();
// disply title
DateTime currentDate = new DateTime(Year, (int) Month, 1);
base.Caption = "<p style=\"padding-bottom: 4px\"><b>" + currentDate.ToString("MMMM yyyy") + "</b></p>";
base.Rows.Clear();
// Add header
TableHeaderRow hr = new TableHeaderRow();
for (short i = 0; i < 7; i++)
{
TableHeaderCell c = new TableHeaderCell();
c.Text = string.Format(" {0} ", ((DayOfWeek)i).ToString().Substring(0, 3));
hr.Cells.Add(c);
}
base.Rows.Add(hr);
// Add weeks
DateTime endDate = currentDate.AddMonths(1);
TableRow row = null;
while (currentDate < endDate)
{
int dayOfWeek = (int)currentDate.DayOfWeek;
if (row == null || dayOfWeek == (int)DayOfWeek.Sunday)
{
// Create a row
row = new TableRow();
for (int i = 0; i < 7; i++)
{
row.Cells.Add(new TableCell());
}
base.Rows.Add(row);
}
// Add text, back/forecolor
TableCell currentCell = row.Cells[dayOfWeek];
currentCell.Text = string.Format(" {0} ", currentDate.Day);
// bool found = false;
foreach (SeletedPeriod period in Periods)
{
if (currentDate.Day >= period.StartDay && currentDate.Day <= period.EndDay)
{
currentCell.BackColor = period.BackColor;
currentCell.ForeColor = period.ForeColor;
currentCell.ToolTip = period.Message;
currentCell.Attributes["onclick"] = string.Format("javascript:{0};", Page.ClientScript.GetPostBackEventReference(this, string.Format("SelectDate({0})", currentDate.Day)));
break;
}
}
// Next day
currentDate = currentDate.AddDays(1);
}
}
public int Year
{
get { return ViewState["Year"] == null ? 0 : (int) ViewState["Year"]; }
set { ViewState["Year"] = value; }
}
public Month Month
{
get { return ViewState["Month"] == null ? Month.Undefined : (Month)ViewState["Month"]; }
set { ViewState["Month"] = value; }
}
private System.Collections.Generic.List<SeletedPeriod> Periods
{
get { return ViewState["Periods"] == null ? new System.Collections.Generic.List<SeletedPeriod>() : (System.Collections.Generic.List<SeletedPeriod>)ViewState["Periods"]; }
set { ViewState["Periods"] = value; }
}
public void Add(SeletedPeriod period)
{
System.Collections.Generic.List<SeletedPeriod> data = Periods;
data.Add(period);
Periods = data; // save in ViewState
}
public void AddRange(SeletedPeriod[] periods)
{
System.Collections.Generic.List<SeletedPeriod> data = Periods;
data.AddRange(periods);
Periods = data; // save in ViewState
}
public void AddRange(System.Collections.Generic.List<SeletedPeriod> periods)
{
System.Collections.Generic.List<SeletedPeriod> data = Periods;
data.AddRange(periods);
Periods = data; // save in ViewState
}
protected override void RaisePostBackEvent(string eventArgument)
{
this.EnsureChildControls();
DateClickHandler handler = (DateClickHandler)Events[EventDateClicked];
if (handler != null)
{
string eventArg = System.Web.HttpContext.Current.Request["__EVENTARGUMENT"];
if (eventArg != null)
{
string searchFor = "SelectDate(";
int startPos = eventArg.IndexOf(searchFor);
if (startPos != -1)
{
int endPos = eventArg.IndexOf(')', startPos);
if (endPos != -1 && endPos > startPos)
{
string clickedDate = eventArg.Substring(startPos + searchFor.Length, endPos - startPos - searchFor.Length);
// Raise event
handler(this, new DateTime(Year, (int) Month, int.Parse(clickedDate)));
}
}
}
}
}
}
[Serializable]
public class SeletedPeriod
{
private int _startDay;
private int _endDay;
private string _message;
private System.Drawing.Color _backColor;
private System.Drawing.Color _foreColor;
public SeletedPeriod(int startDay, int endDay, string message, System.Drawing.Color backColor, System.Drawing.Color foreColor)
{
_startDay = startDay;
_endDay = endDay;
_message = message;
_backColor = backColor;
_foreColor = foreColor;
}
public int StartDay
{
get { return _startDay; }
set { _startDay = value; }
}
public int EndDay
{
get { return _endDay; }
set { _endDay = value; }
}
public string Message
{
get { return _message; }
set { _message = value; }
}
public System.Drawing.Color BackColor
{
get { return _backColor; }
set { _backColor = value; }
}
public System.Drawing.Color ForeColor
{
get { return _foreColor; }
set { _foreColor = value; }
}
}
public enum Month : short
{
Undefined = 0,
January = 1,
February = 2,
March = 3,
April = 4,
May = 5,
June = 6,
July = 7,
August = 8,
September = 9,
October = 10,
November = 11,
December = 12
}
}
3. On .aspx page, add the following line
<%@ Register Assembly="Web.UI.CustomControls" Namespace="Web.UI.CustomControls" TagPrefix="cc1" %>
and add the actual control:
<cc1:monthlycalendar id="MonthlyCalendar1" runat="server" ></cc1:monthlycalendar>
<asp:Label ID="SelectedDate" runat="server"></asp:Label>
4. On Page_Load, add your initialization code. E.g.
if (Page.IsPostBack == false)
{
MonthlyCalendar1.Month = Web.UI.CustomControls.Month.February;
MonthlyCalendar1.Year = 2006;
MonthlyCalendar1.Add(new Web.UI.CustomControls.SeletedPeriod(3, 3, string.Format("Meeting with Mike @ 11:30 am\r\nHoustons restaurant"), System.Drawing.Color.Yellow, System.Drawing.Color.Black));
MonthlyCalendar1.Add(new Web.UI.CustomControls.SeletedPeriod(12, 12, string.Format("Performance review with boss\r\n9:00 am"), System.Drawing.Color.Red, System.Drawing.Color.White));
}
MonthlyCalendar1.OnDateClick += new Web.UI.CustomControls.MonthlyCalendar.DateClickHandler(MonthlyCalendar_OnDateClick);
When a date (appointment) is clicked, it’ll raise an event… Here is your delegate:
void MonthlyCalendar_OnDateClick(Web.UI.CustomControls.MonthlyCalendar source, DateTime selectedDay)
{
SelectedDate.Text = selectedDay.ToShortDateString();
}
Enjoy!
Comments
Anonymous
October 26, 2006
looks good but this one i think works only on .net 2.0 ,so maybe it would help if you can add the platform it works also in details :)Anonymous
October 26, 2006
why not just use the built-in ASP.NET calendar (which has all the rendering goodness for you and full features) and just attach to its events (or extend it rather than implement your own rendering)Anonymous
October 27, 2006
If you need more functionality, then your suggestion is right on. However, if you want a control that's very light, that has just few features described above, then the source code above should get you started.Anonymous
April 16, 2007
do u know how can i change the text of DayOfWeek i want to cghange sun to bun.. how is that possible ?Anonymous
August 06, 2008
Consider doing a modified version of this where you pull the "tooltip" display and populate the days / colors based on info returned from a database table? Please? :-)