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
<DefaultProperty("Text"),
ToolboxData("<{0}:ComboBox
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
' <snip!> (Download the code to see
all properties).
Protected Overrides Sub
CreateChildControls()
' Set the properties of your child
controls here.
' <snip!>
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("<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 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 <br> 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.
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.
Figure 6: Output from running the code shown
in Figure 5.
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:Steve@Orr.net.
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.