List Controls

The list controls family has four members: ListBox, DropDownList, CheckBoxList, and RadioButtonList. All four have two important characteristics in common: they all derive from System.Web.UI.WebControls.ListControl, and they’re all designed to present lists of items to the user. ListBox and DropDownList controls display textual items that the user can select. Both render back to the browser as HTML <select> tags. CheckBoxList and RadioButtonList display arrays of check boxes and radio buttons and render as <input type=“checkbox”> and <input type=“radio”> tags, respectively. The <input> tags are optionally contained in an HTML table for alignment purposes.

Items in a list control are represented by instances of ListItem. Instances of ListItem are declared with <asp:ListItem> tags. Inside a ListItem are string properties named Text and Value. Text exposes the text that represents the item in a list control; Value allows an arbitrary string to be associated with the item. ListItem also exposes a Boolean property named Selected that determines whether the item is selected. The following statements declare a ListBox control containing four items and select the second item:

<asp:ListBox?ID="MyListBox" RunAt="server">
??<asp:ListItem?Text="John" RunAt="server" />
??<asp:ListItem?Text="Paul" Selected="true" RunAt="server" />
??<asp:ListItem?Text="George" RunAt="server" />
??<asp:ListItem?Text="Ringo" RunAt="server" />
</asp:ListBox>

A minor change to the code produces a DropDownList instead of a ListBox:

<asp:DropDownList?ID="MyDropDownList" RunAt="server">
??<asp:ListItem?Text="John" RunAt="server" />
??<asp:ListItem?Text="Paul" Selected="true" RunAt="server" />
??<asp:ListItem?Text="George" RunAt="server" />
??<asp:ListItem?Text="Ringo" RunAt="server" />
</asp:DropDownList>

In a ListBox or DropDownList, a ListItem’s Selected property determines whether the item is selected (true) or not selected (false). In a CheckBoxList or RadioButtonList, the same property determines whether the corresponding control is checked or unchecked.

Following a postback, a server-side script doesn’t have to examine every item in a list control to determine which one is currently selected. List controls inherit public properties named SelectedIndex and SelectedItem from the base class ListControl. Thus, a script can determine which radio button in a RadioButtonList is selected by reading its 0-based index:

int?index?=?MyRadioButtonList.SelectedIndex;

SelectedIndex and SelectedItem aren’t that interesting for CheckBoxList controls because multiple check boxes in the list might be checked, but they’re extremely useful for other types of list controls.

List controls fire SelectedIndexChanged events when the selection changes—that is, when a new item is selected in a ListBox or DropDownList or a button is clicked in a CheckBoxList or RadioButtonList. By default, the event doesn’t fire until something else on the page causes a postback. However, all list controls inherit an AutoPostBack property from ListControl that you can set to true to fire SelectedIndexChanged events immediately.

DropDownList Controls

DropDownList controls display items in a drop-down list that resembles a Windows combo box. A classic use for DropDownList controls is to display a list of the 50 U.S. states in a form that solicits an address. The following code sample presents such a list and echoes the user’s choice to the Web page:

<html>
??<body>
????<form?runat="server">
??????<asp:DropDownList?ID="StateList" RunAt="server">
????????<asp:ListItem?Text="AL" RunAt="server" />
????????<asp:ListItem?Text="AK" RunAt="server" />
????????<asp:ListItem?Text="AR" RunAt="server" />
??????????.
??????????.
??????????.
????????<asp:ListItem?Text="WI" RunAt="server" />
????????<asp:ListItem?Text="WV" RunAt="server" />
????????<asp:ListItem?Text="WY" RunAt="server" />
??????</asp:DropDownList>
??????<asp:Button?Text="Submit" OnClick="OnSubmit" RunAt="server" />
??????<br>
??????<asp:Label?ID="Output" RunAt="server" />
????</form>
??</body>
</html>

<script?language="C#" runat="server">
??void?OnSubmit?(Object?sender,?EventArgs?e)
??{
??????Output.Text?=?StateList.SelectedItem.Text;
??}
</script>

Figure 6-21 later in the chapter contains a complete listing for a DropDownList control that displays the abbreviations of all 50 U.S. states plus the District of Columbia.

ListBox Controls

