Geeks With Blogs

News Welcome to Adam Pooler's weblog.

Adam Pooler

Microsoft Certified Professional Developer

The Out Campaign: Scarlet Letter of Atheism

The Quandary Phase This code was generated by a tool.

I have been playing around with the ASP.NET ListView and Repeater controls quite a bit recently, and thought that a simple calendar control with date range selection capabilities baked-in would make a good example of how these controls can be used to produce some pretty neat functionality without a great deal of code.

The System.Web.UI.WebControls namespace already contains a calendar control of course, which is itself quite feature-rich. Although it doesn’t offer support for date range selection out-of-the-box, it can quite easily be extended to do so. The built-in ASP.NET Calendar Control also has a wealth of properties which enable its layout and appearance to be tweaked as necessary. So really, the code for this article is by way of example only; the date range selection functionality is useful, or the code could be used as the starting point for a custom calendar control if you have a need to develop one.

The calendar is implemented as a UserControl, and the UserControl contains a Repeater control, nested within an HTML table. The repeater is used to generate the linkbuttons for each of the calendar days for the current month. The end result looks, as you might expect, not entirely dissimilar to the ASP.NET Calendar:


The binding of the repeater control is performed using two classes: CalendarMonth and CalendarWeek. The CalendarMonth class represents a single month within a specific year, and the code looks like this:


   1: public class CalendarMonth : IEnumerable<DateTime>
   2: {
   4:     private List<DateTime> _days;
   6:     public CalendarMonth(int month, int year)
   7:     {
   8:         this._days = new List<DateTime>();
  10:         DateTime date = new DateTime(year, month, 1);
  11:         while (date.Month == month)
  12:         {
  13:             this._days.Add(date);
  14:             date = date.AddDays(1);
  15:         }
  16:     }
  18:     #region IEnumerable<DateTime> Members
  20:     public IEnumerator<DateTime> GetEnumerator()
  21:     {
  22:         return this._days.GetEnumerator();
  23:     }
  25:     #endregion
  27:     #region IEnumerable Members
  29:     System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  30:     {
  31:         return this._days.GetEnumerator();
  32:     }
  34:     #endregion
  36:     /// <summary>
  37:     /// Gets a collection of calendar weeks for the current month.
  38:     /// </summary>
  39:     /// <returns></returns>
  40:     public IList<CalendarWeek> GetWeeks()
  41:     {
  42:         List<CalendarWeek> weeks = new List<CalendarWeek>();
  43:         CalendarWeek currentWeek = null;
  44:         for (int i = 0; i < this._days.Count; i++)
  45:         {
  46:             if (currentWeek == null || this._days[i].DayOfWeek == DayOfWeek.Monday)
  47:             {
  48:                 if (currentWeek != null)
  49:                     weeks.Add(currentWeek);
  51:                 currentWeek = new CalendarWeek();
  52:             }
  54:             currentWeek[this._days[i].DayOfWeek] = this._days[i];
  56:             if (i == this._days.Count - 1)
  57:                 weeks.Add(currentWeek);
  58:         }
  60:         return weeks;
  61:     }
  62: }

As you can see from the above, there isn’t a great deal of functionality exposed: just a single public method which returns a collection of CalendarWeek instances. It is the collection returned from this method which is bound to the repeater on the page. The CalendarWeek class is intended to represent a single Monday-Sunday period within a given CalendarMonth. The CalendarWeek class is coded like this:

   1: public class CalendarWeek
   2: {
   3:     private System.Collections.Specialized.ListDictionary _days;
   5:     internal CalendarWeek()
   6:     {
   7:         this._days = new System.Collections.Specialized.ListDictionary();
   9:         //add the days of the week
  10:         for (int i = 1; i < 7; i++)
  11:         {
  12:             this._days.Add((DayOfWeek)i, DateTime.MinValue);
  13:         }
  14:         this._days.Add(DayOfWeek.Sunday, DateTime.MinValue);
  15:     }
  17:     /// <summary>
  18:     /// Indexer to supply access to the date values.
  19:     /// </summary>
  20:     /// <param name="day"></param>
  21:     /// <returns></returns>
  22:     public DateTime this[DayOfWeek day]
  23:     {
  24:         get { return (DateTime)this._days[day]; }
  25:         internal set { this._days[day] = value; }
  26:     }
  27: }

