LANGUAGES: VB.NET | C#
ASP.NET VERSIONS: 2.x
Clear Your Calendar
Clean Up the Clutter with a Custom DropDownCalendar Control
By Steve C. Orr
The Calendar control included with ASP.NET provides an easy and intuitive way for users to visually select dates. However, it takes up so much screen real estate that you may not be able to fit many other controls on the page. The custom DropDownCalendar control detailed in this article solves the problem with a small footprint that expands only when needed.
The custom DropDownCalendar control described in this article inherits from the standard ASP.NET Calendar control and extends it with the ability to show and hide on demand. The control also provides useful new smart tags that make configuration easy. While touring the source code (available for download; see end of article for details), you can learn how to add smart tags to your own controls for a truly professional polish.
To use this control, download the sample code and add the included DropDownCalendar.dll to your Visual Studio 2005 toolbox. Then drag it onto any WebForm to get started using it. At first it will appear much like a standard DropDownList control. If you wish to drop down the calendar at design time, simply set the control s Expanded property to True. If you leave this property set to True, the control will appear expanded by default when the page is loaded in the browser at run time. Other useful properties are shown in Figure 1.
Unique DropDownCalendar Properties
Sets or gets the initial dropdown state. Default: False
Specifies whether the selected date should be displayed in the text area with a long or short date format. Default: True
The style applied to the text area.
Sets or gets the color of the background of the button. Default: White
Sets or gets the color of the button arrow. Default: Black
Figure 1: The DropDownCalendar control provides several new and useful properties beyond those the base Calendar control already provides.
The ASPX declaration looks roughly like this (depending on how the control properties have been configured):
At run time the control looks a lot like a dropdown list with an expandable calendar bolted onto the bottom, as shown in Figure 2. In design reality, the control is actually inherited directly from the Calendar control and the dropdown functionality is bolted onto the top.
Figure 2: The DropDownCalendar control s calendar can hide and show on demand, much like the list portion of a DropDownList control.
Much like a standard DropDownList control, the calendar portion of this control can be shown and hidden on demand without requiring a postback. When the user clicks on a date, the form posts back and raises the SelectionChanged event (just like the standard Calendar control) and then the dropdown portion will be automatically hidden. Of course, the user can always expand the calendar again and pick a different date if you choose to let them.
Alright, it s time to turn this control inside out and take a look at the code that makes it work. The basic structure of the control is outlined in Figure 3. This structure is fairly standard for any custom Web control. The Designer attribute is there to support the new smart tag functionality, which will be explained in more detail later.
Public Class DropDownCalendar
Figure 3: The DropDownCalendar control inherits directly from the Calendar control, declares a designer for smart tag support, extends the control with some custom properties, and overrides a few base subroutines.
The DropDownCalendar control overrides three subroutines of the base Calendar control. The most significant of these is the Render subroutine, which is where nearly all the real work happens to create the DropDownCalendar control (see Figure 4).
Protected Overrides Sub Render(ByVal _
writer As HtmlTextWriter)
'display calendar on top of other elements
MyBase.Style(HtmlTextWriterStyle.Position) = "absolute"
'hide or show the calendar initially
If Me.Expanded Then
Me.Style(HtmlTextWriterStyle.Display) = "block"
Me.Style(HtmlTextWriterStyle.Display) = "none"
'create the containing table
Dim tbl As New Table
tbl.Enabled = Me.Enabled
tbl.BorderWidth = New Unit(1, UnitType.Pixel)
tbl.BorderColor = Drawing.Color.Black
tbl.CellSpacing = 1
tbl.Width = Me.Width
tbl.ID = Me.ClientID & "_tbl"
If Me.Enabled Then
tbl.Style(HtmlTextWriterStyle.Cursor) = "hand"
tbl.Attributes.Add("Onclick", OnClickJS() )
'create the text area
Dim tr As New TableRow
Dim td As New TableCell
td.Width = New Unit(100, UnitType.Percentage)
Dim dt As String = IIf(Me.DisplayLongDate, _
'create the (simulated) button
Dim td2 As TableCell = New TableCell
td2.BorderStyle = WebControls.BorderStyle.Outset
td2.BorderWidth = 2
td2.BackColor = Me.ButtonBackColor
td2.ForeColor = Me.ButtonForeColor
td2.Font.Name = "Webdings"
'render the containing table
'render the base calendar control
If Me.DesignMode = False OrElse Me.Expanded Then
Figure 4: The Render event draws the text area and the button that drops down the calendar. The rendering of the calendar is delegated to the base Calendar control.
The first line is important. By ensuring the calendar is rendered with absolute positioning, this will allow the calendar to hover over any controls that are placed just below the DropDownCalendar control when expanded, just like the list portion of a DropDownList control.
The fourth code block shown in Figure 4 continues creating the HTML table that positions everything, and creates the text area that will display the selected date in the configured long or short string format. The fifth code block styles one of the cells to look like a button so the user will understand this is a clickable area.
To wrap things up, the containing HTML table is rendered, which renders all the other controls that have been instantiated so far. Finally, the base Calendar control is then commanded to render itself.
To polish things nicely, the OnVisibleMonthChanged event is overridden to keep the calendar portion expanded between postbacks when the user changes months:
Protected Overrides Sub OnVisibleMonthChanged(ByVal _
newDate As Date, ByVal previousDate As Date)
'keep calendar expanded after month change
Me.Expanded = True
Similarly, the OnSelectedChanged event is overridden to ensure the calendar portion automatically hides once the user has selected a date:
Protected Overrides Sub OnSelectionChanged()
'hide calendar by default after selection
Me.Expanded = False
When overriding base control events, it s always good practice to call the base method to ensure the underlying control performs normally its portion of the functionality.
Three things must be done to enhance a custom control with smart tags. First, the control class needs a Designer attribute that refers to a custom Designer class. This is demonstrated in Figure 3. Second, the custom Designer class must add a DesignerActionList (full of smart tag actions) to its DesignerActionListCollection, as shown in Figure 5. Finally, the DesignerActionList class (shown in Listing One) must programmatically add each desired DesignerAction. For all this to work, the control s project must have a reference to System.Design.dll.
Public Class DropdownCalendarDesigner
Private o_ActionLists As DesignerActionListCollection
Public Overrides ReadOnly Property ActionLists() As _
If o_ActionLists Is Nothing Then
o_ActionLists = New DesignerActionListCollection
Dim ctl As DropDownCalendar = _
Figure 5: The Designer for the DropDownCalendar control inherits and extends the CalendarDesigner, extending it with smart tag actions that are defined in the custom DropDownCalendarActions class.
There s not much code to the Designer class in Figure 5 because it is only implementing smart tag support. However, Designer classes can also unlock other advanced design-time features, as well, such as support for control templates and auto-formatting. The code in Figure 5 essentially does little more than add a reference to the DropDownCalendarActions class, which is shown in Listing One.
The primary function shown in Listing One, GetSortedActionItems, creates each item that will be shown in the smart tags dialog box, which is illustrated in Figure 6. A new DesignerActionItemCollection object is instantiated and filled with smart tags, otherwise known as DesignerActionItems. There are four kinds of DesignerActionItems: Headers, Properties, Methods, and Text. All but the latter are demonstrated in Listing One.
Figure 6: The smart tags for the DropDownCalendar control were added programmatically via a Designer class and a DesignerActionList class. Notice that the method item smart tags also show up automatically at the bottom of the Properties window.
Headers are shown in bold text, properties are displayed as editable fields, and commands tend to show up as hyperlinks that execute custom methods on demand. Properties or more specifically, DesignActionPropertyItems are quite versatile in their display; for example, automatically showing up as textboxes when representing strings, dropdownlists when representing enumerations, or checkboxes when representing Boolean values.
Notice that the properties and methods all have the header name passed as a constructor parameter so Visual Studio knows how to group them. Also notice the first parameter for each property item refers to a property declaration further down in Listing One, which does the work of setting and getting the property value from the underlying DropDownCalendar control. Similarly, the first constructor parameter for each method item refers to a function listed further down in Listing One. These methods do the real work. Although the examples here are simple, they could be as complex as necessary launching Windows forms, fancy wizards, or whatever else you can imagine.
Smart tags are a modern design-time feature that can enhance any control with a professional polish. They are a user-friendly way to allow application developers to configure controls in quick, intuitive ways. There is a significant amount of code involved, but most of it is simple and boilerplate.
Nearly any large control can be similarly enhanced with dropdown functionality so it can hide away when not immediately needed. You could try inheriting from a GridView control instead of a Calendar control, for example, which is a common way to create multi-column DropDownLists. Similarly, I ve seen people place a TreeView control into a dropdown area to decrease the footprint of that otherwise bulky control. I can also imagine a dropdown CheckBoxList or RadioButtonList. How about a dropdown Login control, or maybe even a dropdown Crystal Report? I encourage you to combine your imagination and your skills with the information you ve learned here to come up with a custom control that saves your company time and money.
The complete source code for the DropDownCalendar control is available for download (in both VB and C#).
Steve C. Orr is an MCSD and a Microsoft MVP in ASP.NET. He s been developing software solutions for leading companies in the Seattle area for more than a decade. When he s not busy designing software systems or writing about them, he can often be found loitering at local user groups and habitually lurking in the ASP.NET newsgroup. Find out more about him at http://SteveOrr.net or e-mail him at mailto:[email protected].
Public Class DropdownCalendarActions
Private ctl As DropdownCalendar
Public Overrides Function GetSortedActionItems() As _
Dim o_Items As _
DesignerActionItemCollection = _
' create headers
Dim Header2 As String = "Developed by Steve C. Orr"
'add property items
"Day Name Format", "Properties"))
"Selected Date", "Properties"))
"Display Long Date Format", "Properties"))
'add method items
DesignerActionMethodItem(Me, "SelectJan1", _
"Select January 1st", "Properties", _
"Selects January First", True))
DesignerActionMethodItem(Me, "ToggleExpanded", _
"Toggle Expanded property", "Properties", _
"Toggles the dropdown state", True))
DesignerActionMethodItem(Me, "LaunchWebSite", _
"http://SteveOrr.net", Header2, _
"Launches Steve Orr's web site.", True))
#Region " Properties "
Public Property DayNameFormat() _
Set(ByVal value As DayNameFormat)
Public Property SelectedDate() As Date
Set(ByVal value As Date)
Public Property DisplayLongDate() As Boolean
Set(ByVal value As Boolean)
#Region " Methods "
Public Sub New(ByVal ctlCal As DropDownCalendar)
ctl = ctlCal
Public Sub LaunchWebSite()
Dim sURL As String = "http://SteveOrr.net"
Public Sub ToggleExpanded()
GetProperty("Expanded").SetValue(ctl, Not ctl.Expanded)
Public Sub SelectJan1()
Date.Parse("Jan 1 " & Date.Now.Year.ToString))
Private Function GetProperty(ByVal _
propertyName As String) As PropertyDescriptor
Dim o_Property As PropertyDescriptor = _
If o_Property IsNot Nothing Then
Throw New _
ArgumentException("Invalid property", _
End Listing One