April 10, 2006 12:04 AM

Clear Your Calendar

Clean Up the Clutter with a Custom DropDownCalendar Control
DevConnections
Rating: (0)

ControlFreak

LANGUAGES:VB.NET | C#

ASP.NETVERSIONS: 2.x

 

Clear Your Calendar

Clean Up the Clutter with a Custom DropDownCalendar Control

 

 

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

 

 

 

Add a Comment

There are no comments to display. Be the first one!
You must log on before posting a comment.

Are you a new visitor? Register Here

advertisement




Comments from the DevConnections Community

Join our community of development pros.

Windows problem

I all, I have a problem on my Windows Vista that began afetr the purchase of an external Hard Disk Freecom. A few days afetr the purchase I discon...

Most Recent Posts

GOOGLE LINKS
SPONSORED LINKS
FEATURED LINKS