Each CalendarWeek instance contains a collection of seven DateTime instances which can be accessed by day of week. In order to achieve this, the class uses the System.DayOfWeek enumeration: each day of the week is added as the key to a lightweight key/value collection. The assigning of DateTime instances to each day in the CalendarWeek occurs in the GetWeeks() method of the CalendarMonth class, when the collection of CalendarWeek instances is generated. The CalendarWeek exposes a public indexer to allow each date value to be accessed. Of course, not every month starts on a Monday and ends on a Sunday, so one or more date values in the CalendarWeek instance may not be initialised- and the UI uses this to work out whether a linkbutton should be displayed for each day.

So the UI first creates a CalendarMonth instance, gets a collection of CalendarWeek instances for the month, then binds this collection to the repeater.

The markup for the UserControl (which contains the parent HTML table for the calendar, and repeater control which generates the weeks) is structured as follows:

   1: <asp:Panel ID="pnlContainer" runat="server">
   2:     <table class="<%# CalendarCssClass %>">
   3:         <tr>
   4:             <td>
   5:                 <asp:LinkButton ID="lnkBack" runat="server" OnClick="calendar_Back">&lt;&lt;</asp:LinkButton>
   6:             </td>
   7:             <td colspan="5" style="text-align:center">
   8:                 <asp:Label ID="lblMonth" runat="server"></asp:Label>
   9:             </td>
  10:             <td runat="server" id="tdForward">
  11:                 <asp:LinkButton ID="lnkForward" runat="server" OnClick="calendar_Forward">&gt;&gt;</asp:LinkButton>
  12:             </td>
  13:         </tr>
  14:         <tr>
  15:             <td><asp:Literal ID="litMonday" runat="server"></asp:Literal></td>
  16:             <td><asp:Literal ID="litTuesday" runat="server"></asp:Literal></td>
  17:             <td><asp:Literal ID="litWednesday" runat="server"></asp:Literal></td>
  18:             <td><asp:Literal ID="litThursday" runat="server"></asp:Literal></td>
  19:             <td><asp:Literal ID="litFriday" runat="server"></asp:Literal></td>
  20:             <td><asp:Literal ID="litSaturday" runat="server"></asp:Literal></td>
  21:             <td><asp:Literal ID="litSunday" runat="server"></asp:Literal></td>
  22:         </tr>
  23:         <asp:Repeater ID="rptCalendar" runat="server" 
  24:             onitemdatabound="rptCalendar_ItemDataBound" 
  25:             onitemcommand="rptCalendar_ItemCommand">
  26:             <ItemTemplate>
  27:                 <tr class="<%# CurrentRowCssClass %>">
  28:                     <td runat="server" id="tdMonday"><asp:LinkButton ID="lnkMonday" runat="server" CausesValidation="false"></asp:LinkButton></td>
  29:                     <td runat="server" id="tdTuesday"><asp:LinkButton ID="lnkTuesday" runat="server" CausesValidation="false"></asp:LinkButton></td>
  30:                     <td runat="server" id="tdWednesday"><asp:LinkButton ID="lnkWednesday" runat="server" CausesValidation="false"></asp:LinkButton></td>
  31:                     <td runat="server" id="tdThursday"><asp:LinkButton ID="lnkThursday" runat="server" CausesValidation="false"></asp:LinkButton></td>
  32:                     <td runat="server" id="tdFriday"><asp:LinkButton ID="lnkFriday" runat="server" CausesValidation="false"></asp:LinkButton></td>
  33:                     <td runat="server" id="tdSaturday"><asp:LinkButton ID="lnkSaturday" runat="server" CausesValidation="false"></asp:LinkButton></td>
  34:                     <td runat="server" id="tdSunday"><asp:LinkButton ID="lnkSunday" runat="server" CausesValidation="false"></asp:LinkButton></td>  
  35:                 </tr>
  36:             </ItemTemplate>
  37:         </asp:Repeater>
  38:     </table>
  39: </asp:Panel>

