All the List controls inherit from the base ListControl class. If you are not happy with the existing List controls, there is nothing to prevent you from building your own. In this section, we create a custom List control named the MultiSelectList control. This control renders two list boxes and an Add and Remove button. You can click the buttons to move items between the two list boxes (see Figure 10.16). Figure 10.16. Using the MultiSelectList control. The custom control uses client-side JavaScript to move the items between the two list boxes. Using JavaScript enables you to avoid posting the page back to the server each time a list item is moved. The client-side JavaScript is standards compliant so it will work with Internet Explorer 6.0, FireFox 1.0, and Opera 8.0. The code for the custom MultiSelectList is contained in Listing 10.17. Listing 10.17. MultiSelectList.vb [View full width] Imports System Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Collections.Specialized Namespace myControls ''' <summary> ''' Enables you to select mulitple list items ''' from two list boxes ''' </summary> <ValidationProperty("SelectedItem")> _ Public Class MultiSelectList Inherits ListControl Implements IPostBackDataHandler Private _rows As Integer = 5 ''' <summary> ''' This control is contained in a div ''' tag ''' </summary> Protected Overrides ReadOnly Property TagKey() As HtmlTextWriterTag Get Return HtmlTextWriterTag.Div End Get End Property ''' <summary> ''' The number of rows of list items to display ''' </summary> Public Property Rows() As Integer Get Return _rows End Get Set(ByVal Value As Integer) _rows = Value End Set End Property ''' <summary> ''' Name passed to client-side script ''' </summary> Private ReadOnly Property BaseName() As String Get Return ClientID & ClientIDSeparator End Get End Property ''' <summary> ''' Name of unselected items list box ''' </summary> Private ReadOnly Property UnselectedListName() As String Get Return BaseName & "unselected" End Get End Property ''' <summary> ''' Name of selected items list box ''' </summary> Private ReadOnly Property SelectedListName() As String Get Return BaseName & "selected" End Get End Property ''' <summary> ''' Name of hidden input field ''' </summary> Private ReadOnly Property HiddenName() As String Get Return BaseName & "hidden" End Get End Property ''' <summary> ''' Register client scripts ''' </summary> Protected Overrides Sub OnPreRender(ByVal e As EventArgs) Page.RegisterRequiresPostBack(Me) ' Register hidden field Page.ClientScript.RegisterHiddenField(HiddenName, String.Empty) ' Register Include File If Not Page.ClientScript.IsClientScriptIncludeRegistered("MultiSe- lectList") Then Page.ClientScript.RegisterClientScriptInclude("MultiSelectList", Page .ResolveUrl("~/ClientScripts/MultiSelectList.js")) End If ' Register submit script Dim submitScript As String = String.Format("multiSelectList_submit('{0}')", BaseName) Page.ClientScript.RegisterOnSubmitStatement(Me.GetType(), Me.ClientID, submitScript) MyBase.OnPreRender(e) End Sub ''' <summary> ''' Render list boxes and buttons ''' </summary> Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter) ' Render Unselected RenderUnselected(writer) ' Render Buttons RenderButtons(writer) ' Render Selected RenderSelected(writer) ' Render clear break writer.AddStyleAttribute("clear", "both") writer.RenderBeginTag(HtmlTextWriterTag.Br) writer.RenderEndTag() End Sub ''' <summary> ''' Render the buttons ''' </summary> Private Sub RenderButtons(ByVal writer As HtmlTextWriter) writer.AddStyleAttribute("float", "left") writer.AddStyleAttribute(HtmlTextWriterStyle.Width, "20%") writer.AddStyleAttribute(HtmlTextWriterStyle.TextAlign, "center") writer.RenderBeginTag(HtmlTextWriterTag.Div) Dim addScript As String = String.Format("return multiSelectList_add('{0}');", BaseName) writer.AddAttribute(HtmlTextWriterAttribute.Onclick, addScript) writer.AddAttribute(HtmlTextWriterAttribute.Title, "Add Item") writer.RenderBeginTag(HtmlTextWriterTag.Button) writer.Write("-->") writer.RenderEndTag() writer.WriteBreak() Dim removeScript As String = String.Format("return multiSelectList_remove('{0 }');", BaseName) writer.AddAttribute(HtmlTextWriterAttribute.Onclick, removeScript) writer.AddAttribute(HtmlTextWriterAttribute.Title, "Remove Item") writer.RenderBeginTag(HtmlTextWriterTag.Button) writer.Write("<--") writer.RenderEndTag() writer.RenderEndTag() End Sub ''' <summary> ''' Render unselected list box ''' </summary> Private Sub RenderUnselected(ByVal writer As HtmlTextWriter) writer.AddStyleAttribute("float", "left") writer.AddStyleAttribute(HtmlTextWriterStyle.Width, "40%") writer.RenderBeginTag(HtmlTextWriterTag.Div) writer.AddAttribute(HtmlTextWriterAttribute.Size, _rows.ToString()) writer.AddStyleAttribute(HtmlTextWriterStyle.Width, "100%") writer.AddAttribute(HtmlTextWriterAttribute.Id, UnselectedListName) writer.AddAttribute(HtmlTextWriterAttribute.Name, UnselectedListName) writer.RenderBeginTag(HtmlTextWriterTag.Select) For Each item As ListItem In Items If Not item.Selected Then RenderListItem(writer, item) End If Next writer.RenderEndTag() writer.RenderEndTag() End Sub ''' <summary> ''' Render selected list items ''' </summary> Private Sub RenderSelected(ByVal writer As HtmlTextWriter) writer.AddStyleAttribute("float", "left") writer.AddStyleAttribute(HtmlTextWriterStyle.Width, "40%") writer.RenderBeginTag(HtmlTextWriterTag.Div) writer.AddAttribute(HtmlTextWriterAttribute.Size, _rows.ToString()) writer.AddStyleAttribute(HtmlTextWriterStyle.Width, "100%") writer.AddAttribute(HtmlTextWriterAttribute.Id, SelectedListName) writer.AddAttribute(HtmlTextWriterAttribute.Name, SelectedListName) writer.RenderBeginTag(HtmlTextWriterTag.Select) For Each item As ListItem In Items If item.Selected Then RenderListItem(writer, item) End If Next writer.RenderEndTag() writer.RenderEndTag() End Sub ''' <summary> ''' Render a list item ''' </summary> Private Sub RenderListItem(ByVal writer As HtmlTextWriter, ByVal item As ListItem) writer.AddAttribute(HtmlTextWriterAttribute.Value, item.Value) writer.RenderBeginTag(HtmlTextWriterTag.Option) writer.Write(item.Text) writer.RenderEndTag() End Sub ''' <summary> ''' Process postback data ''' </summary> Public Function LoadPostData(ByVal postDataKey As String, ByVal postCollection As NameValueCollection) As Boolean Implements IPostBackDataHandler.LoadPostData EnsureDataBound() ClearSelection() Dim values As String = postCollection(HiddenName) If values <> String.Empty Then Dim splitValues() As String = values.Split(","c) For Each value As String In splitValues Items.FindByValue(value).Selected = True Next End If Return False End Function ''' <summary> ''' Required by the IPostBackDataHandler interface ''' </summary> Public Sub RaisePostDataChangedEvent() Implements IPostBackDataHandler.RaisePostDataChangedEvent End Sub End Class End Namespace | Notice that the TagKey property of the base ListControl class is overridden. The elements of the control are contained in an HTML <div> tag. The MultiSelectList renders its user interface in the RenderContents() method. This method renders the two list boxes and button controls. Each unselected list item is rendered in the first list box and each selected item is rendered in the second list box. Furthermore, the MultiSelectList control implements the IPostBackDataHandler interface. When a user posts a page that contains the MultiSelectList control to the server, each item that the user selected is retrieved and the Items collection of the List control is updated. The control takes advantage of a client-side JavaScript library contained in a file named MultiSelectList.js. This JavaScript library is registered in the control's OnPreRender() method. The MultiSelectList.js library is contained in Listing 10.18. Listing 10.18. MultiSelectList.js function multiSelectList_add(baseName) { var unselectedList = document.getElementById(baseName + 'unselected'); var selectedList = document.getElementById(baseName + 'selected'); // Copy selected items for (var i=0;i < unselectedList.options.length;i++) { if (unselectedList.options[i].selected) { var item = unselectedList.removeChild(unselectedList.options[i]); selectedList.appendChild(item); } } // Prevent post return false; } function multiSelectList_remove(baseName) { var unselectedList = document.getElementById(baseName + 'unselected'); var selectedList = document.getElementById(baseName + 'selected'); // Copy unselected items for (var i=0;i < selectedList.options.length;i++) { if (selectedList.options[i].selected) { var item = selectedList.removeChild(selectedList.options[i]); unselectedList.appendChild(item); } } // Prevent post return false; } // This function executes when the page // is submitted. It stuffs all the // selected items into a hidden field function multiSelectList_submit(baseName) { var hidden = document.getElementById(baseName + 'hidden'); var selectedList = document.getElementById(baseName + 'selected'); var values = new Array(); for (var i=0;i<selectedList.options.length;i++) values.push(selectedList.options[i].value); hidden.value = values.join(','); } | Listing 10.18 contains three JavaScript functions. The first two functions simply move list items from one list box to the other list box. The multiSelectList_submit() function is called immediately before a page containing the MultiSelectList control is posted to the server. This control records each of the selected list items (the items in the second list box) to a hidden form field. The page in Listing 10.19 illustrates how you can use the MultiSelectList control. Listing 10.19. ShowMultiSelectList.aspx <%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="myControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script runat="server"> Protected Sub btnSubmit_Click(ByVal sender As Object, ByVal e As EventArgs) For Each item As ListItem In MultiSelectList1.Items If item.Selected Then lblSelected.Text &= String.Format("<li>{0} ({1})", item.Text, item.Value) End If Next End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Show MultiSelectList</title> </head> <body> <form runat="server"> <div> <b>Movies:</b> <custom:MultiSelectList DataSource DataTextField="Title" DataValueField="Id" Style="width:400px" Runat="server" /> <asp:RequiredFieldValidator ControlToValidate="MultiSelectList1" Text="Required" Runat="server" /> <asp:SqlDataSource SelectCommand="SELECT Id, Title FROM Movies" ConnectionString="<%$ ConnectionStrings:Movies %>" Runat="server" /> <p> <asp:Button Text="Submit" Runat="server" OnClick="btnSubmit_Click" /> </p> <hr /> <asp:Label EnableViewState="false" Runat="server" /> </div> </form> </body> </html> | In the page in Listing 10.19, the MultiSelectList control is bound to a SqlDataSource control, which represents the contents of the Movies database table. You can select movie titles in the MultiSelectList control by moving movie titles from one list box to the second list box. When you click the Submit button, the selected movies are displayed in a Label control. |