ListBox controls are similar to DropDownList controls, but they display their items in a static list rather than in a drop-down list. The following example creates a ListBox control that displays the names of the U.S. states and writes the user’s selection to the Web page:

<html>
??<body>
????<form?runat="server">
??????<asp:ListBox?ID="StateList" Rows="10" RunAt="server">
????????<asp:ListItem?Text="Alabama" RunAt="server" />
????????<asp:ListItem?Text="Alaska" RunAt="server" />
????????<asp:ListItem?Text="Arkansas" RunAt="server" />
??????????.
??????????.
??????????.
????????<asp:ListItem?Text="Wisconsin" RunAt="server" />
????????<asp:ListItem?Text="West?Virginia" RunAt="server" />
????????<asp:ListItem?Text="Wyoming" RunAt="server" />
??????</asp:ListBox>
??????<asp:Button?Text="Submit" OnClick="OnSubmit" RunAt="server" />
??????<br>
??????<asp:Label?ID="Output" RunAt="server" />
????</form>
??</body>
</html>

<script?language="C#" runat="server">
??void?OnSubmit?(Object?sender,?EventArgs?e)
??{
??????Output.Text?=?StateList.SelectedItem.Text;
??}
</script>

By default, a ListBox control is sized to display only four items at a time. The Rows attribute in the <asp:ListBox> tag above increases the ListBox height to 10 items.

The only functional difference between a ListBox control and a DropDownList is that the former can be programmed to support multiple selections. A SelectionMode=“Multiple” attribute in the control tag creates a multiple-selection ListBox:

<asp:ListBox?ID="StateList" SelectionMode="Multiple"
??Rows="10" RunAt="server">

Unfortunately, the ListBox class lacks a public method or property for retrieving the indices of the items selected in a multiple-selection list box. To figure out which items the user selected, you have to iterate through all the list box’s items, checking their Selected properties one by one. The following method takes a ListBox reference as an input parameter and returns an array of integers containing the 0-based indices of all selected items:

int[]?GetSelectedIndices?(ListBox?lb)
{
????ArrayList?a?=?new?ArrayList?();
????for?(int?i=0;?i<lb.Items.Count;?i++)?{
????????if?(lb.Items[i].Selected)
????????????a.Add?(i);
????}
????int?[]?indices?=?new?int[a.Count];
????a.CopyTo?(indices);
????return?indices;
}

With GetSelectedIndices defined this way, the statement

int[]?indices?=?GetSelectedIndices?(StateList);

identifies all the items selected in the multiple-selection ListBox named “StateList.”

CheckBoxList Controls

The CheckBoxList control creates an array of check boxes. The following statements display four vertically stacked check boxes:

<asp:CheckBoxList?ID="MyCheckBoxList" RunAt="server">
??<asp:ListItem?Text="John" RunAt="server" />
??<asp:ListItem?Text="Paul" RunAt="server" />
??<asp:ListItem?Text="George" RunAt="server" />
??<asp:ListItem?Text="Ringo" RunAt="server" />
</asp:CheckBoxList>

To determine whether a given check box is checked, read its Selected property from a server-side script:

