Export (0) Print
Expand All

Top Questions about the DataGrid Web Server Control

Visual Studio .NET 2003
 

Mike Pope and Nikhil Kothari
Visual Studio Team
Microsoft Corporation

January 2002

Summary: Answers frequently asked questions about using the DataGrid Web server control. (17 printed pages)

Applies to

  • Microsoft® Visual Studio® .NET
  • ASP.NET
  • Web Forms
  • DataGrid Web server control

Contents

Windows Forms versus Web Forms DataGrid Controls
Controlling Column Width, Height, and Alignment
Customizing Column Layout in Display and Edit Mode
Formatting Dates, Currency, and Other Data
Showing and Hiding Columns Dynamically
Adding Columns Dynamically
Adding New Records to a Data Source Using the DataGrid Control
Displaying a Drop-Down List in Edit Mode
Selecting Multiple Items Using a Check Box (Hotmail Model)
Editing Multiple Rows At Once
Selecting Rows by Clicking Anywhere

Introduction

The DataGrid Web server control is a powerful tool for displaying information from a data source. It is easy to use; you can display editable data in a professional-looking grid by setting only a few properties. At the same time, the grid has a sophisticated object model that provides you with great flexibility in how you display the data.

This paper addresses some of the questions about customizing grid display that are commonly asked in newsgroups, on Web sites, and in other developer forums. The techniques described here are sometimes quite simple and at other times somewhat involved. In each case, however, they address a question of how to go beyond the basic functionality of the DataGrid control.

This paper assumes that you are already familiar with the control — how to add it to a form and configure it to display data. You should also understand how to put a row in the grid into edit mode and other basic tasks. (For details, see DataGrid Web Server Control.) Finally, you will find it helpful to know how to work with templates — adding template columns to the grid and layout out controls inside a template.

Windows Forms versus Web Forms DataGrid Control

The Web Forms DataGrid control is not the same as the Windows Forms equivalent. It is a common (and not unreasonable) assumption that they are the same control, or at least have identical functionality. However, the entire programming paradigm for Web Forms is quite different from that for Windows Forms. For example, Web Forms pages perform a round trip to the server for any processing; they must manage state; they feature a very different data-binding model; and so on.

Because of these differences, there are also significant differences in their respective controls, including the DataGrid control. As a general rule, the Web Forms DataGrid control includes less built-in functionality. A few examples of differences in the Web Forms DataGrid control are:

  • It does not inherently support master-detail data structures.
  • As with other Web server controls, it does not support two-way data binding. If you want to update data, you must write code to do this yourself.
  • You can only edit one row at a time.
  • It does not inherently support sorting, although it raises events you can handle in order to sort the grid contents.

On the other hand:

  • You can bind the Web Forms DataGrid to any object that supports the IEnumerable interface.
  • The Web Forms DataGrid control supports paging.
  • It is easy to customize the appearance and layout of the Web Forms DataGrid control as compared to the Windows Forms one. (Details are provided later in this paper.)

Controlling Column Width, Height, and Alignment

By default, the DataGrid control sizes rows and columns to fit the overall height and width that you have assigned to the grid. Within the overall grid width, it sizes columns according to the width of the column heading text. All data is displayed left-justified by default.

To control column characteristics, you should turn off auto column generation by setting the AutoGenerateColumns property to false. In fact, you should set this property to true only for short-term uses, such as quick proof-of-concept pages or demonstrations. For production applications, you should add columns explicitly. The individual columns can be bound columns or template columns.

To set the column width, you create a style element for that column and then set the element's Width property to standard units (say, pixels). The following example shows you what the HTML syntax looks like for an ItemStyle element with its Width property set.

<asp:BoundColumn DataField="title" SortExpression="title" 
      HeaderText="Title">
   <ItemStyle Width="100px"></ItemStyle>
</asp:BoundColumn>

Alternatively, you can do the same thing by setting the ItemStyle property directly in the element, as in the following example:

<asp:BoundColumn ItemStyle-Width="100px" DataField="title" 
   SortExpression="title" HeaderText="Title">
</asp:BoundColumn>

You can set alignment using the style element, setting it to "Right," "Left," and other values defined in the HorizontalAlign enumeration. (In Visual Studio, alignment is available for individual columns in the Format tab of the grid's Property builder.) The following is an example:

<asp:BoundColumn DataField="title" SortExpression="title" 
      HeaderText="Title">
   <ItemStyle Width="100px" HorizontalAlign="Right"></ItemStyle>
</asp:BoundColumn>

You can also set a column's height using the style element (or the ItemStyle-Height property). You will probably find this less flexible than setting the width, since setting the height for one column sets it for all of them.

You can set the width in code at run time as well. One place to do so is in an ItemCreated event handler. The following example sets the width of the first two columns to 100 and 50 pixels, respectively:

' Visual Basic
Private Sub DataGrid1_ItemCreated(ByVal sender As Object, _
    ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) _
    Handles DataGrid1.ItemCreated
        e.Item.Cells(0).Width = New Unit(100)
        e.Item.Cells(1).Width = New Unit(50)
    End Sub

// C#
private void DataGrid1_ItemCreated(object sender, 
System.Web.UI.WebControls.DataGridItemEventArgs e)
{
   e.Item.Cells[0].Width = new Unit(100);
   e.Item.Cells[1].Width = new Unit(50);
}

Of course, there is little sense in setting a fixed width in code that you could set at design time. You would normally do this only if you wanted to set the width based on a run-time value. You can set the width of a cell or control in units (typically pixels), but it is not straightforward to translate the length of data — which is simply a character count — into pixels. But the data is available for you to examine when you are creating the item.

Customizing Column Layout in Display and Edit Mode

By default, the grid displays data in pre-sized columns. When you put a row into edit mode, the control displays text boxes for all editable data, regardless of what data type the data is.

If you want to customize the content of a column, make the column a template column. Template columns work like item templates in the DataList or Repeater control, except that you are defining the layout of a column rather than a row.

When you define a template column, you can specify the following template types:

  • The ItemTemplate allows you to customize the normal display of the data.
  • The EditItemTemplate allows you to specify what shows up in the column when a row is put into edit mode. This is how you can specify a control other than the default text box for editing.
  • A HeaderTemplate and FooterTemplate allow you to customize the header and footer, respectively. (The footer is only displayed if the grid's ShowFooter property is true.)

The following example shows the HTML syntax for a template column that displays Boolean data. Both the ItemTemplate and EditItemTemplate use a check box to display the value. In the ItemTemplate, the check box is disabled so that users do not think they can check it. In the EditItemTemplate, the check box is enabled.

<Columns>
<asp:TemplateColumn HeaderText="Discontinued">
<ItemTemplate>
   <asp:Checkbox runat="server" enabled= false name ="Checkbox2" 
     ID="Checkbox2" 
     Checked = '<%# DataBinder.Eval(Container, 
"DataItem.Discontinued") %>' >
   </asp:Checkbox>
</ItemTemplate>
<EditItemTemplate>
   <asp:Checkbox 
      runat="server" name ="Checkbox2" ID="Checkbox2" 
      Checked = '<%# DataBinder.Eval(Container, 
"DataItem.Discontinued") %>' >
   </asp:Checkbox>
