Control Freak
LANGUAGES: VB.NET | SQL
ASP.NET VERSIONS: 1.0 | 1.1
Creating a ComboBox
Don't Let HTML Limitations Stand in Your Way
By Steve C. Orr
HTML supports TextBox controls, ListBox controls, and dropdown lists. Inexplicably however, there is no ComboBox control. As all Windows forms programmers know, a ComboBox control lets the user type in a value or select a value from a dropdown list. This functionality can be quite handy in a variety of situations. This article will use composition to create a ComboBox control that you can use in your Web applications.
Composite controls were discussed briefly in the last "Control Freak," but the details of such a design were postponed until now. While rendering generally produces the most CPU-efficient code, "composition" is usually more efficient with your development and testing time. We all appreciate efficiently performing code, but you must weigh this against the clich that time is money. If you're able to cut development time and still provide high-quality code, then you're the type of programmer most managers value.
E Pluribus Unum
A composite control encapsulates the functionality of two or more existing controls, resulting in one super control. Figure 1 demonstrates how several standard Web controls will be assembled into one ComboBox control. The primary sub-controls will be a TextBox, a ListBox, and a Label control. The TextBox will allow the user to type in a value, and it will display any value that the user might select from the dropdown list. This dropdown list will really be a standard ListBox control that will be shown 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 Label control. Finally, all of these will be inside a Panel control to help keep everything grouped properly. By taking advantage of the rich functionality that these well-tested controls provide, development and testing time will be saved by not "reinventing the wheel."
Figure 1: By combining several existing
controls, a brand new ComboBox control is born.
To determine the best design for the functionality of the control, a basic use case should be examined:
1) The user clicks the down arrow (which is really a Label) to drop down the item list.
2) The item list (which is really a ListBox) is made visible.
3) The user selects an item from the list.
4) The item list is made invisible, and the selected item's text is displayed in the text portion of the control (which is really a TextBox).
What's the best way to accomplish this functionality? The easiest way would be to do a postback between each action so the text and visible properties of the child controls can be set appropriately from server side code. This would work. However, it would also be very slow and inefficient to do this many postbacks to achieve such basic functionality. The bar will be set higher for this ComboBox control. Client-side JavaScript will handle these tasks so that no postbacks will be necessary. This will make the control much more responsive and professional.
For example, using the "visibility" style of the ListBox control, it can be shown and hidden on command from client-side code. The client-side OnClick event of the arrow Label control will invoke JavaScript that looks a lot like that shown in Figure 2. Of course, not every detail of the control can or should be done on the client side. After all, this is a server control that's being created.
if (MyListBox.style.visibility=='visible')
{
MyListBox.style.visibility='hidden';
}
else
{
MyListBox.style.visibility='visible';
}
Figure 2: This client-side JavaScript code will be executed by the user's browser. It will toggle the visibility of the ListBox control that simulates the dropdown portion of the ComboBox control.
Back to the Server
Because composition will be used (instead of rendering, the technique used for last month's bar graph control), the Render event won't be needed. Instead, the CreateChildControls method of the base System.Web.UI.Control class will be overridden, and the bulk of the work will be done there.
The skeleton of this composite control is listed in Figure 3. The code starts by importing some useful namespaces, then declaring the class name with some basic attributes that let it appear nicely in the toolbox at design time. Because this class is a control, it inherits from the standard Control class from which all Web controls are derived.
It also implements the INamingContainer interface. If you've ever examined the HTML output of your ASP.NET pages, you've probably noticed that the client-side names of the controls don't always match the names you've given them in the server code. This is because ASP.NET automatically renames them to keep things organized, and to ensure that no two controls have the same client-side ID. By implementing the INamingContainer interface, the custom control is identified as a container for the child controls it encapsulates. It also ensures that all the sub-controls are named similarly in the HTML output. This will be important for referencing the controls in client-side code.
Imports System.ComponentModel
Imports System.Web.UI
Imports System.Web.UI.WebControls
runat=server>{0}:ComboBox>")> _ Public
Class ComboBox Inherits
System.Web.UI.Control Implements
INamingContainer ' Declare private variables (and child
controls) here. Private
m_txt As New
TextBox() Private
m_btn As New
Label() Private
m_ddl As New
ListBox() Private
m_pnl As New
Panel() ' Define public properties. Public Property [Text]() As String Get Return
m_txt.Text End Get Set(ByVal Value As String) m_txt.Text = Value End Set End Property ' Protected Overrides Sub
CreateChildControls() ' Set the properties of your child
controls here. ' End Sub End Class Figure
3: The basic
structure of a composite control is quite simple. Some
private variables are then defined. In this case, the TextBox, Label,
ListBox, and Panel controls described earlier are declared. Then
the public properties are defined. The beauty of composite controls is that, in
many cases, these properties can be piped through to the appropriate child
control, thereby escaping the complexity of (and potential bugs associated
with) handling these properties manually. The child controls also handle their
own viewstate, which saves the effort of having to store the properties between
postbacks. Download
the full source code for this control to see that the Text and MaxLength
properties are sent to the underlying textbox control, border-related
properties are delegated to the Panel control, and data-binding
properties are piped-through to the ListBox control (see end of article
for download details). The full
code for the CreateChildControls method of the ComboBox control
is listed in Figure 4. The first code block dynamically builds client-side
script that is equivalent to the listing in Figure 2. This script will be
assigned to some client-side events. The StringBuilder class is the most
efficient way to concatenate strings. Protected Overrides
Sub CreateChildControls() ' Client-side script: toggle visibility of
dropdown list. Dim jscript As New
Text.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) AddHandler
m_txt.TextChanged, _ AddressOf Me.OnTextChanged m_txt.ID = "txt" ' Configure the arrow button (which is
really 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(New
LiteralControl(" ' 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 to
the control tree. End Sub Figure
4: Composite controls
encapsulate the functionality of two or more child controls. These child
controls are configured in the CreateChildControls method inherited from
the base Control class. By taking advantage of this technique, rich
controls can be made quickly by farming out tedious chores to existing
controls. The next
code block configures the textbox portion of the control. The first line turns
off AutoComplete, a normally nifty feature of Internet Explorer that will only
get in the way of the custom dropdown functionality. The TextBox is then
added to the Controls collection of the Panel control that keeps
the child controls nicely organized together. A server-side event is then
associated with the TextBox control. It will be raised whenever the user
changes the text of the control. (More on this later.) The
final line of the TextBox code block specifies that the client-side ID
of the control will be named txt. Because this control lies within
a naming container, the actual client-side ID of the control will be the ID of
the TextBox control (txt) combined with the ID of the
parent ComboBox control. In most cases, this will result in the
client-side ID of the TextBox being ComboBox1_DDL.
Of course, if there are two ComboBox controls on the form, the second
one 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 the
control at run time: Me.ID
+ "_" + m_txt.ID The next
code block configures the little arrow that sits to the right of the TextBox
control. ASCII character number 54 of the Webdings font will work well for
displaying the arrow. A border style is also set to make it look more like a
little button, even though it's actually a Label control. (A button Web
control could have been used instead, but these cause postbacks that are
undesirable in this case.) Then the JavaScript that was defined in the first
code block is assigned to the client-side OnClick event of the label.
This will cause the visibility of the dropdown list to toggle whenever the
arrow is clicked. Finally, this control is also added to the Controls
collection of the Panel control, and a line break The
final code block configures the ListBox control, which will appear and
disappear on command, simulating a dropdown list. The ID is set for the ListBox,
and the visibility is initially set to hidden. A client-side OnClick
event 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 only
bit left to discuss is the TextChanged event. This event will be raised
whenever 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 the
code-behind of Web forms that use the control: Public
Event TextChanged As
EventHandler The
event can then be raised by the control's code when appropriate: Protected Overridable Sub OnTextChanged( _ ByVal source
As Object, ByVal e As EventArgs) RaiseEvent
TextChanged(Me, e) End Sub The CreateChildControls
method defined that this sub should be called in response to the TextChanged
event of the child TextBox control. The event is then bubbled up to the
page that is hosting the ComboBox control. Fire It Up When all
the previous code has been put into a Web control library project, it can be
compiled into a DLL and added to the toolbox. Then it can be dropped onto a Web
form in any ASP.NET Web application and used just like any other control in the
toolbox. With a simple code-behind like that shown in Figure 5, output similar
to that shown in Figure 6 can be achieved. Private Sub Page_Load(ByVal sender As
System.Object, _ ByVal e As System.EventArgs) Handles
MyBase.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) _ Handles
ComboBox1.TextChanged labelResult.Text = "You chose "
& ComboBox1.Text End
Sub Figure
5: The ComboBox
is simple and intuitive to use from any Web form. Conclusion At this
point you have a reasonably professional ComboBox control in your hands.
You should also have a good understanding of composition and composite
controls. You should be able to create nearly any custom Web server control
that you can imagine by experimenting with the techniques outlined in this
article. You can glue sets of controls together like Legos and create a
consistent look and behavior throughout your Web site, no matter how large and
complex it may be. The sample code accompanying this article is available for
download. Steve C.
Orr is an MCSD and
a Microsoft MVP in ASP.NET. He's an independent consultant who develops
software solutions for many leading companies in the Seattle area. When he's
not busy designing software systems or writing about it, 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://Steve.Orr.net or e-mail him at mailto:[email protected]. Want More? If you
have the need for a ComboBox control such as this in your Web
applications, I suggest you go to http://www.metabuilders.com
and check out the free ComboBox control created by cohort Andy Smith. It
builds on many of the techniques outlined in this article and takes them to the
next level. Clearly he's worked hard on it, so if you find it useful consider
sending a donation his way. There's also some intriguing C# ComboBox
code available at http://www.codeproject.com/aspnet/combobox.asp.
"))
is added to
make sure the following ListBox control appears beneath the TextBox.
The client-side ID isn't specified, because it doesn't need to be referenced
from any client-side code; it doesn't really matter which ID ASP.NET decides to
give the control.
Figure 6: Output from running the code shown
in Figure 5.