ControlFreak
LANGUAGES:VB.NET | C#
ASP.NETVERSIONS: 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 aneasy and intuitive way for users to visually select dates. However, it takes upso much screen real estate that you may not be able to fit many other controlson the page. The custom DropDownCalendar control detailed in this articlesolves the problem with a small footprint that expands only when needed.
The custom DropDownCalendar control described in thisarticle inherits from the standard ASP.NET Calendar control and extends it withthe ability to show and hide on demand. The control also provides useful new smarttags that make configuration easy. While touring the source code (available fordownload; see end of article for details), you can learn how to add smart tagsto your own controls for a truly professional polish.
To use this control, download the sample code and add theincluded DropDownCalendar.dll to your Visual Studio 2005 toolbox. Then drag itonto any WebForm to get started using it. At first it will appear much like astandard DropDownList control. If you wish to drop down the calendar at designtime, simply set the control?s Expanded property to True. If you leave thisproperty set to True, the control will appear expanded by default when the pageis loaded in the browser at run time. Other useful properties are shown inFigure 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: TheDropDownCalendar control provides several new and useful properties beyondthose the base Calendar control already provides.
The ASPX declaration looks roughly like this (depending onhow the control properties have been configured):
<cc1:DropDownCalendar ID="DropDownCalendar1"runat="server"
TextAreaStyle-Font-Underline="True"
TextAreaStyle-ForeColor="Blue">
</cc1:DropDownCalendar>
At run time the control looks a lot like a dropdown listwith an expandable calendar bolted onto the bottom, as shown in Figure 2. Indesign reality, the control is actually inherited directly from the Calendarcontrol and the dropdown functionality is bolted onto the top.
Figure 2: The DropDownCalendarcontrol?s calendar can hide and show on demand, much like the list portion of aDropDownList control.
Much like a standard DropDownList control, the calendarportion of this control can be shown and hidden on demand without requiring apostback. When the user clicks on a date, the form posts back and raises theSelectionChanged event (just like the standard Calendar control) and then thedropdown portion will be automatically hidden. Of course, the user can alwaysexpand 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 andtake a look at the code that makes it work. The basic structure of the control isoutlined in Figure 3. This structure is fairly standard for any custom Webcontrol. The Designer attribute is there to support the new smart tagfunctionality, which will be explained in more detail later.
<ToolboxData("<{0}:DDCal runat=server></{0}:DDCal>"),_
<Designer(GetType(DropdownCalendarDesigner))> _
Public Class DropDownCalendar
Inherits Calendar
'Constructor...
'PublicProperties...
'Overriden Subroutines...
End Class
Figure 3: The DropDownCalendarcontrol inherits directly from the Calendar control, declares a designer for smarttag support, extends the control with some custom properties, and overrides afew base subroutines.
The DropDownCalendar control overrides three subroutinesof the base Calendar control. The most significant of these is the Rendersubroutine, which is where nearly all the real work happens to create theDropDownCalendar control (see Figure 4).
Protected Overrides Sub Render(ByVal _
writer As HtmlTextWriter)
'display calendar on topof other elements
MyBase.Style(HtmlTextWriterStyle.Position) = "absolute"
'hide or show thecalendar initially
If Me.Expanded Then
Me.Style(HtmlTextWriterStyle.Display) = "block"
Else
Me.Style(HtmlTextWriterStyle.Display) = "none"
End If
'create the containingtable
Dim tbl As New Table
tbl.Enabled = Me.Enabled
tbl.BorderWidth = NewUnit(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(NewLiteralControl(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(NewLiteralControl("6"))
'render the containingtable
tbl.RenderControl(writer)
'render the base calendarcontrol
If Me.DesignMode = FalseOrElse Me.Expanded Then
MyBase.Render(writer)
End If
End Sub
Figure 4: TheRender event draws the text area and the button that drops down the calendar. Therendering of the calendar is delegated to the base Calendar control.
The first line is important. By ensuring the calendar isrendered with absolute positioning, this will allow the calendar to hover overany controls that are placed just below the DropDownCalendar control whenexpanded, just like the list portion of a DropDownList control.
The second code block specifies the Display style of thecalendar, which specifies whether it should initially be visible (block) orinvisible (none). When the arrow button or text area is clicked by the user atrun time, this value will be toggled by client-side code to hide and show thecalendar on demand. This JavaScript is output at the end of the third codeblock, and is assigned to the client-side OnClick event.
The fourth code block shown in Figure 4 continues creatingthe HTML table that positions everything, and creates the text area that willdisplay the selected date in the configured long or short string format. Thefifth code block styles one of the cells to look like a button so the user willunderstand 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 eventis overridden to keep the calendar portion expanded between postbacks when theuser changes months:
Protected Overrides Sub OnVisibleMonthChanged(ByVal _
newDate As Date, ByValpreviousDate As Date)
'keep calendar expandedafter month change
Me.Expanded = True
MyBase.OnVisibleMonthChanged(newDate, previousDate)
End Sub
Similarly, the OnSelectedChanged event is overridden toensure the calendar portion automatically hides once the user has selected adate:
Protected Overrides Sub OnSelectionChanged()
'hide calendar bydefault after selection
Me.Expanded = False
MyBase.OnSelectionChanged()
End Sub
When overriding base control events, it?s always goodpractice to call the base method to ensure the underlying control performs normallyits portion of the functionality.
Smart Tags
Three things must be done to enhance a custom control withsmart tags. First, the control class needs a Designer attribute that refers toa custom Designer class. This is demonstrated in Figure 3. Second, the customDesigner class must add a DesignerActionList (full of smart tag actions) to itsDesignerActionListCollection, as shown in Figure 5. Finally, theDesignerActionList class (shown in Listing One) mustprogrammatically add each desired DesignerAction. For all this to work, thecontrol?s project must have a reference to System.Design.dll.
Public Class DropdownCalendarDesigner
InheritsWeb.UI.Design.WebControls.CalendarDesigner
Private o_ActionLists AsDesignerActionListCollection
Public Overrides ReadOnlyProperty ActionLists() As _
System.ComponentModel.Design.DesignerActionListCollection
Get
If o_ActionLists IsNothing Then
o_ActionLists = NewDesignerActionListCollection
Dim ctl AsDropDownCalendar = _
CType(Component,DropDownCalendar)
o_ActionLists.Add(NewDropdownCalendarActions(ctl))
End If
Return o_ActionLists
End Get
End Property
End Class
Figure 5: TheDesigner for the DropDownCalendar control inherits and extends theCalendarDesigner, extending it with smart tag actions that are defined in thecustom DropDownCalendarActions class.
There?s not much code to the Designer class in Figure 5 becauseit is only implementing smart tag support. However, Designer classes can alsounlock other advanced design-time features, as well, such as support forcontrol templates and auto-formatting. The code in Figure 5 essentially doeslittle more than add a reference to the DropDownCalendarActions class, which isshown 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 isillustrated in Figure 6. A new DesignerActionItemCollection object isinstantiated and filled with smart tags, otherwise known asDesignerActionItems. There are four kinds of DesignerActionItems: Headers,Properties, Methods, and Text. All but the latter are demonstrated in ListingOne.
Figure 6: The smart tags for theDropDownCalendar control were added programmatically via a Designer class and aDesignerActionList class. Notice that the method item smart tags also show upautomatically at the bottom of the Properties window.
Headers are shown in bold text, properties are displayedas editable fields, and commands tend to show up as hyperlinks that execute custommethods on demand. Properties ? or more specifically, DesignActionPropertyItems? are quite versatile in their display; for example, automatically showing upas textboxes when representing strings, dropdownlists when representingenumerations, or checkboxes when representing Boolean values.
Notice that the properties and methods all have the headername passed as a constructor parameter so Visual Studio knows how to groupthem. Also notice the first parameter for each property item refers to a propertydeclaration further down in Listing One, which does the work of setting andgetting the property value from the underlying DropDownCalendar control. Similarly,the first constructor parameter for each method item refers to a functionlisted further down in Listing One. These methods do the real work. Althoughthe examples here are simple, they could be as complex as necessary ? launchingWindows forms, fancy wizards, or whatever else you can imagine.
Conclusion
Smart tags are a modern design-time feature that canenhance any control with a professional polish. They are a user-friendly way toallow application developers to configure controls in quick, intuitive ways. Thereis a significant amount of code involved, but most of it is simple andboilerplate.
Nearly any large control can be similarly enhanced withdropdown functionality so it can hide away when not immediately needed. Youcould try inheriting from a GridView control instead of a Calendar control, forexample, 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 thefootprint of that otherwise bulky control. I can also imagine a dropdownCheckBoxList or RadioButtonList. How about a dropdown Login control, or maybeeven a dropdown Crystal Report? I encourage you to combine your imagination andyour skills with the information you?ve learned here to come up with a customcontrol that saves your company time and money.
The complete sourcecode for the DropDownCalendar control is available for download (in both VB andC#).
Steve C. Orr is anMCSD and a Microsoft MVP in ASP.NET. He?s been developing software solutionsfor leading companies in the Seattlearea for more than a decade. When he?s not busy designing software systems orwriting about them, he can often be found loitering at local user groups andhabitually lurking in the ASP.NET newsgroup. Find out more about him at http://SteveOrr.netor e-mail him at mailto:Steve@Orr.net.
Begin Listing One ? theDesignerActionList class
Public Class DropdownCalendarActions
InheritsSystem.ComponentModel.Design.DesignerActionList
Private ctl AsDropdownCalendar
Public Overrides FunctionGetSortedActionItems() As _
System.ComponentModel.Design.DesignerActionItemCollection
Dim o_Items As _
DesignerActionItemCollection = _
NewDesignerActionItemCollection
' 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", _
"SelectedDate", "Properties"))
o_Items.Add(New _
DesignerActionPropertyItem("DisplayLongDate", _
"Display LongDate Format", "Properties"))
'add method items
o_Items.Add(New _
DesignerActionMethodItem(Me, "SelectJan1", _
"Select January1st", "Properties", _
"Selects JanuaryFirst", True))
o_Items.Add(New _
DesignerActionMethodItem(Me, "ToggleExpanded", _
"Toggle Expanded property","Properties", _
"Toggles thedropdown state", True))
o_Items.Add(New _
DesignerActionMethodItem(Me, "LaunchWebSite", _
"http://SteveOrr.net", Header2, _
"Launches SteveOrr's web site.", True))
Return o_Items
End Function
#Region " Properties "
Public PropertyDayNameFormat() _
AsSystem.Web.UI.WebControls.DayNameFormat
Get
Returnctl.DayNameFormat
End Get
Set(ByVal value AsDayNameFormat)
GetProperty("DayNameFormat").SetValue(ctl, value)
End Set
End Property
Public PropertySelectedDate() As Date
Get
Returnctl.SelectedDate
End Get
Set(ByVal value As Date)
GetProperty("SelectedDate").SetValue(ctl, value)
End Set
End Property
Public Property DisplayLongDate()As Boolean
Get
Returnctl.DisplayLongDate
End Get
Set(ByVal value AsBoolean)
GetProperty("DisplayLongDate").SetValue(ctl, value)
End Set
End Property
#End Region
#Region " Methods "
Public Sub New(ByValctlCal As DropDownCalendar)
MyBase.New(ctlCal)
ctl = ctlCal
End Sub
Public SubLaunchWebSite()
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("Jan1 " & Date.Now.Year.ToString))
End Sub
Private FunctionGetProperty(ByVal _
propertyName As String) As PropertyDescriptor
Dim o_Property AsPropertyDescriptor = _
TypeDescriptor.GetProperties(ctl).Item(propertyName)
If o_Property IsNotNothing Then
Return o_Property
Else
Throw New _
ArgumentException("Invalidproperty", _
propertyName)
End If
End Function
#End Region
End Class
End Listing One