</EditItemTemplate>
</asp:TemplateColumn>
</Columns>
Note   If you use a CheckBox control in the EditItemTemplate, be aware that at run time, the grid cell actually contains several LiteralControl controls (for spacing) in addition to the check box itself. Whenever you know the ID of the control whose value you want, use the FindControl method to create a reference to it, rather than using specific indexes into the Cells and Controls collections:
' Visual Basic
Dim cb As CheckBox
cb = CType(e.Item.FindControl("CheckBox2"), CheckBox)

// C#
CheckBox cb;
cb = (CheckBox) e.Item.FindControl("CheckBox2");

In Visual Studio, you can use the grid's Property builder to create the template column and use the template editor to specify the layout. In the Columns tab of the Properties window page for the grid, select the column and at the bottom, click Convert this column into a Template Column. Close the Properties window, right-click the grid, and choose Edit Template. You can then drag controls from the Toolbox into the template and add static text.

Formatting Dates, Currency, and Other Data

Information in a DataGrid control is ultimately displayed in an HTML table in the Web Forms page. To control how data is displayed, therefore, you can specify .NET string formatting for column values. You cannot specify formatting for columns generated when the grid's AutoGenerateColumns property is set to true, only for bound or template columns.

To format, set the column's DataFormatString property to a string-formatting expression suitable for the data type of the data you are formatting. A slightly confusing aspect of format strings is that the same specifier — for example, "D" — can be applied to different data types (integers, dates) with different results.

Note   In Visual Studio, you can specify a formatting expression in the Columns tab of the control's Property builder.

Some example formatting strings are listed in the following table. For more information, see the topics Formatting Types and BoundColumn.DataFormatString Property in the Visual Studio documentation.

Format expression Applied to this data type Description
Price: {0:C}
Note   The {0} is a zero, not the letter O.
numeric/decimal Displays the literal "Price:" followed by numbers in currency format. The currency format depends on the culture setting specified via the culture attribute on the Page directive or in the Web.config file.
{0:D4} integer (Cannot be used with decimal numbers.) Integers are displayed in a zero-padded field four characters wide.
{0:N2}% numeric Displays the number with 2-decimal place precision followed by the literal "%".
{0:000.0} numeric/decimal Numbers rounded to one decimal place. Numbers less than three digits are zero padded.
{0:D} date/datetime Long date format ("Thursday, August 06, 1996"). Date format depends on the culture settting of the page or the Web.config file.
{0:d} date/datetime Short date format ("12/31/99").
{0:yy-MM-dd} date/datetime Date in numeric year-month-day format (96-08-06).

Showing and Hiding Columns Dynamically

One way to have columns appear dynamically is to create them at design time, and then to hide or show them as needed. You can do this by setting a column's Visible property. The following example shows how to toggle the visibility of the second column (index 1) of the grid:

' Visual Basic
DataGrid1.Columns(1).Visible = Not (DataGrid1.Columns(1).Visible)

// C#
DataGrid1.Columns[1].Visible = !(DataGrid1.Columns[1].Visible);

Adding Columns Dynamically

You can hide and show columns if you know in advance what columns you need. Sometimes, however, you do not know that until run time. In that case, you can create columns dynamically and add them to the grid.

To do so, you create an instance of one of the column classes supported by the grid — BoundColumn, EditCommandColumn, ButtonColumn, or HyperlinkColumn. (You can add template columns to the grid, but it is slightly more complex. For details, see Creating Web Server Control Templates Programmatically.) Set the column's properties, and then add it to the grid's Columns collection.

The following example shows how to add two bound columns to a grid.

' Visual Basic
Private Sub Button1_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles Button1.Click
   'Set data-binding properties of the grid
   DataGrid1.AutoGenerateColumns = False
   DataGrid1.DataSource = Me.dsBooks1
   DataGrid1.DataMember = "Books"
   DataGrid1.DataKeyField = "bookid"

   ' Add two columns
   Dim dgc_id As New BoundColumn()
   dgc_id.DataField = "bookid"
   dgc_id.HeaderText = "ID"
   dgc_id.ItemStyle.Width = New Unit(80)
   DataGrid1.Columns.Add(dgc_id)

   Dim dgc_title As New BoundColumn()
   dgc_title.DataField = "title"
   dgc_title.HeaderText = "Title"
   DataGrid1.Columns.Add(dgc_title)

   Me.SqlDataAdapter1.Fill(Me.dsBooks1)
   DataGrid1.DataBind()
End Sub

// C#
private void Button1_Click(object sender, System.EventArgs e)
{
   DataGrid1.AutoGenerateColumns = false;
   DataGrid1.DataSource = this.dsBooks1;
   DataGrid1.DataMember = "Books";
   DataGrid1.DataKeyField = "bookid";

   // Add two columns
   BoundColumn dgc_id = new BoundColumn();
   dgc_id.DataField = "bookid";
   dgc_id.HeaderText = "ID";
   dgc_id.ItemStyle.Width = new Unit(80);
   DataGrid1.Columns.Add(dgc_id);

   BoundColumn dgc_title= new BoundColumn();
   dgc_title.DataField = "title";
   dgc_title.HeaderText = "Title";
   DataGrid1.Columns.Add(dgc_title);

   this.sqlDataAdapter1.Fill(this.dsBooks1);
   DataGrid1.DataBind();
}

Any time that you add controls to a page dynamically, you have the problem of persistence. Dynamically-added controls (or in this case, columns) are not automatically added to the page's view state, so you are obliged to add logic to the page to make sure the columns are available with each round trip.

An excellent way to do this is to override the page's LoadViewState method, which gives you an early opportunity to reestablish columns in the DataGrid control. Because the LoadViewState method is called before the Page_Load event is raised, re-adding columns in the LoadViewState method assures that they are available for normal manipulation by the time any event code runs.

The following example shows how you would expand the previous example to restore the columns each time the page runs again. As before, the Button1_Click handler adds two columns to the grid. (In this example, the event handler calls a separate routine called AddColumns to do so.) In addition, the page contains a simple Boolean property called DynamicColumnsAdded indicating whether the grid has had columns added; the property persists its value in view state. The LoadViewState method first calls the base class's LoadViewState method, which extracts view state information and configures controls with it. If columns were previously added to the grid (as per the DynamicColumnsAdded property), the method then re-adds them.

' Visual Basic
Private Property DynamicColumnAdded() As Boolean
   Get
      If ViewState("ColumnAdded") Is Nothing Then
         Return False
      Else
         Return True
      End If
   End Get
   Set(ByVal Value As Boolean)
      ViewState("ColumnAdded") = Value
   End Set
End Property

Protected Overrides Sub LoadViewState(ByVal savedState As Object)
   MyBase.LoadViewState(savedState)
   If Me.DynamicColumnAdded Then
      Me.AddColums()
   End If
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles Button1.Click
   ' Check property to be sure columns are not added more than once
   If Me.DynamicColumnAdded Then
      Return
   Else
      Me.AddColums()
   End If
End Sub

Protected Sub AddColums()
   ' Add two columns
   Dim dgc_id As New BoundColumn()
   dgc_id.DataField = "instock"
   dgc_id.HeaderText = "In Stock?"
   dgc_id.ItemStyle.Width = New Unit(80)
   DataGrid1.Columns.Add(dgc_id)

   Dim dgc_title As New BoundColumn()
   dgc_title.DataField = "title"
   dgc_title.HeaderText = "Title"
   DataGrid1.Columns.Add(dgc_title)
   Me.DataGrid1.DataBind()
   Me.DynamicColumnAdded = True
End Sub

// C#
private bool DynamicColumnAdded{
   get
   {
      object b = ViewState["DynamicColumnAdded"];
      return (b == null) ? false : true;
   }
   set
   {
      ViewState["DynamicColumnAdded"] = value;
   }
}

protected override void LoadViewState(object savedState)
{
   base.LoadViewState(savedState);
   if (DynamicColumnAdded) 
   {
      this.AddColumns();
   }
}

private void Button1_Click(object sender, System.EventArgs e)
{
   if(this.DynamicColumnAdded != true)
   {
      this.AddColumns();
   }
}

private void AddColumns()
{
   BoundColumn dgc_id = new BoundColumn();
   dgc_id.DataField = "bookid";
   dgc_id.HeaderText = "ID";
   dgc_id.ItemStyle.Width = new Unit(80);
   DataGrid1.Columns.Add(dgc_id);

   BoundColumn dgc_title= new BoundColumn();
   dgc_title.DataField = "title";
   dgc_title.HeaderText = "Title";
   DataGrid1.Columns.Add(dgc_title);

   this.sqlDataAdapter1.Fill(this.dsBooks1);
   DataGrid1.DataBind();
   this.DynamicColumnAdded = true;
}

Adding New Records to a Data Source Using the DataGrid Control

The DataGrid control allows users to view and edit records, but does not inherently include the facility to add new ones. However, you can add this functionality in various ways, all of which involve the following:

  • Adding a new, blank record to the data source of the grid (in the dataset or database). If necessary, you will need to assign an ID for the record and put placeholder values into it for any columns that cannot be null.
  • Rebinding the DataGrid control to the source.
  • Putting the grid into edit mode for the new record. You need to be able to determine where in the grid the new record appears.
  • Updating the record normally when the user clicks Update, thereby writing the new record to the source with user-provided values.

The following example shows the process for adding the new record, binding the grid, and putting it into edit mode. In this example, the data source is a dataset (DsBooks1 or dsBooks1) containing a table called "Books."

' Visual Basic
Private Sub btnAddRow_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles btnAddRow.Click
   Dim dr As DataRow = Me.DsBooks1.Books.NewRow
   dr("title") = "(New)"
   dr("instock") = True
   Me.DsBooks1.Books.Rows.InsertAt(dr, 0)
   Session("DsBooks") = DsBooks1
   DataGrid1.EditItemIndex = 0
   DataGrid1.DataBind()
End Sub

// C#
private void btnAddRow_Click(object sender, System.EventArgs e)
{
   DataRow dr = this.dsBooks1.Books.NewRow();
   dr["title"] = "(New)";
   dr["instock"] = true;
   this.dsBooks1.Books.Rows.InsertAt(dr, 0);
   Session["DsBooks"] = dsBooks1;
   DataGrid1.EditItemIndex = 0;
    DataGrid1.DataBind();
}

Some things to notice:

  • This code runs when a user clicks an Add button somewhere in the page.
  • The new row is created using the NewRow method. It is then inserted into the dataset table using the InsertAt method, which allows you to place it at a specific, predefined location — in this case, as the first record in the table (that is, the first record in the Rows collection). Alternatively, you could add it to the end of the table, using the row count as the value. The important thing is that you know exactly where the row is in the table.
  • Because you know that the record is in the first position of the table, you can set the grid's EditItemIndex value to zero to put the new row into edit mode. (If you created the row elsewhere in the table, you would set EditItemIndex to that location instead.)
  • Because you have a new record in the dataset (but not yet in the database), you have to keep a copy of the dataset between round trips — you do not want to refill it from the database and lose the new record. Here, the code stores it in Session state. You need to reload the dataset from Session state when the page loads. The following example shows what your Page_Load handler might look like:
    ' Visual Basic
    Private Sub Page_Load(ByVal sender As System.Object, _
           ByVal e As System.EventArgs) Handles MyBase.Load
       If Me.IsPostBack Then
          DsBooks1 = CType(Session("DsBooks"), dsBooks)
       Else
          Me.SqlDataAdapter1.Fill(Me.DsBooks1)
          Session("DsBooks") = DsBooks1
          DataGrid1.DataBind()
       End If
    End Sub
    
    // C#
    private void Page_Load(object sender, System.EventArgs e)
    {
       if(this.IsPostBack)
       {
          dsBooks1 = (dsBooks) Session["DsBooks"];
       }
       else
       {
          this.sqlDataAdapter1.Fill(this.dsBooks1);
          Session["DsBooks"] = dsBooks1;
          this.DataGrid1.DataBind();
       }
    }
    

    For information about maintaining state, see Web Forms State Management in the Visual Studio documentation.

You can update the record normally. For an example, see Walkthrough: Using a DataGrid Web Control to Read and Write Data in the Visual Studio documentation. After updating the dataset, update the database, then refresh the dataset. Be sure to save the refreshed dataset to Session state again. Here is an example of an update handler:

' Visual Basic
Private Sub DataGrid1_UpdateCommand(ByVal source As Object, _
       ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
       Handles DataGrid1.UpdateCommand
   Dim dr As Dataset.BooksRow
   'Get a reference to row zero (where the row was inserted)
   dr = Me.DsBooks1.Books(0)
   Dim tb As TextBox = CType(e.Item.Cells(2).Controls(0), TextBox)
   dr.title = tb.Text
   Dim cb As CheckBox = CType(e.Item.Cells(3).Controls(1), CheckBox)
   dr.instock = cb.Checked
   Me.SqlDataAdapter1.Update(Me.DsBooks1)
   DataGrid1.EditItemIndex = -1
   'Refresh the dataset from the database
   DsBooks1.Clear()
   Me.SqlDataAdapter1.Fill(Me.DsBooks1)
   'Save the refreshed dataset in Session state agin
   Session("DsBooks") = DsBooks1
   DataGrid1.DataBind()
End Sub

// C#
private void DataGrid1_UpdateCommand(object source, 
      System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
   dsBooks.BooksRow dr;
   //Get a reference to row zero (where the row was inserted)
   dr = this.dsBooks1.Books[0];
   TextBox tb1 = (TextBox) e.Item.Cells[2].Controls[0];
   dr.title = tb1.Text;
   CheckBox cb = (CheckBox) e.Item.Cells[3].Controls[1];
   dr.instock = cb.Checked;
   this.sqlDataAdapter1.Update(this.dsBooks1);
   DataGrid1.EditItemIndex = -1;
   //Refresh the dataset from the database
   dsBooks1.Clear();
   this.sqlDataAdapter1.Fill(this.dsBooks1);
   //Save the refreshed dataset in Session state agin
   Session["DsBooks"] = dsBooks1;
   DataGrid1.DataBind();
}

Displaying a Drop-Down List in Edit Mode

A common request is to present users with a drop-down list when a row is in edit mode. For example, the grid might show a list of books, including each book's genre. When users edit a book record, they might want to assign a different genre; ideally, they can select from a drop-down list that shows possible genre values such as "fiction," "biography," or "reference."

Displaying a drop-down list requires a template column in the grid. Typically, the ItemTemplate contains a control such as a data-bound Label control to show the current value of a field in the record. You then add a drop-down list to the EditItemTemplate. In Visual Studio, you can add a template column in the Property builder for the grid, and then use standard template editing to remove the default TextBox control from the EditItemTemplate and drag a DropDownList control into it instead. Alternatively, you can add the template column in HTML view.

After you have created the template column with the drop-down list in it, there are two tasks. The first is to populate the list. The second is to preselect the appropriate item in the list — for example, if a book's genre is set to "fiction," when the drop-down list displays, you often want "fiction" to be preselected. (Preselecting an item might not be an issue in all scenarios.)

There are many ways to populate the drop-down list. The following examples show you three possibilities: using static items; using records from a dataset; or by using a data reader to read information directly from a database.

Static Items

To display static items in the drop-down list, you do not data bind the control. Instead, you simply define items in the control's Items collection. In Visual Studio, you can invoke the Items collection editor from the Items property in the Properties window. Alternatively, you can add items in HTML view.

The following shows a complete column definition for a template column that displays the genre in display mode, and a static list of genre types in edit mode. The ItemTemplate contains a Label control whose Text property is bound to the "genre" field of the current record. The declarations for the static items in the EditItemTemplate are highlighted.

<asp:TemplateColumn HeaderText="genre">
   <ItemTemplate>
      <asp:Label id=Label4 runat="server" 
         Text='<%# DataBinder.Eval(Container, "DataItem.genre") %>'>
      </asp:Label>
   </ItemTemplate>
   <EditItemTemplate>
      <asp:DropDownList id="DropDownList2" runat="server" Width="172px">
         <asp:ListItem Value="fiction">fiction</asp:ListItem>
         <asp:ListItem Value="biography">biography</asp:ListItem>
         <asp:ListItem Value="reference">reference</asp:ListItem>
      </asp:DropDownList>
   </EditItemTemplate>
</asp:TemplateColumn>

Dataset

If the data you want to display in the drop-down list is in a dataset, you can use ordinary data binding. The following shows the declarative syntax. The DropDownList control is bound to the Genre table in a dataset called DsBooks1. The data-binding settings are highlighted.

<asp:TemplateColumn HeaderText="genre (dataset)">
   <ItemTemplate>
      <asp:Label id=Label3 runat="server"
          Text='<%# DataBinder.Eval(Container, "DataItem.genre") %>'>
      </asp:Label>
   </ItemTemplate>
   <EditItemTemplate>
      <asp:DropDownList id=DropDownList4 runat="server" 
         DataSource="<%# DsBooks1 %>" DataMember="Genre" 
         DataTextField="genre" DataValueField="genre" Width="160px">
      </asp:DropDownList>
   </EditItemTemplate>
</asp:TemplateColumn>

Data Reader

You can also populate the drop-down list directly from a database. This method is more involved, but it can be more efficient, since you do not actually read the data from the database till the moment you need it.

A relatively easy way to do this is to take advantage of Web Forms data-binding expressions. Although it is most common to call the DataBinder.Eval method in a data-binding expression, you can in fact call any public member available to the page. This example shows you how to create a function that creates, fills, and returns a DataTable object that the drop-down list can bind to.

For this scenario, you will need to be able to execute a data command that gets the records you want. For example, you might define a data command whose CommandText property is Select * from Genres. To simplify the example, it will be assumed that you have a connection object and a data command object already on the page.

Start by creating a public function in the page that creates a data table object and defines the columns you need in it. Then open the connection, execute the data command to return a data reader, and loop through the reader, copying the data to the table. Finally, return the table as the function's return value.

The following example shows how you can do this. In this case, there is only one column in the returned table ("genre"). When you populate a drop-down list, you usually need only one column, or two columns if you want to set the drop-down list's text and values to different columns.

' Visual Basic
Public Function GetGenreTable() As DataTable
   Dim dtGenre As DataTable = New DataTable()
   If Application("GenreTable") Is Nothing Then
      Dim dr As DataRow
      Dim dc As New DataColumn("genre")
      dtGenre.Columns.Add(dc)
      Me.SqlConnection1.Open()
      Dim dreader As SqlClient.SqlDataReader = _
         Me.SqlCommand1.ExecuteReader()
      While dreader.Read()
         dr = dtGenre.NewRow()
         dr(0) = dreader(0)
         dtGenre.Rows.Add(dr)
      End While
      Me.SqlConnection1.Close()
   Else
      dtGenre = CType(Application("GenreTable"), DataTable)
   End If
   Return dtGenre
End Function

//C#
public DataTable GetGenreTable()
{
   DataTable dtGenre = new DataTable();
   if(Application["GenreTable"] == null)
   {
      DataRow dr;
      DataColumn dc = new DataColumn("genre");
               dtGenre.Columns.Add(dc);
      this.sqlConnection1.Open();
      System.Data.SqlClient.SqlDataReader dreader = 
          this.sqlCommand1.ExecuteReader();
      while(dreader.Read())
      {
          dr = dtGenre.NewRow();
         dr[0] = dreader[0];
         dtGenre.Rows.Add(dr);
      }
      this.sqlConnection1.Close();
   }
   else
   {
      dtGenre = (DataTable) Application["GenreTable"];
   }
   return dtGenre;
}

Notice that the function caches the table it creates into Application state. Since the table is acting as a static lookup table, you do not need to re-read it every time a different row is put into edit mode. Moreover, because the same table can be used by multiple users, you can cache it in the global Application state rather than in user-specific Session state.

The following shows the declaration for the template column. You will see that this is very similar to the syntax used for binding to a dataset table; the only real difference is that the DataSource binding calls your function. A slight disadvantage of this technique is that you do not get much design-type assistance from Visual Studio. Because you are defining the table to bind to in code, Visual Studio cannot offer you any choices for the DataMember, DataTextField, and DataValueField property settings. It is up to you to be sure that you set these properties to the names of the members you create in code.

<asp:TemplateColumn HeaderText="genre (database)">
   <ItemTemplate>
      <asp:Label id=Label1 runat="server" 
          Text='<%# DataBinder.Eval(Container, "DataItem.genre") %>'>
      </asp:Label>
   </ItemTemplate>
   <EditItemTemplate>
      <asp:DropDownList id=DropDownList1 runat="server"
         DataSource="<%# GetGenreTable() %>"
         DataMember="Genre" 
         DataTextField="genre" 
         DataValueField="genre" 
         Width="120px">
     </asp:DropDownList>
   </EditItemTemplate>
</asp:TemplateColumn>

Preselecting an Item in the Drop-Down List

You often want to set the selected item in the drop-down list to match a specific value, usually the value displayed in the cell in display mode. You can do this by setting the SelectedIndex property of the drop-down list to the index of the value to display.

The following example shows a reliable way to do this in a handler for the DataGrid item's ItemDataBound event. This is the correct event to use, because it guarantees that the drop-down list has already been populated, no matter what data source the drop-down list is using.

The trick is in knowing what value to set the drop-down list to. Typically, the value is already available to you either in the current item (being displayed) or in the DataItem property of the current item, which returns a DataRowView object containing the current record. Once you have the value, you can use the DropDownList control's FindByText or FindByValue method to locate the correct item in the list; you can then use the item's IndexOf property to return the index.

' Visual Basic
Private Sub DataGrid1_ItemDataBound(ByVal sender As Object, _
      ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) _
      Handles DataGrid1.ItemDataBound
   If e.Item.ItemType = ListItemType.EditItem Then
      Dim drv As DataRowView = CType(e.Item.DataItem, DataRowView)
      Dim currentgenre As String = CType(drv("genre"), String)
      Dim ddl As DropDownList
      ddl = CType(e.Item.FindControl("DropDownList1"), DropDownList)
      ddl.SelectedIndex = ddl.Items.IndexOf(ddl.Items.FindByText(currentgenre))
   End If
End Sub

// C#
private void DataGrid1_ItemDataBound(object sender, 
      System.Web.UI.WebControls.DataGridItemEventArgs e)
{
   if(e.Item.ItemType == ListItemType.EditItem){
      DataRowView drv = (DataRowView) e.Item.DataItem;
     String currentgenre = drv["genre"].ToString();
     DropDownList ddl = 
        (DropDownList) e.Item.FindControl("DropDownList1");
     ddl.SelectedIndex = 
         ddl.Items.IndexOf(ddl.Items.FindByText(currentgenre));
   }
}

Selecting Multiple Items Using a Check Box (Hotmail Model)

In applications such as Microsoft Hotmail®, users can "select" rows by checking a box and then performing an operation on all the selected rows — for example, delete them or copy them.

To add functionality like this, add a template column to the grid and put a check box into the column. When the page runs, users will be able to check the items they want to work with.

To actually perform the user action, you can walk the grid's Items collection, looking into the appropriate column (cell) to see if the check box is checked. The following example shows how you can delete rows in a dataset corresponding to the items that a user has checked. The dataset, called dsBooks1, is assumed to contain a table called Books.

' Visual Basic
Private Sub btnDelete_Click(ByVal sender As System.Object, _
       ByVal e As System.EventArgs) Handles btnDelete.Click
   ' Walk the grid looking for selected rows
   Dim i As Integer = 0
   Dim cb As CheckBox
   Dim dgi As DataGridItem
   Dim bookid As Integer
   Dim dr As dsBooks.BooksRow
   For Each dgi In DataGrid1.Items
      cb = CType(dgi.Cells(0).Controls(1), CheckBox)
      If cb.Checked Then
         ' Determine the key of the selected record ... 
         bookid = CType(DataGrid1.DataKeys(i), Integer)
         ' ... get a pointer to the corresponding dataset record ...
         dr = Me.DsBooks1.Books.FindBybookid(bookid)
         ' ... and delete it.
         dr.Delete()
      End If
      i += 1
   Next
   Me.SqlDataAdapter1.Update(DsBooks1)
   Me.SqlDataAdapter1.Fill(DsBooks1)
   DataGrid1.DataBind()
End Sub

// C#
private void btnDelete_Click(object sender, System.EventArgs e)
{
   int i = 0;
   CheckBox cb;
   int bookid;
   dsBooks.BooksRow dr;
   foreach(DataGridItem dgi in this.DataGrid1.Items)
   {
      cb = (CheckBox) dgi.Cells[0].Controls[1];
      if(cb.Checked)
      {
         // Determine the key of the selected record ... 
         bookid = (int) DataGrid1.DataKeys[i];
         // ... get a pointer to the corresponding dataset record ...
         dr = this.dsBooks1.Books.FindBybookid(bookid);
         // ... and delete it.
         dr.Delete();
      }
      i++;
   }
   this.sqlDataAdapter1.Update(this.dsBooks1);
   this.sqlDataAdapter1.Fill(this.dsBooks1);
   DataGrid1.DataBind();
}

Some points to note:

  • You can determine whether the check box is checked by using the standard approach for getting a control value from a template column — getting an object from the Controls collection of the cell and casting it appropriately. If you are getting a Checkbox control, remember that it is usually the second control (index 1) because a literal control precedes it (even if it is blank).
  • If you are deleting, you must do so by key and not by offset in the dataset. The index of an item in the DataGrid control might not match the index of the same record in the table. Even if it does at first, after the first record is deleted it will not. Here, the code gets the record key out of the grid's DataKey collection. It then uses the FindBy<key> method in the dataset table to locate the record to delete.
  • After the records have been deleted from the dataset (technically, they are only marked for deletion), you delete them from the database by calling the data adapter's Update method. The code then refreshes the dataset from the database and re-binds the grid.

Editing Multiple Rows At Once

The standard way to edit rows in the DataGrid control — by adding an "Edit, Update, Cancel" button to the grid's columns — only allows users to edit one row at a time. If users want to edit multiple rows, they must click the Edit button, make their changes, and then click the Update button for each row.

In some cases, a useful alternative is to configure the grid so that it is in edit mode by default. In this scenario, the grid always displays editable data in text boxes or other controls; users do not explicitly have to put the grid into edit mode. Typically, users make whatever changes they want and then click a button (not a button in the grid) to submit all changes at once. The page might look something like the following:

Figure 1

You can use this style of editing grid with any data model, whether you are working against a dataset or directly against the data source using data commands.

To configure the grid for multiple-row edit, add the columns as you normally would and convert all editable columns to template columns. In the Columns tab of the grid's Property Builder, select the column and at the bottom of the window, choose Convert this column into a Template column. To edit the templates, right-click the grid and choose Edit Template.

Add the edit controls to the ItemTemplate. Note that you are not adding them to the EditItemTemplate, as you normally would, because the rows will not be displayed in edit mode. That is, the ItemTemplate will contain editable controls.

Set up data binding for the grid normally. You will need to bind each editable control individually. A typical data binding expression will look like this:

DataBinder.Eval(Container, "DataItem.title")

Loading the grid is no different than usual. Updating is slightly different, however, because when users click the Update button, you need to go through the entire grid, making updates for all the rows.

The following example shows one possibility. In this case, it is assumed that you are using a data command (dcmdUpdateBooks) that contains a parameterized SQL Update statement. The code walks through the grid, item by item, extracts values from the editable controls, and assigns the values to command parameters. It then executes the data command once for each grid item.

' Visual Basic
Private Sub btnUpdate_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles btnUpdate.Click
   Dim i As Integer
   Dim dgi As DataGridItem
   Dim bookid As Integer
   Dim TextBoxTitle As TextBox
   Dim CheckBoxInStock As CheckBox
   Dim TextBoxPrice As TextBox
   Dim LabelBookId as Label

   For i = 0 To DataGrid1.Items.Count - 1
       dgi = DataGrid1.Items(i)
       LabelBookId = CType(dgi.Cells(0).Controls(1), Label)
       bookid = CType(LabelBookId.Text, Integer)
       TextBoxTitle = CType(dgi.FindControl("TextBoxTitle"), TextBox)
       CheckBoxInStock = _
           CType(dgi.FindControl("CheckBoxInstock"), CheckBox)
       TextBoxPrice = CType(dgi.FindControl("TextBoxPrice"), TextBox)
       Me.dcmdUpdateBooks.Parameters("@bookid").Value = bookid
       Me.dcmdUpdateBooks.Parameters("@Title").Value = TextBoxTitle.Text
       Me.dcmdUpdateBooks.Parameters("@instock").Value = _
          CheckBoxInStock.Checked
       Me.dcmdUpdateBooks.Parameters("@Price").Value = TextBoxPrice.Text
       Me.SqlConnection1.Open()
       Me.dcmdUpdateBooks.ExecuteNonQuery()
       Me.SqlConnection1.Close()
   Next
End Sub

// C#
private void btnUpdate_Click(object sender, System.EventArgs e)
{
   int i;
   DataGridItem dgi;
   int bookid;
   TextBox TextBoxTitle;
   CheckBox CheckBoxInStock;
   TextBox TextBoxPrice;

   for(i = 0; i <= DataGrid1.Items.Count -1 ; i++)
   {
      dgi = DataGrid1.Items[i];
      Label LabelBookId = (Label) dgi.Cells[0].Controls[1];
      bookid = int.Parse(LabelBookId.Text);
      TextBoxTitle = (TextBox) dgi.FindControl("TextBoxTitle");
      CheckBoxInStock = (CheckBox) dgi.FindControl("CheckBoxInStock");
      TextBoxPrice = (TextBox) dgi.FindControl("TextBoxPrice");
      this.dcmdUpdateBooks.Parameters["@bookid"].Value = bookid;
      this.dcmdUpdateBooks.Parameters["@Title"].Value = TextBoxTitle.Text;
      this.dcmdUpdateBooks.Parameters["@instock"].Value = 
CheckBoxInStock.Checked;
      this.dcmdUpdateBooks.Parameters["@Price"].Value = 
float.Parse(TextBoxPrice.Text);
      this.sqlConnection1.Open();
      this.dcmdUpdateBooks.ExecuteNonQuery();
      this.sqlConnection1.Close();
   }
}

Checking for Changed Items

One disadvantage of the update strategy illustrated above is that it can be inefficient to send updates to the dataset or database for each grid row if there have been only a few changes. If you are working with a dataset, you can add logic to check for changes between the controls in the grid and the corresponding columns in dataset rows. If you are not using a dataset — as in the example above — you cannot easily make this comparison, since it would involve a round trip to the database.

A strategy that works for both types of data sources is to establish a way to determine whether rows are "dirty" so you can check that before making an update. The definitive way to determine whether a row has been dirtied is to handle the changed event for the controls in a row. For example, if your grid row contains a TextBox control, you can respond to the control's TextChanged event. Similarly, for check boxes, you can respond to a CheckedChanged event.

In the handler for these events, you maintain a list of the rows to be updated. Generally, the best strategy is to track the primary keys of the affected rows. For example, you can maintain an ArrayList object that contains the primary keys of the rows to update.

Imagine that you want to follow this strategy for the example above. Create an instance of an ArrayList object as a member of the page class:

' Visual Basic
Protected bookidlist As ArrayList = New ArrayList()

// C#
protected ArrayList bookidlist = new ArrayList();

Then create a handler to add the book ID to the ArrayList object whenever a control is changed. The following code shows a handler that can be invoked when a TextBox control raises its TextChanged event or when a CheckBox control raises its CheckedChanged event:

' Visual Basic
Protected Sub RowChanged(ByVal sender As Object, _
      ByVal e As System.EventArgs)
   Dim dgi As DataGridItem = _
      CType(CType(sender, Control).NamingContainer, DataGridItem)
   Dim bookidlabel As Label = CType(dgi.Cells(0).Controls(1), Label)
   Dim bookid As Integer = CType(bookidlabel.Text, Integer)
   If Not (bookidlist.Contains(bookid)) Then
      bookidlist.Add(bookid)
   End If
End Sub

// C#
protected void RowChanged( object sender, System.EventArgs e)
{
   DataGridItem dgi = (DataGridItem)(((Control)sender).NamingContainer);
   Label bookidlabel = (Label) dgi.Cells[0].Controls[1];
   int bookid = int.Parse(bookidlabel.Text);
   if (!bookidlist.Contains(bookid))
   {
      bookidlist.Add(bookid);
   }
}
Note   The method cannot be private, or you will not be able to bind to it later.

It is helpful to understand that change events do not, by default, post the page back to the server. Instead, the event is raised only when the page is posted some other way (usually via a Click event). During page processing, the page and its controls are initialized, and then all change events are raised. Only when the change event's handlers have finished is the Click event raised for the control that caused the post.

On to the RowChanged method illustrated above. The code needs to get the book ID out of the current item. The event does not pass the item to you (as it does for many DataGrid events, for example), so you have to work backwards. From the sender argument of the event, get the NamingContainer property, which will be the grid item. From there, you can drill back down to get the value of the Label control that displays the book ID.

You need to check that the book ID is not already in the array. Each control in the row raises the event individually, so if there has been a change in more than one control, you could potentially end up adding the book ID to the array more than once.

The change events for controls are always raised and handled before click events. Therefore, you can build the array list in the change event and know that it will be available when the event handler runs for the button click that posted the form (in this example, the btnUpdate_Click handler).

Now that you have the array list, you can make a minor modification to the handler that manages the update. In the btnUpdate_Click, when you iterate through the data grid items, add a test to see if the current book ID is in the array list; if so, make the update.

' Visual Basic
Private Sub btnUpdate_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles btnUpdate.Click
   Dim i As Integer
   Dim dgi As DataGridItem
    'Rest of declarations here

   For i = 0 To DataGrid1.Items.Count - 1
       dgi = DataGrid1.Items(i)
       LabelBookId = CType(dgi.Cells(0).Controls(1), Label)
       If bookidlist.Contains(bookid) Then
         TextBoxTitle = CType(dgi.FindControl("TextBoxTitle"), TextBox)
         ' Rest of update code here
       End If
   Next
End Sub

// C#

private void btnUpdate_Click(object sender, System.EventArgs e)
{
   int i;
   DataGridItem dgi;
   int bookid;
   //Rest of declarations here

   for(i = 0; i <= DataGrid1.Items.Count -1 ; i++)
   {
      dgi = DataGrid1.Items[i];
      TableCell tc = dgi.Cells[0];
      string s = dgi.Cells[0].Text;
      Label LabelBookId = (Label) dgi.Cells[0].Controls[1];
      bookid = int.Parse(LabelBookId.Text);
      if (bookidlist.Contains(bookid)) 
      {
         // Update code here
      }
   }
}

One task is left: binding the handlers to the control events. In Visual Studio, you can only do this in HTML view. The controls are not explicitly instantiated in the code-behind file, so they are not supported by the code tools. Switch the .aspx file to HTML view and in the declarative elements for each of the controls, add the following highlighted syntax:

<asp:TemplateColumn HeaderText="title">
   <ItemTemplate>
      <asp:TextBox OnTextChanged="RowChanged"
         id=TextBoxTitle runat="server"
         Text='<%# DataBinder.Eval(Container, "DataItem.title") %>'>
      </asp:TextBox>
   </ItemTemplate>
</asp:TemplateColumn>

<asp:TemplateColumn HeaderText="instock">
   <ItemTemplate>
      <asp:CheckBox id=cbInStock OnCheckedChanged="RowChanged"
        runat="server" 
        Checked='<%# DataBinder.Eval(Container, "DataItem.instock") %>'>
      </asp:CheckBox>
   </ItemTemplate>
</asp:TemplateColumn>

Both the TextBox and CheckBox controls can call the same method from their respective change methods, because the signature for both event handlers is the same. That would be true also if you had a list box or drop-down list control, whose SelectedIndexChanged events likewise pass the same arguments.

Selecting Rows by Clicking Anywhere

The default model for selecting rows in the grid is for you to add a Select button (actually, a LinkButton control) whose CommandName property is set to "Select." When the button is clicked, the DataGrid control receives the Select command and automatically displays the row in selected mode.

Not everyone likes having an explicit Select button, and a common question is how to implement the feature where users can click anywhere in a grid row to select it. The solution is to perform a kind of sleight-of-hand in the grid. You add the Select LinkButton control as normal. Users can still use it, or you can hide it. In either event, you then inject some client script into the page that effectively duplicates the functionality of the Select button for the row as a whole.

The example below shows how. In the grid's ItemDataBound handler, first make sure that you are not in the header, footer, or pager. Then get a reference to the Select button, which in this instance is assumed to be the first control in the first cell. You then call a little-known method called GetPostBackClientHyperlink. This method returns the name of the postback call for the designated control. In other words, if you pass in a reference to a LinkButton control, it returns the name of the client function call that will perform the postback.

Finally, you assign the client-side method to the item itself. When the grid renders, it renders as an HTML table. By assigning the method to the item, it is the equivalent of adding client-side code to each row (<TR> element) in the table. The grid's Item object does not directly support a way to assign client code to it, but you can do that by using its Attributes collection, which passes anything you assign to it through to the browser.

Note   One small disadvantage of this technique is that it adds somewhat to the stream rendered to the browser, and it adds information for each row to view state.
' Visual Basic
Private Sub DataGrid1_ItemDataBound(ByVal sender As Object, _
      ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) _
      Handles DataGrid1.ItemDataBound
   Dim itemType As ListItemType = e.Item.ItemType
   If ((itemType = ListItemType.Pager) Or _
      (itemType = ListItemType.Header) Or _
      (itemType = ListItemType.Footer)) Then
      Return
   Else
      Dim button As LinkButton = _
         CType(e.Item.Cells(0).Controls(0), LinkButton)
      e.Item.Attributes("onclick") = _
         Page.GetPostBackClientHyperlink(button, "")
   End If
End Sub

// C#
private void DataGrid1_ItemDataBound(object sender, 
System.Web.UI.WebControls.DataGridItemEventArgs e)
{
   ListItemType itemType = e.Item.ItemType;
   if ((itemType == ListItemType.Pager) || 
       (itemType == ListItemType.Header) || 
       (itemType == ListItemType.Footer)) 
   {
      return;
   }
   LinkButton button = (LinkButton)e.Item.Cells[0].Controls[0];
   e.Item.Attributes["onclick"] = 
      Page.GetPostBackClientHyperlink(button, "");
}
Show:
© 2014 Microsoft