LANGUAGES: VB.NET | C#
ASP.NET VERSIONS: 2.x
DropDown Calendar
Clean Up the Clutter with a Custom DropDownCalendar Control
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
|
Description
|
|
Expanded
|
Sets or gets the initial dropdown state.
Default: False
|
|
DisplayLongDate
|
Specifies whether the selected date should be displayed in the text area with a
long or short date format. Default: True
|
|
TextAreaStyle
|
The style applied to the text area
|
|
ButtonBackColor
|
Sets or gets the color of the background of the button.
Default: White
|
|
ButtonForeColor
|
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):
<cc1:DropDownCalendar ID="DropDownCalendar1"
runat="server"
ns="urn:schemas-microsoft-com:office:office"
TextAreaStyle-Font-Underline="True"
TextAreaStyle-ForeColor="Blue">
</cc1:DropDownCalendar>
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.
Inside Out
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.
<ToolboxData("<{0}:DDCal runat=server></{0}:DDCal>"),
_
<Designer(GetType(DropdownCalendarDesigner))> _
Public
Class DropDownCalendar
Inherits
Calendar
'Constructor…
'Public Properties…
'Overriden Subroutines…
End
Class
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"
Else
Me.Style(HtmlTextWriterStyle.Display) =
"none"
End If
'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() )
End If
'create the text
area
Dim tr
As New TableRow
tbl.Rows.Add(tr)
Dim td
As New TableCell
tr.Cells.Add(td)
td.Width = New
Unit(100, UnitType.Percentage)
td.ApplyStyle(Me.TextAreaStyle)
Dim dt
As String = IIf(Me.DisplayLongDate, _
Me.SelectedDate.ToLongDateString,
_
Me.SelectedDate.ToShortDateString)
td.Controls.Add(New
LiteralControl(dt))
'create the (simulated)
button
Dim td2
As TableCell = New
TableCell
tr.Cells.Add(td2)
td2.BorderStyle = WebControls.BorderStyle.Outset
td2.BorderWidth = 2
td2.BackColor = Me.ButtonBackColor
td2.ForeColor = Me.ButtonForeColor
td2.Font.Name =
"Webdings"
td2.Controls.Add(New
LiteralControl("6"))
'render the containing
table
tbl.RenderControl(writer)
'render the base
calendar control
If Me.DesignMode = False
OrElse Me.Expanded
Then
MyBase.Render(writer)
End If
End
Sub
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 second code block specifies the Display style of the calendar, which specifies
whether it should initially be visible (block) or invisible (none). When the arrow
button or text area is clicked by the user at run time, this value will be toggled
by client-side code to hide and show the calendar on demand. This JavaScript is
output at the end of the third code block, and is assigned to the client-side OnClick
event.
(advertisement)
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 _
ns="urn:schemas-microsoft-com:office:office"
newDate As
Date, ByVal previousDate
As Date)
'keep calendar
expanded after month change
Me.Expanded
= True
MyBase.OnVisibleMonthChanged(newDate,
previousDate)
End
Sub
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
MyBase.OnSelectionChanged()
End
Sub
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.
Smart Tags
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.
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.
(advertisement)
Conclusion
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 sample code in this article is available for download.
This article was originally published in
ASP.NET Pro Magazine.
BEGIN LISTING ONE
Public
Class DropdownCalendarActions
Inherits
System.ComponentModel.Design.DesignerActionList
Private
ctl As DropdownCalendar
Public Overrides Function
GetSortedActionItems() As _
System.ComponentModel.Design.DesignerActionItemCollection
Dim o_Items
As _
DesignerActionItemCollection = _
New
DesignerActionItemCollection
' create headers
o_Items.Add(New
_
DesignerActionHeaderItem("Properties"))
Dim
Header2 As String
= "Developed by Steve C. Orr"
o_Items.Add(New
_
DesignerActionHeaderItem(Header2))
'add property
items
o_Items.Add(New
_
DesignerActionPropertyItem("DayNameFormat", _
"Day
Name Format", "Properties"))
o_Items.Add(New
_
DesignerActionPropertyItem("SelectedDate", _
"Selected
Date", "Properties"))
o_Items.Add(New
_
DesignerActionPropertyItem("DisplayLongDate", _
"Display
Long Date Format", "Properties"))
'add method
items
o_Items.Add(New
_
DesignerActionMethodItem(Me,
"SelectJan1", _
"Select
January 1st", "Properties", _
"Selects
January First", True))
o_Items.Add(New
_
DesignerActionMethodItem(Me, "ToggleExpanded",
_
"Toggle
Expanded property", "Properties",
_
"Toggles
the dropdown state", True))
o_Items.Add(New
_
DesignerActionMethodItem(Me, "LaunchWebSite",
_
"http://SteveOrr.net",
Header2, _
"Launches
Steve Orr's web site.", True))
Return
o_Items
End Function
#Region " Properties "
Public Property DayNameFormat() _
As
System.Web.UI.WebControls.DayNameFormat
Get
Return
ctl.DayNameFormat
End Get
Set(ByVal value As DayNameFormat)
GetProperty("DayNameFormat").SetValue(ctl,
value)
End Set
End Property
Public Property SelectedDate() As
Date
Get
Return
ctl.SelectedDate
End
Get
Set(ByVal value As Date)
GetProperty("SelectedDate").SetValue(ctl,
value)
End
Set
End Property
Public Property DisplayLongDate() As
Boolean
Get
Return
ctl.DisplayLongDate
End
Get
Set(ByVal value As Boolean)
GetProperty("DisplayLongDate").SetValue(ctl,
value)
End
Set
End Property
#End Region
#Region " Methods "
Public
Sub New(ByVal
ctlCal As DropDownCalendar)
MyBase.New(ctlCal)
ctl = ctlCal
End Sub
Public
Sub LaunchWebSite()
Dim
sURL As String
= "http://SteveOrr.net"
Try
System.Diagnostics.Process.Start(sURL)
Catch
End
Try
End Sub
Public
Sub ToggleExpanded()
GetProperty("Expanded").SetValue(ctl,
Not ctl.Expanded)
End Sub
Public
Sub SelectJan1()
GetProperty("SelectedDate").SetValue(ctl,
_
Date.Parse("Jan 1 " & Date.Now.Year.ToString))
End Sub
Private
Function GetProperty(ByVal
_
propertyName
As String) As
PropertyDescriptor
Dim
o_Property As PropertyDescriptor = _
TypeDescriptor.GetProperties(ctl).Item(propertyName)
If
o_Property IsNot Nothing
Then
Return
o_Property
Else
Throw
New _
ArgumentException("Invalid property", _
propertyName)
End
If
End Function
#End Region
End
Class
END LISTING ONE