//?Is?the?third?check?box?checked?
if?(MyCheckBoxList.Items[2].Selected)?{
????//?The?check?box?is?checked
else?{
????//?The?check?box?is?not?checked
}

Creating an array of check boxes with CheckBoxList is generally preferable to using an array of <asp:CheckBox> tags because CheckBoxList makes it easy to align check boxes in rows and columns and to control the spacing between check boxes. Two properties control the check boxes’ layout: RepeatColumns and RepeatDirection. The following statements create an array of check boxes divided into four rows and three columns. The first row contains the check boxes whose indices are 0–2, the second row check boxes 3–5, and so on:

<asp:CheckBoxList?ID="MyCheckBoxList" RepeatColumns="3"
??RepeatDirection="Horizontal" RunAt="server">
??<asp:ListItem?Text="Item?0" RunAt="server" />
??<asp:ListItem?Text="Item?1" RunAt="server" />
??<asp:ListItem?Text="Item?2" RunAt="server" />
??<asp:ListItem?Text="Item?3" RunAt="server" />
??<asp:ListItem?Text="Item?4" RunAt="server" />
??<asp:ListItem?Text="Item?5" RunAt="server" />
??<asp:ListItem?Text="Item?6" RunAt="server" />
??<asp:ListItem?Text="Item?7" RunAt="server" />
??<asp:ListItem?Text="Item?8" RunAt="server" />
??<asp:ListItem?Text="Item?9" RunAt="server" />
??<asp:ListItem?Text="Item?10" RunAt="server" />
??<asp:ListItem?Text="Item?11" RunAt="server" />
</asp:CheckBoxList>

Changing RepeatDirection to Vertical modifies the array so that the first column contains check boxes 0–3, the second column contains check boxes 4–7, and the third column contains check boxes 8–11:

<asp:CheckBoxList?ID="MyCheckBoxList" RepeatColumns="3"
??RepeatDirection="Vertical" RunAt="server">

To change the spacing between check boxes, use CheckBoxList’s CellPadding and CellSpacing properties. The values that you specify are attached to the <table> tag that the CheckBoxList returns.

RadioButtonList Controls

RadioButtonList simplifies the task of creating groups of radio buttons and finding out which radio button in a group is selected. The statements

<asp:RadioButtonList?ID="MyRadioButtonList" RunAt="server">
??<asp:ListItem?Text="John" Selected="true" RunAt="server" />
??<asp:ListItem?Text="Paul" RunAt="server" />
??<asp:ListItem?Text="George" RunAt="server" />
??<asp:ListItem?Text="Ringo" RunAt="server" />
</asp:RadioButtonList>

create a column of radio buttons and check the first one. A server-side script can use RadioButtonList.SelectedIndex to determine which button the user selected:

int?index?=?MyRadioButtonList.SelectedIndex;

Like CheckBoxList, RadioButtonList features properties named RepeatColumns and RepeatDirection that can be used to align the radio buttons in rows and columns, and properties named CellPadding and CellSpacing that control the spacing between radio buttons. Because radio buttons never appear by themselves (that is, without other radio buttons), and because SelectedIndex makes it so easy to find out which radio button is selected, RadioButtonList all but obviates the need for RadioButton to even exist.

Data Binding with List Controls

Think back for a moment to the currency converter Web form named Converter.aspx presented in Chapter 5. Converter.aspx uses a ListBox to display a list of international currencies. But rather than statically defining the ListBox’s contents with <asp:ListItem> tags, Converter.aspx adds items to the ListBox by reading entries from an XML file and calling Add on the ListBox’s Items collection. Here’s that code again:

DataSet?ds?=?new?DataSet?();
ds.ReadXml?(Server.MapPath?("Rates.xml"));
foreach?(DataRow?row?in?ds.Tables[0].Rows)
????Currencies.Items.Add?(row["Currency"].ToString?());

Because initializing list controls at run time using the results of database queries or data extracted from XML files is so common, ASP.NET’s list controls support a feature called data binding. Rather than initialize the ListBox control by calling ListItemCollection.Add repeatedly, Converter.aspx could have done this:

DataSet?ds?=?new?DataSet?();
ds.ReadXml?(Server.MapPath?("Rates.xml"));
Currencies.DataSource?=?ds;
Currencies.DataTextField?= "Currency";
Currencies.DataBind?();

This method is simpler and more intuitive. It also makes the code more generic by eliminating direct interactions with the DataSet.

How does data binding work? All list controls inherit from ListControl properties named DataSource, DataTextField, and DataValueField. DataSource identifies a data source. It can be initialized with a reference to any object that implements the FCL’s IEnumerable or IListSource interface. IEnumerable is an enumeration interface that allows a control to interact with a data source using a well-defined protocol. IListSource permits objects that don’t implement IEnumerable themselves but that have subobjects that implement IList (which derives from IEnumerable) to expose their subobjects’ IList interfaces. Because DataSet implements IListSource, a list control can enumerate the rows in the DataTable that a DataSet contains. DataTextField connects the Text property of the list control’s items to a field in the data source. DataValueField specifies which field, if any, provides the items’ Value properties. List controls also inherit a method named DataBind from the ListControl base class. DataBind commands the control to initialize itself from the data source.

Literally dozens of FCL classes implement IEnumerable, meaning a list control can bind to a wide variety of data sources. The following example initializes a ListBox named “MyListBox” by binding to an array of strings. The example works because an array is an instance of System.Array, and System.Array implements IList:

string[]?names?=?{ "John", "Paul", "George", "Ringo" };
MyListBox.DataSource?=?names;
MyListBox.DataBind?();

There’s no need to initialize DataTextField when binding to an array because an array holds a single column of data. That column is automatically bound to the list items’ Text property.

You can write custom data types that support binding to list controls by implementing IEnumerable in those types. The following example defines a class named Beatles that serves up the names of the Fab Four. It implements IEnumerable’s one and only method, GetEnumerator, by returning an IEnumerator interface implemented by a nested class named Enumerator:

class?Beatles?:?IEnumerable
{
????protected?Enumerator?enumerator?=?new?Enumerator?();

????public?IEnumerator?GetEnumerator?()
????{
????????return?enumerator;
????}

????public?class?Enumerator?:?IEnumerator
????{
????????protected?int?index?=?-1;
????????protected?string[]?names?=
????????????{ "John", "Paul", "George", "Ringo" };

????????public?object?Current
????????{
????????????get
????????????{
????????????????if?(index?==?-1)
????????????????????index?=?0;?//?Just?in?case
????????????????return?names[index];
????????????}
????????}

????????public?bool?MoveNext?()
????????{
????????????if?(index?<?(names.Length?-?1))?{
????????????????index++;
????????????????return?true;
????????????}
????????????return?false;
????????}

????????public?void?Reset?()
????????{
????????????index?=?-1;
????????}
????}
}

Two simple statements initialize a ListBox control with the names encapsulated in Beatles:

MyListBox.DataSource?=?new?Beatles?();
MyListBox.DataBind?();

Figure 6-1 contains a modified version of Converter.aspx that populates its list box by binding to a DataSet containing the currency and exchange values read from Rates.xml. It also stores exchange rates in the list box items’ Value properties, eliminating the need to access the XML file again when OnConvert is called. Changes are highlighted in bold.

Converter2.aspx
<%@?Import?Namespace=System.Data?%>

<html>
??<body>
????<h1>Currency?Converter</h1>
????<hr>
????<form?runat="server">
??????Target?Currency<br>
??????<asp:ListBox?ID="Currencies" Width="256" RunAt="server" /><br>
??????<br>
??????Amount?in?U.S.?Dollars<br>
??????<asp:TextBox?ID="USD" Width="256" RunAt="server" /><br>
??????<br>
??????<asp:Button?Text="Convert" ID="ConvertButton" Width="256"
????????RunAt="server" /><br>
??????<br>
??????<asp:Label?ID="Output" RunAt="server" />
????</form>
??</body>
</html>

<script?language="C#" runat="server">
??void?Page_Init?(Object?sender,?EventArgs?e)
??{
??????//?Wire?the?Convert?button?to?OnConvert
??????ConvertButton.Click?+=?new?EventHandler?(OnConvert);
??}

??void?Page_Load?(Object?sender,?EventArgs?e)
??{
??????//?If?this?isn't?a?postback,?initialize?the?ListBox
??????if?(!IsPostBack)?{
??????????DataSet?ds?=?new?DataSet?();
??????????ds.ReadXml?(Server.MapPath?("Rates.xml"));
??????????Currencies.DataSource?=?ds;
??????????Currencies.DataTextField?= "Currency";
??????????Currencies.DataValueField?= "Exchange";
??????????Currencies.DataBind?();
??????????Currencies.SelectedIndex?=?0;
??????}
??}

??void?OnConvert?(Object?sender,?EventArgs?e)
??{
??????//?Perform?the?conversion?and?display?the?results
??????try?{
??????????decimal?dollars?=?Convert.ToDecimal?(USD.Text);
??????????decimal?rate?=
??????????????Convert.ToDecimal?(Currencies.SelectedItem.Value);
??????????decimal?amount?=?dollars?*?rate;
??????????Output.Text?=?amount.ToString?("f2");
??????}
??????catch?(FormatException)?{
??????????Output.Text?= "Error";
??????}
??}
</script>
Figure 6-1
A currency converter that takes advantage of data binding.