And to complete the picture, the code which initialises each repeater item when the control is data bound:

   1: /// <summary>
   2: /// Invoked when a repeater item is being data bound.
   3: /// </summary>
   4: /// <param name="sender"></param>
   5: /// <param name="e"></param>
   6: protected void rptCalendar_ItemDataBound(object sender, RepeaterItemEventArgs e)
   7: {
   8:     if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
   9:     {
  10:         this.InitialiseCalendarItem(e.Item);
  11:     }
  12: }
  14: /// <summary>
  15: /// Initialise calendar repeater item.
  16: /// </summary>
  17: /// <param name="item"></param>
  18: protected void InitialiseCalendarItem(RepeaterItem item)
  19: {
  20:     CalendarWeek week = (CalendarWeek)item.DataItem;
  21:     DateTime date;
  22:     LinkButton lnk;
  24:     for (int i = 0; i < 7; i++)
  25:     {
  26:         DayOfWeek day = (DayOfWeek)i;
  27:         date = week[day];
  29:         //initialise th date linkbutton
  30:         lnk = (LinkButton)item.FindControl("lnk" + day.ToString());
  31:         lnk.Visible = !(date.Equals(DateTime.MinValue));
  32:         if (lnk.Visible)
  33:         {
  34:             lnk.Text = date.Day.ToString();
  35:             lnk.CommandArgument = date.ToString();
  36:         }
  38:         if (!date.Equals(DateTime.MinValue))
  39:         {
  40:             if (this.DateSelectionMode == SelectionMode.Single)
  41:             {
  42:                 //if the current day is selected, highlight it
  43:                 DateTime selectedDate = this.SelectedDate;
  44:                 if (!selectedDate.Date.Equals(DateTime.MinValue) && date.Date.Equals(selectedDate.Date))
  45:                 {
  46:                     this.HighlightDay(item, day);
  47:                 }
  48:             }
  49:             else
  50:             {
  51:                 //if the current day is contained in the date range, highlight it
  52:                 DateRange selectedRange = this.SelectedDateRange;
  53:                 if (!selectedRange.Equals(DateRange.MinValue) && selectedRange.Contains(date))
  54:                 {
  55:                     this.HighlightDay(item, day);
  56:                 }
  57:             }
  58:         }
  59:     }
  60: }
  62: /// <summary>
  63: /// Highlights a selected day in the current item.
  64: /// </summary>
  65: /// <param name="item"></param>
  66: /// <param name="day"></param>
  67: protected void HighlightDay(RepeaterItem item, DayOfWeek day)
  68: {
  69:     HtmlTableCell td = (HtmlTableCell)item.FindControl("td" + day.ToString());
  71:     if (!string.IsNullOrEmpty(this.SelectedDateCssClass))
  72:     {
  73:         td.Attributes.Add("class", this.SelectedDateCssClass);
  74:     }
  75:     else
  76:     {
  77:         td.BgColor = "red";
  78:     }
  79: }

As an extra bit of sugar coating, the abbreviated day names (shown at the top of the control) are set dynamically in the code-behind, based on the current culture settings:

   1: /// <summary>
   2: /// Sets the abbreviated day names for the current culture.
   3: /// </summary>
   4: private void SetDayNames()
   5: {
   6:     Array values = Enum.GetValues(typeof(System.DayOfWeek));
   7:     DayOfWeek day;
   8:     int dayNumber;
   9:     Literal lit = null;
  11:     foreach (object value in values)
  12:     {
  13:         day = (DayOfWeek)value;
  14:         dayNumber = (int)day;
  15:         lit = (Literal)this.FindControl("lit" + day.ToString());
  16:         lit.Text = System.Globalization.CultureInfo.CurrentUICulture.DateTimeFormat.AbbreviatedDayNames[dayNumber];
  17:     }   
  18: }

The rest of the code in the UserControl is concerned with the navigation between months, and tracking the selected date or date range, and I won’t go into any of the details here- suffice to say it’s all pretty straightforward. The UserControl also exposes a few properties (all of which persisted via ViewState) for setting CSS classes on the calendar, for setting the selected date (or date range). There’s also a property for switching the selection mode between single date and date range, and finally, a couple of events for notifying when a date or date range is selected.

In summary, we have a pretty elegant, and readily extensible, calendar control in fewer than 800 lines of code…not bad! The UserControl, class files, and a test page are included in the zip file which can be downloaded using the link at the top of the article.

