Control Freak
LANGUAGES: VB.NET |SQL
ASP.NET VERSIONS: 1.0 | 1.1
Creatinga ComboBox
Don'tLet HTML Limitations Stand in Your Way
By Steve C.Orr
HTMLsupports TextBox controls, ListBox controls, and dropdown lists.Inexplicably however, there is no ComboBox control. As all Windows formsprogrammers know, a ComboBox control lets the user type in a value orselect a value from a dropdown list. This functionality can be quite handy in avariety of situations. This article will use composition to create a ComboBoxcontrol that you can use in your Web applications.
Compositecontrols were discussed briefly in the last "Control Freak," but the details ofsuch a design were postponed until now. While rendering generally produces themost CPU-efficient code, "composition" is usually more efficient with yourdevelopment and testing time. We all appreciate efficiently performing code,but you must weigh this against the clich? that time is money. If you're ableto cut development time and still provide high-quality code, then you're thetype of programmer most managers value.
E Pluribus Unum
Acomposite control encapsulates the functionality of two or more existingcontrols, resulting in one super control. Figure 1 demonstrates how severalstandard Web controls will be assembled into one ComboBox control. Theprimary sub-controls will be a TextBox, a ListBox, and a Labelcontrol. The TextBox will allow the user to type in a value, and it willdisplay any value that the user might select from the dropdown list. Thisdropdown list will really be a standard ListBox control that will beshown and hidden when the arrow to the right of the TextBox is clicked.This arrow will be a character from the Webdings font displayed in a standard Labelcontrol. Finally, all of these will be inside a Panel control to helpkeep everything grouped properly. By taking advantage of the rich functionalitythat these well-tested controls provide, development and testing time will besaved by not "reinventing the wheel."
Figure 1: By combining several existingcontrols, a brand new ComboBox control is born.
Todetermine the best design for the functionality of the control, a basic use case should be examined:
1) Theuser clicks the down arrow (which is really a Label) to drop down theitem list.
2) Theitem list (which is really a ListBox) is made visible.
3) Theuser selects an item from the list.
4) Theitem list is made invisible, and the selected item's text is displayed in thetext portion of the control (which is really a TextBox).
What'sthe best way to accomplish this functionality? The easiest way would be to do apostback between each action so the text and visible properties of the childcontrols can be set appropriately from server side code. This would work.However, it would also be very slow and inefficient to do this many postbacksto achieve such basic functionality. The bar will be set higher for this ComboBoxcontrol. Client-side JavaScript will handle these tasks so that no postbackswill be necessary. This will make the control much more responsive andprofessional.
Forexample, using the "visibility" style of the ListBox control, it can beshown and hidden on command from client-side code. The client-side OnClickevent of the arrow Label control will invoke JavaScript that looks a lotlike that shown in Figure 2. Of course, not every detail of the control can orshould be done on the client side. After all, this is a server control that'sbeing created.
if(MyListBox.style.visibility=='visible')
{
MyListBox.style.visibility='hidden';
}
else
{
MyListBox.style.visibility='visible';
}
Figure2: Thisclient-side JavaScript code will be executed by the user's browser. It willtoggle the visibility of the ListBox control that simulates the dropdownportion of the ComboBox control.
Back to the Server
Becausecomposition will be used (instead of rendering, the technique used for lastmonth's bar graph control), the Render event won't be needed. Instead,the CreateChildControls method of the base System.Web.UI.Controlclass will be overridden, and the bulk of the work will be done there.
Theskeleton of this composite control is listed in Figure 3. The code starts byimporting some useful namespaces, then declaring the class name with some basicattributes that let it appear nicely in the toolbox at design time. Becausethis class is a control, it inherits from the standard Control classfrom which all Web controls are derived.
It alsoimplements the INamingContainer interface. If you've ever examined theHTML output of your ASP.NET pages, you've probably noticed that the client-sidenames of the controls don't always match the names you've given them in theserver code. This is because ASP.NET automatically renames them to keep thingsorganized, and to ensure that no two controls have the same client-side ID. Byimplementing the INamingContainer interface, the custom control isidentified as a container for the child controls it encapsulates. It alsoensures that all the sub-controls are named similarly in the HTML output. Thiswill be important for referencing the controls in client-side code.
Imports System.ComponentModel
Imports System.Web.UI
Imports System.Web.UI.WebControls
<DefaultProperty("Text"),ToolboxData("<{0}:ComboBox
runat=server></{0}:ComboBox>")> _
PublicClass ComboBox
InheritsSystem.Web.UI.Control
ImplementsINamingContainer
' Declare private variables (and childcontrols) here.
Privatem_txt As NewTextBox()
Privatem_btn As NewLabel()
Privatem_ddl As NewListBox()
Privatem_pnl As NewPanel()
' Define public properties.
Public Property [Text]() As String
Get
Returnm_txt.Text
End Get
Set(ByVal Value As String)
m_txt.Text = Value
End Set
End Property
' <snip!> (Download the code to seeall properties).
Protected Overrides SubCreateChildControls()
' Set the properties of your childcontrols here.
' <snip!>
End Sub
End Class
Figure3: The basicstructure of a composite control is quite simple.
Someprivate variables are then defined. In this case, the TextBox, Label,ListBox, and Panel controls described earlier are declared. Thenthe public properties are defined. The beauty of composite controls is that, inmany cases, these properties can be piped through to the appropriate childcontrol, thereby escaping the complexity of (and potential bugs associatedwith) handling these properties manually. The child controls also handle theirown viewstate, which saves the effort of having to store the properties betweenpostbacks.
Downloadthe full source code for this control to see that the Text and MaxLengthproperties are sent to the underlying textbox control, border-relatedproperties are delegated to the Panel control, and data-bindingproperties are piped-through to the ListBox control (see end of articlefor download details).
The fullcode for the CreateChildControls method of the ComboBox controlis listed in Figure 4. The first code block dynamically builds client-sidescript that is equivalent to the listing in Figure 2. This script will beassigned to some client-side events. The StringBuilder class is the mostefficient way to concatenate strings.
Protected OverridesSub CreateChildControls()
' Client-side script: toggle visibility ofdropdown list.
Dim jscript As NewText.StringBuilder()
jscript.Append("if (")
jscript.Append(Me.ID& _
"_DDL.style.visibility=='visible')")
jscript.Append("{" & Me.ID & _
"_DDL.style.visibility='hidden';}")
jscript.Append("else{" & Me.ID & _
"_DDL.style.visibility='visible';}")
' Configure the textbox.
m_txt.Attributes.Add("AUTOCOMPLETE", "off")
m_pnl.Controls.Add(m_txt)
AddHandlerm_txt.TextChanged, _
AddressOf Me.OnTextChanged
m_txt.ID = "txt"
' Configure the arrow button (which isreally a label).
m_btn.Font.Name = "webdings"
m_btn.Text = Chr(54)
m_btn.BorderStyle = BorderStyle.Outset
m_btn.Attributes.Add("OnClick",jscript.ToString)
m_pnl.Controls.Add(m_btn)
m_pnl.Controls.Add(NewLiteralControl("<br>"))
' Configure the dropdownlist.
m_ddl.ID = "DDL"
m_ddl.Style.Add("visibility","hidden")
m_ddl.Attributes.Add("onclick", Me.ID + "_txt.value=" _
+ Me.ID +"_DDL.value;" + jscript.ToString)
m_pnl.Controls.Add(m_ddl)
Controls.Add(m_pnl) ' Add the panel tothe control tree.
End Sub
Figure4: Composite controlsencapsulate the functionality of two or more child controls. These childcontrols are configured in the CreateChildControls method inherited fromthe base Control class. By taking advantage of this technique, richcontrols can be made quickly by farming out tedious chores to existingcontrols.
The nextcode block configures the textbox portion of the control. The first line turnsoff AutoComplete, a normally nifty feature of Internet Explorer that will onlyget in the way of the custom dropdown functionality. The TextBox is thenadded to the Controls collection of the Panel control that keepsthe child controls nicely organized together. A server-side event is thenassociated with the TextBox control. It will be raised whenever the userchanges the text of the control. (More on this later.)
Thefinal line of the TextBox code block specifies that the client-side IDof the control will be named txt. Because this control lies withina naming container, the actual client-side ID of the control will be the ID ofthe TextBox control (txt) combined with the ID of theparent ComboBox control. In most cases, this will result in theclient-side ID of the TextBox being ComboBox1_DDL.Of course, if there are two ComboBox controls on the form, the secondone will have an ID of ComboBox2_DDL. As seen further down in the code,syntax like this can be used to determine the actual client-side ID of thecontrol at run time:
Me.ID+ "_" + m_txt.ID
The nextcode block configures the little arrow that sits to the right of the TextBoxcontrol. ASCII character number 54 of the Webdings font will work well fordisplaying the arrow. A border style is also set to make it look more like alittle button, even though it's actually a Label control. (A button Webcontrol could have been used instead, but these cause postbacks that areundesirable in this case.) Then the JavaScript that was defined in the firstcode block is assigned to the client-side OnClick event of the label.This will cause the visibility of the dropdown list to toggle whenever thearrow is clicked. Finally, this control is also added to the Controlscollection of the Panel control, and a line break <br> is added tomake sure the following ListBox control appears beneath the TextBox.The client-side ID isn't specified, because it doesn't need to be referencedfrom any client-side code; it doesn't really matter which ID ASP.NET decides togive the control.
Thefinal code block configures the ListBox control, which will appear anddisappear on command, simulating a dropdown list. The ID is set for the ListBox,and the visibility is initially set to hidden. A client-side OnClickevent attribute is then specified. When a user clicks on an item in the ListBox,client-side code will copy the text of the clicked item into the TextBox,and the visibility of the dropdown is then toggled to hidden using the JavaScript defined in the first code block.
The onlybit left to discuss is the TextChanged event. This event will be raisedwhenever the user types in a new value or selects one from the dropdown list.Such an event must be publicly declared so it can be referenced from thecode-behind of Web forms that use the control:
PublicEvent TextChanged AsEventHandler
Theevent can then be raised by the control's code when appropriate:
Protected Overridable Sub OnTextChanged( _
ByVal sourceAs Object, ByVal e As EventArgs)
RaiseEventTextChanged(Me, e)
End Sub
The CreateChildControlsmethod defined that this sub should be called in response to the TextChangedevent of the child TextBox control. The event is then bubbled up to thepage that is hosting the ComboBox control.
Fire It Up
When allthe previous code has been put into a Web control library project, it can becompiled into a DLL and added to the toolbox. Then it can be dropped onto a Webform in any ASP.NET Web application and used just like any other control in thetoolbox. With a simple code-behind like that shown in Figure 5, output similarto that shown in Figure 6 can be achieved.
Private Sub Page_Load(ByVal sender AsSystem.Object, _
ByVal e As System.EventArgs) HandlesMyBase.Load
ComboBox1.MaxLength = 30
ComboBox1.Width = Unit.Pixel(150)
ComboBox1.DataSource = MyDataSource()
ComboBox1.DataBind()
End Sub
Private Sub ComboBox1_TextChanged(ByVal sender As Object, _
ByVal e As System.EventArgs) _
HandlesComboBox1.TextChanged
labelResult.Text = "You chose "& ComboBox1.Text
EndSub
Figure5: The ComboBoxis simple and intuitive to use from any Web form.
Figure 6: Output from running the code shownin Figure 5.
Conclusion
At thispoint you have a reasonably professional ComboBox control in your hands.You should also have a good understanding of composition and compositecontrols. You should be able to create nearly any custom Web server controlthat you can imagine by experimenting with the techniques outlined in thisarticle. You can glue sets of controls together like Legos and create aconsistent look and behavior throughout your Web site, no matter how large andcomplex it may be.
The sample code accompanying this article is available fordownload.
Steve C.Orr is an MCSD anda Microsoft MVP in ASP.NET. He's an independent consultant who developssoftware solutions for many leading companies in the Seattle area. When he'snot busy designing software systems or writing about it, he can often be foundloitering at local user groups and habitually lurking in the ASP.NET newsgroup.Find out more about him at http://Steve.Orr.net or e-mail him at mailto:Steve@Orr.net.
Want More?
If youhave the need for a ComboBox control such as this in your Webapplications, I suggest you go to http://www.metabuilders.comand check out the free ComboBox control created by cohort Andy Smith. Itbuilds on many of the techniques outlined in this article and takes them to thenext level. Clearly he's worked hard on it, so if you find it useful considersending a donation his way. There's also some intriguing C# ComboBoxcode available at http://www.codeproject.com/aspnet/combobox.asp.