|
LANGUAGES: VB.NET | SQL
ASP.NET VERSIONS: 1.x
Creating a ComboBox
Don't Let HTML Limitations Stand in Your Way
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.
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 AsNew TextBox()
Private m_btn AsNew Label()
Private m_ddl AsNew ListBox()
Private m_pnl AsNew Panel()
' Define public properties.
Public
Property [Text]() As String
Get
Return m_txt.Text
End
Get
Set ( ByVal
Value AsString)
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.
ProtectedPrivate 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: (Above) The ComboBox is simple
and intuitive to use from any Web form.

Figure 6: (Above) 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 in this article is available for
download.
This article was originally published in
ASP.NET Pro Magazine.
|
|