Posted on Monday, May 4, 2009 2:52 AM | Back to top

Comments on this post: ASP.NET: Simple Calendar & Date Range Selector

# re: ASP.NET: Simple Calendar & Date Range Selector
Requesting Gravatar...
Does this customisation helps in highlighting the week the date belongs to on the control? we have a requirement when selected a date we need to highlight/color its relavant week. EX: if my business week starts from wedness day and I select a following Monday's date, the calander should highlight the week from wed this week to next wed. Can we achieve this?
Left by Murthy on Jul 17, 2009 1:09 AM

# classic ugg boots
Requesting Gravatar...
I was looking for to help me get started with a custom calendar with some data in each cell from a CMS.Ugg Ultra Short
Ultra Tall Ugg Boots
Left by ugg on Oct 31, 2010 3:37 PM

# re: ASP.NET: Simple Calendar & Date Range Selector
Requesting Gravatar...
Your blog article is very intersting and fantastic,at the same time the blog theme is unique and perfect,great job.To your success.One of the more impressive blogs Ive seen. Thanks so much for keeping the internet classy for a change.I ran across search engines and find this blog that fulfill my needs. There is a thing I do not agree but It doesn't matter since I think it does not hurt the whole content.
The added inshight increases my insights about:Indonesian Furniture | Indonesian Teak Furniture.
Thamk you,I was looking for to help me get started with a custom calendar with some data in each cell from a CMS.
Left by John Agung Vanderbilt on Dec 07, 2010 5:07 AM

# re: ASP.NET: Simple Calendar & Date Range Selector
Requesting Gravatar...
This is so complex, or it's just so looking, There are so many strings of code! Is there some easier way to perform this task? proofread my essay
Left by bill8 on Jan 31, 2011 5:06 AM

# re: ASP.NET: Simple Calendar & Date Range Selector
Requesting Gravatar...
Thanks for detailed these script for us. I'd never can do the same (I'm just a beginner in this). Greatest thank you my friend
Left by custom research paper writing se on Feb 03, 2011 5:48 PM

# re: ASP.NET: Simple Calendar & Date Range Selector
Requesting Gravatar...
acquainted with the site very recently, but for all time use of the site had not changed its slogan. Information is really complete, timely, and thorough presentation. Cool site.
Left by writing thesis on Feb 07, 2011 5:37 AM

# re: ASP.NET: Simple Calendar & Date Range Selector
Requesting Gravatar...

Actually i want display in every month of calendar just seven days start from the current date.

So is it possible.

Left by SP on Feb 11, 2011 10:23 AM

# re: ASP.NET: Simple Calendar & Date Range Selector
Requesting Gravatar...
Hi, I'm pretty new to .net and have used your code sample in a project that I am working on. Great article! I literally copied and pasted the control. Is there any way to clear the selected dates on the click of a button without re-loading the page. Thanks!
Left by Wez on Feb 13, 2011 12:35 PM

# re: ASP.NET: Simple Calendar & Date Range Selector
Requesting Gravatar...
acquainted with the site very recently, but for all time use of the site had not changed its slogan. Information is really complete, timely, and thorough presentation. Cool site.This is so complex, or it's just so looking, There are so many strings of code! Is there some easier way to perform this task?hermes birkin bag
Left by pice man on Feb 14, 2011 4:09 PM

# 先生
Requesting Gravatar...
have some functional features: easy-access exterior pockets,
Left by mulberry bags on Mar 02, 2011 8:20 PM

# 先生
Requesting Gravatar...
I am thoroughly convinced in this said post. I am currently searching for ways in which I could enhance my knowledge in this said topic you have posted here. It does help me a lot knowing that you have shared this information here freely. I love the way the people here interact and shared their opinions too. I would love to track your future posts pertaining to the said topic we are able to read.
Left by Cheap Oakley sunglasses on Mar 17, 2011 8:56 PM

# re: ASP.NET: Simple Calendar & Date Range Selector
Requesting Gravatar...
I suppose a repeater is an electronic device that receives a signal and retransmits it at a higher level and/or higher power, or onto the other side of an obstruction, so that the signal can cover longer distances.
Left by writing essay papers on Mar 22, 2011 10:52 PM

Comments have been closed on this topic.
Copyright © Adam Pooler | Powered by: