Dynamic Controls

Normally, server controls are hard-coded on a page because you know in advance what types of controls you need and how many are needed. For certain applications, though, you may not know this information. Either you may be aware of which types of controls are needed but not know how many; or you may not know exactly what controls are required to respond to page events.

This is especially the case when page information is imported from external data sources. For instance, assume you want to produce Button controls to display book types from the BooksDB.mdb database. The number of buttons needed depends on the number of book types in the database. Although it currently contains six different types, over time this number might expand or contract depending on maintenance activities surrounding the database. Some types may be added, others removed. Therefore, only as many buttons are needed as there are currently book types in the database, and this number isn't known until the database is accessed.

The following example displays six buttons to match the number of different book types. However, these buttons are not hard-coded on the page. They are created by a script which first determines how many different book types are in the database; then the script creates this many buttons. The controls are produced dynamically, when the page is loaded. They must, in fact, be created each time the page is loaded or posted back since they are not hard-coded on the page.

Display Book Types

      
      


BookIDBookTitleBookPriceBookQty
DB111Oracle Database$69.9910
DB222Databases in Depth$29.956
DB333Database Processing$136.6512
DB444Access Database Design$34.9525
DB555SQL Server 2005$29.990

Figure 6-71. Dynamically created Button controls.

Creating Controls with Script

Any type of server control can be created in script, producing it on-the-fly as needed. The statement used to create a new control is shown below.

Dim Control As New Type
Figure 6-72. General format for creating a control dynamically.

A Control object is declared as a new control Type. The name assigned to the Control follows Visual Basic naming conventions. The control Type is one of the standard types of server controls. For example, to create an <asp:Label> control, declare it as a Label type; to create an <asp:Button> control, declare it as a Button type; to create an <asp:TextBox> control, declare it as a TextBox type; and so forth.

Dim MyLabel As New Label
Dim MyButton As New Button
Dim MyTextBox As New TextBox
Listing 6-57. Declaring server control types.

The <asp:PlaceHolder> Control

Once a control is created, it is placed on the page by adding it to one of the Controls collections appearing on the page. The page itself has a Controls collection (Page.Controls) representing all controls on the page and to which new controls can be added. Preferably, though, you create a special area on the page as the location for adding new controls.

An <asp:PlaceHolder> control identifies a location where dynamically created controls can be placed. A control is assigned to this PlaceHolder's Controls collection to appear at this exact location on a page. The general format for creating a PlaceHolder control is shown below.

<asp:PlaceHolder id="id" Runat="Server"/>
Figure 6-73. General format for <asp:PlaceHolder> control.

The PlaceHolder itself does not occupy space on the page. It simple reserves an area where dynamically created controls can be added. It must be assigned an id so that a script can identify this PlaceHolder's Controls collection as the location where a control is added.

Adding Controls to a PlaceHolder

Once a PlaceHolder appears on the page, its Add() method identifies the control to add to its Controls collection using the following general format.

PlaceholderId.Controls.Add(Control)
Figure 6-74. General format for adding a control to the PlaceHolders's Controls collection.

PlaceholderId identifies a previously defined PlaceHolder to which a new control is added. Control gives the name of a dynamically created control to add next in order to this PlaceHolder's Controls collection.

For example, the code below declares a PlaceHolder to which dynamically created controls can be added. The script creates a new Label control and adds it to this PlaceHolder.

<SCRIPT Runat="Server">

Sub Page_Load

  Dim MyLabel As New Label
  MyPlaceHolder.Controls.Add(MyLabel)

End Sub

</SCRIPT>

<form Runat="Server">

<asp:PlaceHolder id="MyPlaceHolder" Runat="Server"/>

</form>
Listing 6-58. Adding a script-generated control to a PlaceHolder.

The newly created Label appears on the page at the location of the PlaceHolder. Any additional controls created by the script can be added to the PlaceHolder and appear next-in-line after the Label control. An option is to create different PlaceHolder controls in different areas of the page to which various script-generated controls can be added.

Creation of the new Label takes place in the Page_Load subprogram. It is necessarily placed here in order to create the Label each time the page loads since a script-generated control does not take part in the page's View State and is not automatically recreated between page postings.

Scripting Control Properties

In the previous example, the Label control appears in its default configuration without any styling or property settings. Normally, you will want to add properties to a control before placing it on the page. This is done for a script-generated control in exactly the same manner as for a control that is hard-coded on the page. In the following code rewrite for the example Label control, various properties are set prior to adding it to the PlaceHolder's Controls collection.

<%@ Import Namespace="System.Drawing" %>

<SCRIPT Runat="Server">

Sub Page_Load

  Dim MyLabel As New Label
  MyLabel.id = "Label01"
  MyLabel.Text = "This is a script-generated Label"
  MyLabel.ForeColor = Color.FromName("blue")
  MyLabel.Font.Bold = True
  MyLabel.Font.Size = FontUnit.Parse("14pt")
  MyPlaceHolder.Controls.Add(MyLabel)

End Sub

</SCRIPT>

<form Runat="Server">

<asp:PlaceHolder id="MyPlaceHolder" Runat="Server"/>

</form>
Listing 6-59. Styling a script-generated control.

This exact coding appears on this page to produce the Label control shown below. The PlaceHolder appears where the Label text is displayed.


This is a script-generated Label

Figure 6-75. A dynamically created Label control.

Scripting Control Events

When creating script activation controls such as Buttons, the controls need to include event handlers that call subprograms. These event handlers can be script-generated by using the statement format shown below.

AddHandler Control.Event, AddressOf Subprogram
Figure 6-76. General format for adding an event handler to a script-generated control.

Control is the name of the control to which the event handler is being added. Event is the type of event handler. Subprogram is the name of the subprogram to call.

Consider, for example, adding a script-generated Button control to a PlaceHolder. In addition to creating the button and setting its properties, an event handler needs to be added to the button to identify a subprogram to call when the button is clicked. Assume that the event handler OnClick="My_Subprogram" needs to be added to the button. The following AddHandler statement is added to the script.

Dim MyButton As New Button
MyButton.id = "Button1"
MyButton.Text = "Click Me"
AddHandler MyButton.Click, AddressOf My_Subprogram
MyPlaceHolder.Controls.Add(MyButton)
Listing 6-60. Adding an event handler to a script-generated control.

A handler for the Click event is added to the Button control. My_Subprogram is identified (AddressOf) as the subprogram to call when the button is clicked. When this button is added to the page—at the location of MyPlaceHolder—it appears just as if you had hard-coded it as

<asp:Button id="Button1" Text="Click Me" OnClick="My_Subprogram" 
Runat="Server"/>

The types of scripted events that can be added to controls are given by the same event handlers that are hard-coded for these controls. For example, a Button control recognizes both OnClick and OnCommand event handlers. Therefore, a Click event (as shown above) or a Command event can be scripted for a Button. Event names are the same as hard-coded event handlers with the prefix "On" removed. Note that when a Command event is scripted, CommandName and CommandArgument properties also can be set for a button.

Scripting Literal Controls

It is often the case that text and XHTML tags need to be added to a PlaceHolder to help arrange or format dynamically created controls. For example, you might wish to insert paragraphs, line breaks, blank characters, or other strings of text among the controls to correctly space them on the page. Since these strings of text and XHTML cannot be precoded in the PlaceHolder, they must be inserted by script at the same time the controls are being created. The special <asp:Literal> control is used for this purpose.

A Literal control is created in script just like other ASP.NET controls. It is declared as a Literal control, its Text property is assigned a text or XHTML string, and the control is added to the PlaceHolder among the other controls. In the following example, a Literal control is created in order to add a line break characters among other dynamically created controls.

Dim MyBreak As Literal
MyBreak.Text = "<br/>"
MyPlaceHolder.Controls.Add(MyBreak)
Listing 6-61. Adding a Literal control to a PlaceHolder.

As many Literal controls as necessary can be created and interspersed within a PlaceHolder to arrange or style other controls appearing in that area.

Scripting Command Buttons

Returning to the example of scripted buttons shown at the top of this page, the following script and code is used. As many command buttons are created as there are book types in the database. At the same time, Literal controls provide spacing of the buttons.

<%@ Import Namespace="System.Data.OleDb" %>
<%@ Import Namespace="System.Drawing" %>

<SCRIPT Runat="Server">

Sub Page_Load
  
  Dim DBConnection As OleDbConnection
  Dim DBCommand As OleDbCommand
  Dim DBReader As OleDbDataReader
  Dim SQLString As String
  Dim Counter As Integer = 0
  
  DBConnection = New OleDbConnection( _
    "Provider=Microsoft.Jet.OLEDB.4.0;" & _
    "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb"))
  DBConnection.Open()
  
  SQLString = "SELECT DISTINCT BookType FROM Books ORDER BY BookType"
  DBCommand = New OleDbCommand(SQLString, DBConnection)
  DBReader = DBCommand.ExecuteReader()
	
  While DBReader.Read()
    Dim MyButton as New Button
    MyButton.Text = DBReader("BookType")
    MyButton.CommandName = DBReader("BookType")
    MyButton.Width = Unit.Parse("100px")
    MyButton.Font.Size = FontUnit.Parse("8pt")
    AddHandler MyButton.Command, AddressOf Get_Books
    ButtonArea.Controls.Add(MyButton)
    
    Dim MyBlank As New Literal
    MyBlank.Text = "&nbsp;&nbsp;"
    ButtonArea.Controls.Add(MyBlank)
    
    Counter += 1
    If Counter Mod 3 = 0 Then
      Dim MyBreak As New Literal
      MyBreak.Text = "<br/>"
      ButtonArea.Controls.Add(MyBreak)
    End If

  End While
	
  DBReader.Close()
  DBConnection.Close()

End Sub

Sub Get_Books (Src as Object, Args As CommandEventArgs)
  
  Dim SQLString As String 
  SQLString = "SELECT BookID, BookTitle, BookPrice, BookQty FROM Books " & _
              "WHERE BookType = '" & Args.CommandName & "' " & _
              "ORDER BY BookID"
  BookSource.SelectCommand = SQLString

End Sub

</SCRIPT>

<form Runat="Server">

<h3>Display Book Types</h3>

<asp:PlaceHolder id="ButtonArea" Runat="Server"/>

<asp:AccessDataSource id="BookSource" Runat="Server" 
  DataFile="../Databases/BooksDB.mdb"
  SelectCommand="SELECT BookID, BookTitle, BookPrice, BookQty FROM Books
                 WHERE BookType = 'DataBase' ORDER BY BookID"/>

<br/><br/>	
<asp:GridView id="BookGrid" DataSourceID="BookSource" Runat="Server"/>

</form>
Listing 6-62. Scripting command buttons for selection of GridView display.

When the page loads, the database is opened and the set of unique BookType values is retrieved. A button is created for each of these BookType records.

For each record, a new Button object is created. The Text property of the button is set to the BookType value from the record, and the button is styled with width and font-size properties. Since the button is configured as a command button, it is given a CommandName property equal to the BookType from the record. These BookType values get passed to the Get_Books subprogram to retrieve these books for display in the GridView. A Command event is scripted for the button, pointing to this subprogram. Finally, the button is added to the PlaceHolder reserved for its display.

Each time through the processing loop another button is created and added to the PlaceHolder following the previous button. In this example, a total of six buttons are created because there are six BookType values in the database. Importantly, though, the script may produce more or fewer buttons depending on how many book types are in the database.

Immediately following each button is a pair of blank spaces to separate them horizontally. These spaces are added to the PlaceHolder by creating a Literal control with the Text value "&nbsp;&nbsp;" (a pair of non-break space characters). This control is written to the PlaceHolder after a button is written.

Also, a line break character is written after every three buttons to create three columns of buttons. This character is added to the PlaceHolder with a Literal control with the Text value "<br/>". A counter is maintained to determine when this Literal control is added. The test, Counter Mod 3 = 0, adds the literal line break character whenever the Counter divided by 3 does not produce a remainder (every third button).

Finding Scripted Controls

As you have seen, hard-coded controls can be referenced directly through arguments they pass to subprograms. Given the subprogram signature (Src As Object, Args As EventArgs), for example, a reference to Src or Src.id points directly to the button control that calls the subprogram, and these arguments can be used to get or set properties of the control. Scripted controls, however, do not have this direct visibility to subprograms they call. Scripted controls do not even exist when the page is opened, so there is no preexisting connnection between a script and a control. The script must discover the presence of a dynamically created control after a page is loaded.

The following set of buttons illustrate this need to "find" a scripted control in order to work with its properties. This is a rewrite of the previous application to display book types from the BooksDB.mdb database. In this case, a clicked button has its background and text colors changed to visually indicate that it has been clicked.

Display Book Types

      
      


BookIDBookTitleBookPriceBookQty
DB111Oracle Database$69.9910
DB222Databases in Depth$29.956
DB333Database Processing$136.6512
DB444Access Database Design$34.9525
DB555SQL Server 2005$29.990

Figure 6-77. Setting properties of dynamically created Button controls.

The above buttons are created dynamically within a ButtonArea PlaceHolder as coded below. The script to create the buttons is similar to the previous script to display book types from the BooksDB.mdb database. One of the differences is that buttons are assigned id values which are needed subsequently to locate these controls to set their color properties. Here, button id values are given by the BookTypes. The second difference is that buttons have their EnableViewState properties set to "False." The reason is that on page post-back buttons should revert to their original styles so that they do not remain highlighted and result in multiple highlighted buttons as additional buttons are clicked.

<%@ Import Namespace="System.Data.OleDb" %>
<%@ Import Namespace="System.Drawing" %>

<SCRIPT Runat="Server">

Sub Page_Load
  
  Dim DBConnection As OleDbConnection
  Dim DBCommand As OleDbCommand
  Dim DBReader As OleDbDataReader
  Dim SQLString As String
  Dim Counter As Integer = 0
  
  DBConnection = New OleDbConnection( _
    "Provider=Microsoft.Jet.OLEDB.4.0;" & _
    "Data Source=" & Server.MapPath("../Databases/BooksDB.mdb"))
  DBConnection.Open()

  SQLString = "SELECT DISTINCT BookType FROM Books ORDER BY BookType"
  DBCommand = New OleDbCommand(SQLString, DBConnection)
  DBReader = DBCommand.ExecuteReader()
  
  While DBReader.Read()
    Dim MyButton as New Button
    MyButton.id = DBReader("BookType")
    MyButton.Text = DBReader("BookType")
    MyButton.CommandName = DBReader("BookType")
    MyButton.Width = Unit.Parse("100px")
    MyButton.Font.Size = FontUnit.Parse("8pt")
    MyButton.BackColor = Color.FromName("#FFFFFF")
    MyButton.EnableViewState = "False"
    AddHandler MyButton.Command, AddressOf Get_Books
    ButtonArea.Controls.Add(MyButton)
    
    Dim MyBlank As New Literal
    MyBlank.Text = "&nbsp;&nbsp;"
    ButtonArea.Controls.Add(MyBlank)
		
    Counter += 1
    If Counter Mod 3 = 0 Then
      Dim MyBreak As New Literal
      MyBreak.Text = "<br/>"
      ButtonArea.Controls.Add(MyBreak)
    End If
    
  End While
  
  DBReader.Close()
  DBConnection.Close()
  
  If Not Page.IsPostBack Then
    Dim FoundButton As Button = ButtonArea.FindControl("Database")
    FoundButton.BackColor = Color.FromName("#990000")
    FoundButton.ForeColor = Color.FromName("#FFFFFF")
  End If

End Sub

Sub Get_Books (Src as Object, Args As CommandEventArgs)
  
  Dim SQLString As String
  SQLString = "SELECT BookID, BookTitle, BookPrice, BookQty FROM Books " & _
              "WHERE BookType = '" & Args.CommandName & "' " & _
              "ORDER BY BookID"
  BookSource.SelectCommand = SQLString
  
  Dim FoundButton As Button = ButtonArea.FindControl(Src.id)
  FoundButton.BackColor = Color.FromName("#990000")
  FoundButton.ForeColor = Color.FromName("#FFFFFF")
  
End Sub

</SCRIPT>

<form Runat="Server">

<h3>Display Book Types</h3>

<asp:PlaceHolder id="ButtonArea" Runat="Server"/>

<asp:AccessDataSource id="BookSource" Runat="Server" 
  DataFile="../Databases/BooksDB.mdb"
  SelectCommand="SELECT BookID, BookTitle, BookPrice, BookQty FROM Books
                 WHERE BookType = 'DataBase' ORDER BY BookID"/>

<br/><br/>	
<asp:GridView id="BookGrid" DataSourceID="BookSource" Runat="Server"/>

</form>
Listing 6-63. Code to produce and style dynamic buttons.

When the Get_Products subprogram is called by these scripted buttons, the button that is clicked needs to be "found" among the several buttons appearing in the ButtonArea PlaceHolder so that its background and text colors can be set. To locate script-generated controls on a page, the FindControl() method of the PlaceHolder's Controls collection is used. This method uses the id of the control to locate it, and the control is assigned to a reference of the appropriate control type. The general format for locating a control and assigning it to a proper control type is shown below.

Dim FoundControl As Type = PlaceHolderId.FindControl("id")
Figure 6-78. General format for locating a script-generated control.

Code for the previous Get_Books subprogram is shown below. In addition to retrieving book types, this subprogram includes code to locate and assign color properties to the button that is clicked. This button's id is given by the Src.id argument passed to the subprogram by the button click.

Sub Get_Books (Src as Object, Args As CommandEventArgs)
  
  Dim SQLString As String
  SQLString = "SELECT BookID, BookTitle, BookPrice, BookQty FROM Books " & _
              "WHERE BookType = '" & Args.CommandName & "' " & _
              "ORDER BY BookID"
  BookSource.SelectCommand = SQLString
  
  Dim FoundButton As Button = ButtonArea.FindControl(Src.id)
  FoundButton.BackColor = Color.FromName("#990000")
  FoundButton.ForeColor = Color.FromName("#FFFFFF")
  
End Sub
Listing 6-64. Code to find and style dynamic buttons.

If the first button in the set is clicked, for example, then Src.id is "Database", recalling the manner in which id values are assigned to these buttons (using the BookType field) in the Page_Load subprogram. This particular control is assigned to a reference variable, FoundButton, that is declared as a Button type of control. Then its color properties are set. Note that this same code to find and style a button is included in the Page_Load subprogram. It appears here to highlight the first button in the list when the page is first loaded to match the initial display in the GridView.

Iterating the Controls Collection

If the above script-generated buttons had not had their EnableViewState properties set to "False" they would remain highlighted on page post-back, defeating the purpose of highlighting the single button that is clicked. Eventually, all buttons would be highlighted.

In the absence of setting the button's EnableViewState property, an approach to "un-highlighting" buttons on post-back is to iterate all button in the PlaceHolder's Controls collection, setting all buttons back to normal styling prior to highlighting the clicked button. This approach illustrates a general technique for accessing all controls in a Controls collection as shown in the following alternate code for the Get_Books subprogram.

Sub Get_Books (Src as Object, Args As CommandEventArgs)
  
  Dim SQLString As String
  SQLString = "SELECT BookID, BookTitle, BookPrice, BookQty FROM Books " & _
              "WHERE BookType = '" & Args.CommandName & "' " & _
              "ORDER BY BookID"
  BookSource.SelectCommand = SQLString
	
  Dim Item As Button
  For Each Item in ButtonArea.Controls
    Item.BackColor = Color.FromName("#FFFFFF")
    Item.ForeColor = Color.Fromname("#000000")
  Next
	
  Dim FoundButton As Button = ButtonArea.FindControl(Src.id)
  FoundButton.BackColor = Color.FromName("#990000")
  FoundButton.ForeColor = Color.FromName("#FFFFFF")

End Sub
Listing 6-65. Iterating a controls collection.

An iteration variable, Item, is declared as a Button type. All buttons in the PlaceHolder's Controls collection are iterated and their background and text colors set to normal. Thereafter, the clicked button is highlighted.

Disabling the View State of script-generated controls is the cleanest way of ensuring they revert to their normal stylings. However, various other applications might require you to iterate a Controls collection to work with its component controls. The iteration technique shown here is the proper way to work through these controls.


Being able to create and find scripted controls is one of the most important ASP.NET skills you can possess. This is especially the case when generating Web pages from external data sources where you cannot accurately predict the variety of data values upon which control creation is based. You cannot hard-code controls if you do not know how many are needed to represent those unknown values. You are forced to create them dynamically depending on the contents of the external data sources. So, learn well how to create and find controls.