Programming ASP.NET

JavaScript Editor Javascript validator     Javascripts

Main Page


Previous Section Next Section

10.2 The DataGrid Control

The problem with predesigned user controls is typically that they are either simple and therefore too limited to do what you want, or they are powerful and therefore so complex that they are very difficult to learn. The DataGrid control attempts to overcome both of these constraints. Creating a simple DataGrid control couldn't be much easier, yet there is enough power and complexity to keep you quite busy tweaking and modifying the control to do exactly what you want.

To explore both the simplicity and the power of the DataGrid control, we'll use the process of successive approximation to get something working quickly and then to keep it working while we enhance it.

10.2.1 Version 1: Displaying Data

In the first iteration, you'll create a DataGrid object and display some simple data. To get started, you need a data source, in this case an ArrayList that you'll populate with Bug objects. You will define the Bug class, and each Bug object will represent a single bug report. For now to keep it simple, you'll give the Bug class a few fields to hold representative information about a given code bug. Example 10-1 is the definition of the Bug class in C#; Example 10-2 is the same definition in VB.NET.

Example 10-1. The Bug class in C#
using System;

public class Bug
{
   // private instance variables
   private int bugID;
   private string title;
   private string reporter;
   private string product;
   private string version;
   private string description;
   private DateTime dateCreated;
   private string severity;
      
   // constructor
   public Bug(int id, 
      string title,        // for display
      string reporter,     // who filed bug
      string product, 
      string version, 
      string description,  // bug report
      DateTime dateCreated, 
      string severity)
   {
      bugID = id;                      
      this.title = title;              
      this.reporter = reporter;        
      this.product = product;          
      this.version = version;
      this.description = description;
      this.dateCreated = dateCreated;
      this.severity = severity;
   }

   // public read only properties
   public int BugID { get { return bugID; }}
   public string Title { get { return title; }}
   public string Reporter { get { return reporter; }}
   public string Product { get { return product;  }}
   public string Version { get { return version;  }}
   public string Description { get { return description; }}
   public DateTime DateCreated { get { return dateCreated; }}
   public string Severity { get { return severity; }}
}
Example 10-2. The Bug class in VB.NET
Public Class Bug
   Private _bugID As Int32
   Private _title As String
   Private _reporter As String
   Private _product As String
   Private _version As String
   Private _description As String
   Private _dateCreated As DateTime
   Private _severity As String

   Sub New(ByVal theID As Int32, _
   ByVal theTitle As String, _
   ByVal theReporter As String, _
   ByVal theProduct As String, _
   ByVal theVersion As String, _
   ByVal theDescription As String, _
   ByVal theDateCreated As DateTime, _
   ByVal theSeverity As String)

      _bugID = theID
      _title = theTitle
      _reporter = theReporter
      _product = theProduct
      _version = theVersion
      _description = theDescription
      _dateCreated = theDateCreated
      _severity = theSeverity
   End Sub

   Public ReadOnly Property BugID(  ) As Int32
      Get
         BugID = _bugID
      End Get
   End Property

   Public ReadOnly Property Title(  ) As String
      Get
         Title = _title
      End Get
   End Property

   Public ReadOnly Property Reporter(  ) As String
      Get
         Reporter = _reporter
      End Get
   End Property

   Public ReadOnly Property Product(  ) As String
      Get
         Product = _product
      End Get
   End Property

   Public ReadOnly Property Version(  ) As String
      Get
         Version = _version
      End Get
   End Property

   Public ReadOnly Property Description(  ) As String
      Get
         Description = _description
      End Get
   End Property

   Public ReadOnly Property DateCreated(  ) As String
      Get
         DateCreated = _dateCreated
      End Get
   End Property

   Public ReadOnly Property Severity(  ) As String
      Get
         Severity = _severity
      End Get
   End Property

End Class

The Bug class consists of nothing except a number of private members and read-only properties to retrieve these values. In addition, there is a constructor to initialize the values. The reporter member variable (_reporter) stores the name of the person reporting the bug, the product and version (_product and _version) are strings that represent the specific product that has the bug. The description field holds the full description of the bug, while title is a short summary to be displayed in the data grid.

The .aspx file simply creates a DataGrid within a form. The only attribute is the ID and, of course, runat="server", as you would expect in any ASP web control. The complete .aspx file is shown in Example 10-3.

Example 10-3. The .aspx file
<%@ Page language="c#" 
Codebehind="WebForm1.aspx.cs" 
AutoEventWireup="false" 
Inherits="WebApplication1.WebForm1" %>

<html>
  <head>
      <meta name=vs_targetSchema content="Internet Explorer 5.0">
      <meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">
      <meta name="CODE_LANGUAGE" Content="C#">
  </head>
  <body>
    <form runat="server" ID="Form1">
        <asp:DataGrid id="dataGrid1" runat="server" />
    </form>
  </body>
</html>

All that is left is to bind the data. This is accomplished in the Page_Load method in the code-behind file. If the page is not being posted back, you call a helper method, BindGrid.

BindGrid creates a new ArrayList named bugs and populates it with a couple of instances of the Bug class. It then sets dataGrid1's DataSource property to the bugs ArrayList object and calls BindGrid. The complete C# code-behind file is shown in Example 10-4, with the complete VB.NET code shown in Example 10-5

Example 10-4. The code-behind file in C#
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace DataGridBindAllColumnsBugs
{
   public class WebForm1 : System.Web.UI.Page
   {
      // declare the controls on the web page
      protected System.Web.UI.WebControls.DataGrid 
         dataGrid1;
   
      public WebForm1(  )
      {
         Page.Init += 
            new System.EventHandler(Page_Init);
      }

      private void Page_Load(
         object sender, System.EventArgs e)
      {
         // if this is the first time
         // the page is to be displayed
         // bind the data
         if (!IsPostBack) 
         {
            BindGrid(  );
         }
      }

      private void Page_Init(
         object sender, EventArgs e)
      {
         InitializeComponent(  );
      }
      void BindGrid(  ) 
      {
         // create the data source
         // add a couple bug objects
         ArrayList bugs = new ArrayList(  );
         bugs.Add(
            new Bug(
            101,
            "Bad Property Value",
            "Jesse Liberty", 
            "XBugs",
            "0.01",
            "Property values incorrect",
            DateTime.Now,
            "High"
            )      // end new bug
         );        // end add

         bugs.Add(
            new Bug(
            102,
            "Doesn't load properly",
            "Dan Hurwitz", 
            "XBugs",
            "0.01",
            "The system fails with error x2397",
            DateTime.Now,
            "Medium"
            )      // end new bug
         );        // end add

         // assign the data source
         dataGrid1.DataSource=bugs;

         // bind the grid
         dataGrid1.DataBind(  );

      }
      #region Web Form Designer generated code
      /// <summary>
      /// Required method for Designer support - do not modify
      /// the contents of this method with the code editor.
      /// </summary>
      private void InitializeComponent(  )
      {    
         this.Load += 
            new System.EventHandler(this.Page_Load);

      }
      #endregion
   }

   // the Bug class 
   public class Bug
   {
      // private instance variables
      private int bugID;
      private string title;
      private string reporter;
      private string product;
      private string version;
      private string description;
      private DateTime dateCreated;
      private string severity;
      
      // constructor
      public Bug(int id, 
         string title,        // for display
         string reporter,     // who filed bug
         string product, 
         string version, 
         string description,  // bug report
         DateTime dateCreated, 
         string severity)
      {
         bugID = id;                      
         this.title = title;              
         this.reporter = reporter;        
         this.product = product;          
         this.version = version;
         this.description = description;
         this.dateCreated = dateCreated;
         this.severity = severity;
      }

      // public read only properties
      public int        BugID          
                  { get { return bugID; }}
      public string     Title          
                  { get { return title; }}
      public string     Reporter       
                  { get { return reporter; }}
      public string     Product        
                  { get { return product;  }}
      public string     Version        
                  { get { return version;  }}
      public string     Description    
                  { get { return description; }}
      public DateTime   DateCreated    
                  { get { return dateCreated; }}
      public string     Severity       
                  { get { return severity; }}

   }
}
Example 10-5. The complete code-behind file in VB.NET
Public Class WebForm1
    Inherits System.Web.UI.Page
   Protected WithEvents dataGrid1 As System.Web.UI.WebControls.DataGrid

#Region " Web Form Designer Generated Code "

    'This call is required by the Web Form Designer.
    <System.Diagnostics.DebuggerStepThrough(  )> Private Sub InitializeComponent(  )

   End Sub

   Private Sub Page_Init(ByVal sender As System.Object)
      'CODEGEN: This method call is required by the Web Form Designer
      'Do not modify it using the code editor.
      InitializeComponent(  )
   End Sub

#End Region

   Private Sub Page_Load(ByVal sender As System.Object, _
                         ByVal e As System.EventArgs) Handles MyBase.Load

      If Not IsPostBack Then
         BindGrid(  )
      End If
   End Sub

   Private Sub BindGrid(  )
      Dim bugs As New ArrayList(  )
      bugs.Add(New Bug(101, _
         "BadProperty Value", _
         "Jesse Liberty", _
         "XBugs", _
         "0.01", _
         "Property values incorrect", _
         DateTime.Now, _
         "High") _
      )
      bugs.Add( _
         New Bug( _
         102, _
         "Doesn't load properly", _
         "Dan Hurwitz", _
         "XBugs", _
         "0.01", _
         "The system fails with error x2397", _
         DateTime.Now, _
         "Medium") _
      )

      dataGrid1.DataSource = bugs
      dataGrid1.DataBind(  )

   End Sub

End Class

Public Class Bug
   Private _bugID As Int32
   Private _title As String
   Private _reporter As String
   Private _product As String
   Private _version As String
   Private _description As String
   Private _dateCreated As DateTime
   Private _severity As String

   Sub New(ByVal theID As Int32, _
   ByVal theTitle As String, _
   ByVal theReporter As String, _
   ByVal theProduct As String, _
   ByVal theVersion As String, _
   ByVal theDescription As String, _
   ByVal theDateCreated As DateTime, _
   ByVal theSeverity As String)

      _bugID = theID
      _title = theTitle
      _reporter = theReporter
      _product = theProduct
      _version = theVersion
      _description = theDescription
      _dateCreated = theDateCreated
      _severity = theSeverity
   End Sub

   Public ReadOnly Property BugID(  ) As Int32
      Get
         BugID = _bugID
      End Get
   End Property

   Public ReadOnly Property Title(  ) As String
      Get
         Title = _title
      End Get
   End Property

   Public ReadOnly Property Reporter(  ) As String
      Get
         Reporter = _reporter
      End Get
   End Property

   Public ReadOnly Property Product(  ) As String
      Get
         Product = _product
      End Get
   End Property

   Public ReadOnly Property Version(  ) As String
      Get
         Version = _version
      End Get
   End Property

   Public ReadOnly Property Description(  ) As String
      Get
         Description = _description
      End Get
   End Property

   Public ReadOnly Property DateCreated(  ) As String
      Get
         DateCreated = _dateCreated
      End Get
   End Property

   Public ReadOnly Property Severity(  ) As String
      Get
         Severity = _severity
      End Get
   End Property

End Class

When the page is loaded, Page_Load is called, which in turn calls BindGrid. In BindGrid, the bugs ArrayList object is created, and two instances of Bug are added, each representing a bug. The DataSource property of DataGrid1 is set, and DataBind is called. The data grid binds each of the properties in Bug to a column in the data grid. The result is shown in Figure 10-1.

Figure 10-1. Displaying the bugs
figs/pan2_1001.gif

This result is both spectacular and unacceptable. It is spectacular because you've done so little work to display this data from your data source. You did nothing more than bind the collection to the data grid, and ASP.NET took care of the rest. It is unacceptable because this is not how you want the grid to look: the columns are in the wrong order, there is data you don't want to display, there is no link to a detail record, and so forth.

Before you improve on this version of the Bug display page, however, take a close look at Figure 10-1. Notice that there is a header on each column! The data grid picked up the title for each column from the Bug object. The default column header is the name of the property.

10.2.2 Version 2: Controlling the Columns

In the next iteration of this program, you'll eliminate the Description column, add a link to a details page (where you can display the description), change the order of the columns, and color the Severity red if it is marked "high." Piece of cake. The result is shown in Figure 10-2.

Figure 10-2. Taking control of Data Grid columns
figs/pan2_1002.gif

The complete .aspx page is shown in Example 10-6 and is analyzed in detail in the pages that follow.

Example 10-6. Completed .aspx file
<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" 
Inherits="DataGridMasterDetailNew.WebForm1" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > 

<html>
  <head>
    <meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">
    <meta name="CODE_LANGUAGE" Content="C#">
    <meta name=vs_defaultClientScript content="JavaScript (ECMAScript)">
    <meta name=vs_targetSchema content="http://schemas.microsoft.com/intellisense/ie5">
  </head>
 <body MS_POSITIONING="GridLayout">
  
    <form runat="server" ID="Form1">
      <asp:DataGrid id="dataGrid1" 
      OnItemDataBound="OnItemDataBoundEventHandler" 
      AutoGenerateColumns="False" 
      CellPadding="5" 
      HeaderStyle-BackColor="PapayaWhip" 
      BorderWidth="5px" 
      BorderColor="#000099" 
      AlternatingItemStyle-BackColor="LightGrey" 
      HeaderStyle-Font-Bold="True" 
      runat="server">
        <Columns>
          <asp:HyperLinkColumn HeaderText="Bug ID" 
           DataTextField="BugID" DataNavigateUrlField="BugID"
           DataNavigateUrlFormatString="details.aspx?bugID={0}"  />
          <asp:BoundColumn DataField="Title" HeaderText="Bug Title" />
          <asp:BoundColumn DataField="Reporter" HeaderText="Reported by" />
          <asp:BoundColumn DataField="Product" HeaderText="Product" />
          <asp:BoundColumn DataField="Version" HeaderText="Version" />
          <asp:BoundColumn DataField="DateCreated" 
             HeaderText="Date Created" />
          <asp:BoundColumn DataField="Severity" HeaderText="Severity" />
        </Columns>
      </asp:DataGrid>
    </form>
  </body>
</html>

In the VB.NET version, besides the page directive difference, we do not include this line in the HTML:

OnItemDataBound="OnItemDataBoundEventHandler"

The complete code-behind file in C# is shown in Example 10-7, and in VB.NET in Example 10-8.

Example 10-7. Implementing events with data grids in C#
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace DataGridMasterDetail
{
   public class WebForm1 : System.Web.UI.Page
   {

      protected System.Web.UI.WebControls.DataGrid dataGrid1;

      public WebForm1(  )
      {
         Page.Init += new System.EventHandler(Page_Init);
      }

      private void Page_Load(object sender, System.EventArgs e)
      {
         if (!IsPostBack) 
         {
            BindGrid(  );
         }
      }

      private void Page_Init(object sender, EventArgs e)
      {
         InitializeComponent(  );
      }

      // Handle the ItemDataBound event
      protected void OnItemDataBoundEventHandler(Object sender, DataGridItemEventArgs e) 
      {

         // Don't bother for header, footer and separator items
         ListItemType itemType = (ListItemType)e.Item.ItemType;
         if (itemType == ListItemType.Header || 
            itemType == ListItemType.Footer || 
            itemType == ListItemType.Separator)
            return;

         // e.Item.DataItem is the data for the item
         Bug bug = (Bug)e.Item.DataItem;

         // check the severity for this item
         // if it is high, set the cell to red
         if (bug.Severity == "High")
         {
            // this would make the entire entry red
            //  e.Item.ForeColor = Color.FromName("red");

            // get just the cell we want
            TableCell severityCell = (TableCell)e.Item.Controls[6];

            // set that cell's forecolor to red
            severityCell.ForeColor = Color.FromName("Red");
         }
      }
      void BindGrid(  ) 
      {
         ArrayList bugs = new ArrayList(  );
         bugs.Add(
            new Bug(
            101,
            "Bad Property Value",
            "Jesse Liberty", 
            "XBugs",
            "0.01",
            "Property values incorrect when you enter a new type",
            DateTime.Now,
            "High"
            )
            );

         bugs.Add(
            new Bug(
            102,
            "Doesn't load properly",
            "Dan Hurwitz", 
            "XBugs",
            "0.01",
            "The system fails on load with error x2397",
            DateTime.Now,
            "High"
            )
            );

         bugs.Add(
            new Bug(
            103,
            "Hangs on exit",
            "Jack Ryan", 
            "XBugs",
            "0.01",
            "When you press close, it hangs",
            DateTime.Now,
            "High"
            )
            );

         bugs.Add(
            new Bug(
            104,
            "Wrong data",
            "Demetri Karamazov", 
            "XBugs",
            "0.01",
            "The data does not match the DB",
            DateTime.Now,
            "Medium"
            )
            );
   
         dataGrid1.DataSource=bugs;
         dataGrid1.DataBind(  );

      }

    #region Web Form Designer generated code
      /// <summary>
      /// Required method for Designer support - do not modify
      /// the contents of this method with the code editor.
      /// </summary>
      private void InitializeComponent(  )
      {    
         this.Load += new System.EventHandler(this.Page_Load);

      }
    #endregion
   }
   public class Bug
   {
      private int bugID;
      private string title;
      private string reporter;
      private string product;
      private string version;
      private string description;
      private DateTime dateCreated;
      private string severity;

      public Bug(int id, string title, string reporter,
         string product, string version, 
         string description, DateTime dateCreated, 
         string severity)
      {
         bugID = id;                      
         this.title = title;              
         this.reporter = reporter;        
         this.product = product;          
         this.version = version;
         this.description = description;
         this.dateCreated = dateCreated;
         this.severity = severity;
      }
      public int        BugID          { get { return bugID;      }}
      public string     Title          { get { return title;      }}
      public string     Reporter       { get { return reporter;   }}
      public string     Product        { get { return product;    }}
      public string     Version        { get { return version;    }}
      public string     Description    { get { return description;}}
      public DateTime   DateCreated    { get { return dateCreated;}}
      public string     Severity       { get { return severity;   }}
   }
}
Example 10-8. Implementing events with data grids in VB.NET
Public Class WebForm1
   Inherits System.Web.UI.Page
   Protected WithEvents dataGrid1 As System.Web.UI.WebControls.DataGrid

#Region " Web Form Designer Generated Code "

   'This call is required by the Web Form Designer.
   <System.Diagnostics.DebuggerStepThrough(  )> Private Sub InitializeComponent(  )

   End Sub

   Private Sub Page_Init(ByVal sender As System.Object)
      'CODEGEN: This method call is required by the Web Form Designer
      'Do not modify it using the code editor.
      InitializeComponent(  )
   End Sub

#End Region

   Private Sub Page_Load(ByVal sender As System.Object, _
                        ByVal e As System.EventArgs) Handles MyBase.Load
      If Not IsPostBack Then
         BindGrid(  )
      End If
   End Sub

   Private Sub BindGrid(  )
      Dim bugs As New ArrayList(  )
      bugs.Add(New Bug(101, _
         "BadProperty Value", _
         "Jesse Liberty", _
         "XBugs", _
         "0.01", _
         "Property values incorrect", _
         DateTime.Now, _
         "High") _
      )
      bugs.Add( _
         New Bug( _
         102, _
         "Doesn't load properly", _
         "Dan Hurwitz", _
         "XBugs", _
         "0.01", _
         "The system fails with error x2397", _
         DateTime.Now, _
         "High") _
      )

      bugs.Add( _
         New Bug( _
         103, _
         "Hangs on exit", _
         "Jack Ryan", _
         "XBugs", _
         "0.01", _
         "When you press close, it hangs", _
         DateTime.Now, _
         "High") _
         )

      bugs.Add( _
         New Bug( _
         104, _
         "Wrong data", _
         "Demetri Karamazov", _
         "XBugs", _
         "0.01", _
         "The data does not match the DB", _
         DateTime.Now, _
         "Medium") _
         )

      dataGrid1.DataSource = bugs
      dataGrid1.DataBind(  )

   End Sub

   ' handle the item data bound event
   Protected Sub OnItemDataBoundEventHandler( _
   ByVal sender As System.Object, _
   ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) 


      ' Don't bother for the header, footer or separator type
      Dim itemType As ListItemType
      itemType = e.Item.ItemType
      If itemType = ListItemType.Header Or _
      itemType = ListItemType.Footer Or _
      itemType = ListItemType.Separator Then
         Exit Sub
      End If

      ' e.item.dataItem is the data for the item
      Dim theBug As Bug
      theBug = e.Item.DataItem

      ' check the severity of this item
      ' if it is high, set the cell to red
      If theBug.Severity = "High" Then
         Dim severityCell As TableCell

         ' just get the cell you want
         severityCell = e.Item.Controls(6)

         ' set the cell's foreground color to red
         severityCell.ForeColor = Color.FromName("Red")
      End If

      Dim linkCell As TableCell
      linkCell = e.Item.Controls(0)
      Dim h As HyperLink
      h = linkCell.Controls(0)
      h.NavigateUrl = "details.aspx?bugID=" & theBug.BugID

   End Sub
End Class

Public Class Bug
   Private _bugID As Int32
   Private _title As String
   Private _reporter As String
   Private _product As String
   Private _version As String
   Private _description As String
   Private _dateCreated As DateTime
   Private _severity As String

   Sub New(ByVal theID As Int32, _
   ByVal theTitle As String, _
   ByVal theReporter As String, _
   ByVal theProduct As String, _
   ByVal theVersion As String, _
   ByVal theDescription As String, _
   ByVal theDateCreated As DateTime, _
   ByVal theSeverity As String)

      _bugID = theID
      _title = theTitle
      _reporter = theReporter
      _product = theProduct
      _version = theVersion
      _description = theDescription
      _dateCreated = theDateCreated
      _severity = theSeverity
   End Sub

   Public ReadOnly Property BugID(  ) As Int32
      Get
         BugID = _bugID
      End Get
   End Property

   Public ReadOnly Property Title(  ) As String
      Get
         Title = _title
      End Get
   End Property

   Public ReadOnly Property Reporter(  ) As String
      Get
         Reporter = _reporter
      End Get
   End Property

   Public ReadOnly Property Product(  ) As String
      Get
         Product = _product
      End Get
   End Property

   Public ReadOnly Property Version(  ) As String
      Get
         Version = _version
      End Get
   End Property

   Public ReadOnly Property Description(  ) As String
      Get
         Description = _description
      End Get
   End Property

   Public ReadOnly Property DateCreated(  ) As String
      Get
         DateCreated = _dateCreated
      End Get
   End Property

   Public ReadOnly Property Severity(  ) As String
      Get
         Severity = _severity
      End Get
   End Property
End Class
10.2.2.1 Data-bound columns

The key changes to the DataGrid declaration are to add two attributes: AutoGenerateColumns and (for the C# example) OnItemDataBound. In addition, a number of style attributes are set to make the DataGrid look a bit nicer. The following is the replacement DataGrid tag for the one shown in Example 10-2.

<asp:DataGrid id="dataGrid1" 
AutoGenerateColumns="False" 
OnItemDataBound="OnItemDataBoundEventHandler" 
CellPadding="5"
HeaderStyle-BackColor="PapayaWhip"
BorderWidth ="5px"
BorderColor = "#000099"
AlternatingItemStyle-BackColor ="LightGrey"
HeaderStyle-Font-Bold
runat="server" >

AutoGenerateColumns is set to false, so that the data grid will not automatically add a column for every property it finds for the Bug object. You are now free to add bound columns for the data you do want to display, in whatever order you choose.

Bound columns are added within a Columns tag that acts as a subcontrol for the DataGrid object, as follows:

<Columns>

</Columns>

Between the opening and the closing tags, you'll add the various bound columns:

<Columns>
   <asp:HyperLinkColumn HeaderText="Bug ID" 
    DataTextField="BugID" DataNavigateUrlField="BugID"
    DataNavigateUrlFormatString="details.aspx?bugID={0}" />
   <asp:BoundColumn DataField="Title" HeaderText="Bug Title"/>
   <asp:BoundColumn DataField="Reporter" HeaderText="Reported by"/>
   <asp:BoundColumn DataField="Product" HeaderText="Product"/>
   <asp:BoundColumn DataField="Version" HeaderText="Version"/>
   <asp:BoundColumn DataField="DateCreated" HeaderText="Date Created"/>
   <asp:BoundColumn DataField="Severity" HeaderText="Severity"/>
</Columns>

Skip over the first column for now and look at the remaining ones, which are all simple BoundColumn elements. Each is given a DataField attribute to identify which property of the Bug object holds the data for that field, and a HeaderText attribute that defines a caption for that column's header.

Go back and look at the very first column that you skipped over previously.

<asp:HyperLinkColumn HeaderText="Bug ID" 
 DataTextField="BugID" DataNavigateUrlField="BugID"
 DataNavigateUrlFormatString="details.aspx?bugID={0}" />

The job of the first column is not just to display the bug ID, but also to provide a link to the detail page for that bug. This is accomplished by creating an anchor tag using the HyperLinkColumn element.

The text to be displayed is taken from the data as defined by the DataTextField attribute. In this case, the text will be the value of the BugID property of the Bug object in the data grid's data source collection. The header to display for this column is set by the HeaderText attribute (in this case "Bug ID").

The link is created by the combination of the DataNavigateUrlField and DataNavigateUrlFormatString attributes. The {0} symbol is a substitution parameter. ASP.NET knows to substitute the value in the DataNavigateUrlField (the bug ID) for the parameter {0} in DataNavigateUrlFormatString If, for example, the current record's BugID is 101, the link created will be details.aspx?bugID=101.

10.2.2.2 Handling the ItemDataBound event

The ItemDataBound event is fired every time a data item is bound to a control. The OnItemDataBound attribute of the DataGrid control sets the method that will be called when the ItemDataBound event is fired, as the following fragment from the DataGrid tag shows:

OnItemDataBound="OnItemDataBoundEventHandler"

When the event fires, the event handler method is called. At that time, you can fix up the item in the data grid based on the contents of the data item. In this example, you'll set the value to display in red if the severity is High.

Remember, the item is the element in the data grid and the data item is the data associated with that item from the collection that is the data grid's data source.

Your OnItemDataBoundEventHandler must take two parameters: an object and a DataGridItemEventArgs type. You may of course name these arguments whatever you like. Visual Studio .NET will name them sender and e, respectively, when you declare the event handler.

The DataGridItemEventArgs object (e) has an Item property (e.Item) which returns the referenced item from the DataGrid control. That is, e.Item returns the item in the DataGrid that raised the event.

This item returned by e.Item is an object of type DataGridItem. As mentioned earlier, the DataGridItem class has an ItemType property (e.Item.ItemType) which returns a member of the ListItemType enumeration. You examine that value to see if it is equal to one of the enumerated types you want to ignore (Header, Footer, Separator), and if so, you return immediately, taking no further action on this item. In C#, this looks like:

ListItemType itemType = (ListItemType) e.Item.ItemType;
if (itemType == ListItemType.Header || 
   itemType == ListItemType.Footer || 
   itemType == ListItemType.Separator)
   return;

In VB.NET, the code is:

Dim itemType As ListItemType
itemType = CType(e.Item.ItemType, ListItemType)
If itemType = ListItemType.Header Or _
              itemType = ListItemType.Footer Or _
              itemType = ListItemType.Separator Then
                    Return
End If

Assuming you do have an item of a type you care about, you want to extract the actual data item that this row in the grid will represent. You go back to the object returned by the Item property, which you will remember is a DataGridItem object. The DataGridItem object has another property, DataItem, which gets us the actual Bug object from the collection that is this data grid's data source, as the following C# code fragment illustrates:

Bug bug = (Bug)e.Item.DataItem;

In VB.NET, the equivalent is:

Dim theBug As Bug
theBug = CType(e.Item.DataItem, Bug)

Bug is a class, and thus a reference object; therefore bug is a reference to the actual Bug object rather than a copy.

The relationships among the data grid objects can be a bit confusing and are worth a quick review. There are five objects involved in the previous scenario:

  • The data grid (DataGrid1)

  • The ArrayList (bugList), which acts as the data source to the data grid

  • The DataGridItemEventArgs object, which is passed as the second parameter (e) to your designated event handler (OnItemDataBoundEventHandler) each time an item is added to the grid

  • The DataGridItem object that raised the event and a reference to which you can get from the Item property of the DataGridItemEventArgs object (e.Item)

  • The Bug that is being added to the data grid, which you can get to through the DataItem property of the DataGridItemEventArgs object (e.Item.DataItem)

10.2.2.3 Conditionally setting the severity color

Each time a data item is bound, the OnItemDataBoundEventHandler event handler is called, and you have an opportunity to examine the data and take action based on the specific data item being added. In this example, you'll check the severity of the bug, and if it is high, you'll set the color of that column to red.

To do so, you start with the Bug object, which in C# would be written:

Bug bug = (Bug)e.Item.DataItem;

The equivalent in VB.NET is:

Dim theBug As Bug
theBug = CType(e.Item.DataItem, Bug)

Severity is a property of the Bug object, illustrated here in C#:

if (bug.Severity == "High")
{

In VB.NET, it is:

If theBug.Severity = "High" Then

To set the entire row to red, just set the ForeColor property for the item:

e.Item.ForeColor = Color.FromName("red");

FromName is a static method of the Color class, which in turn is a class provided by the System.Drawing namespace in the .NET Framework.

You've set the row red, but in this example you want to set only a single cell. The DataGridItem object has a Controls collection that represents all the child controls for that DataGrid item. Controls is of type ControlCollection and supplies a zero-based indexer that you can use like an array. The cell you want for the bugID is the seventh in the Controls collection, which in C# you specify using:

TableCell severityCell = (TableCell)e.Item.Controls[6];

Once you have that cell, you can set the properties of that TableCell object:

severityCell.ForeColor = Color.FromName("Red");

In VB.NET, these lines of code are:

Dim severityCell As TableCell
severityCell = e.Item.Controls(6)
severityCell.ForeColor = Color.FromName("Red")
10.2.2.4 Creating the hyperlink

In this example you have set the URL through the DataNavigateUrlField and the DataNavigateUrlFormatString attributes. It is possible, however, that you want to set the URL based not on a single attribute of the data item, but on a computation you'd like to make when the item is added to the grid. In that case, you can remove these two attributes from the declaration, and update the URL when you process the ItemDataBound event.

To set the anchor tag, you need the Hyperlink object within the first cell of the table. You start by getting the TableCell object, in this case the first cell in the row, which in C# looks like:

TableCell linkCell = (TableCell)e.Item.Controls[0];

In VB.NET, this is done using:

Dim linkCell As TableCell
linkCell = CType(e.Item.Controls(0), TableCell)

The table cell itself has child controls. The first child control is the hyperlink. The hyperlink was placed in that cell when the HyperLinkColumn was created in the .aspx file:

<asp:HyperlinkColumn  HeaderText="BugID" DataTextField="BugID"  />

You extract the HyperLink object from the TableCell by casting the first element in the collection to type HyperLink:

HyperLink h = (HyperLink) linkCell.Controls[0];

In VB.NET, this is done using:

Dim h As HyperLink
h = CType(linkCell.Controls(0), HyperLink)

The HyperLink object has a NavigateUrl property. You can now set that to whatever string you like. For example, to accomplish the same work you did with the DataNavigateUrlField and the DataNavigateUrlFormatString attributes, you can set the NavigateUrl property in C# as follows:

h.NavigateUrl = "details.aspx?bugID=" + bug.BugID;

In VB.NET, use:

h.NavigateUrl = "details.aspx?bugID=" & theBug.BugID

10.2.3 Version 3: The Details Page

In the next version, you'll create the details page that the data grid links to. In addition, you'll add a footer to the data grid that summarizes how many bugs were found.

Example 10-9 is the modified C# code for the code-behind page, and Example 10-10 is the modified VB.NET code for the code-behind page. Detailed analysis follows the listings.

Example 10-9. Modified to handle footer and details page (C#)
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace DataGridMasterDetailNew
{
   public class WebForm1 : System.Web.UI.Page
   {

      protected System.Web.UI.WebControls.DataGrid dataGrid1;

      public WebForm1(  )
      {
         Page.Init += new System.EventHandler(Page_Init);
      }

      private void Page_Load(object sender, System.EventArgs e)
      {
         if (!IsPostBack) 
         {
            BindGrid(  );
         }
      }

      private void Page_Init(object sender, EventArgs e)
      {
         InitializeComponent(  );
      }
      protected void OnItemCreatedEventHandler(
        Object sender, DataGridItemEventArgs e)
      {
         ListItemType itemType = (ListItemType)e.Item.ItemType;
         if (itemType == ListItemType.Footer)
         {
            // get the number of cells
            int numberOfCells = e.Item.Cells.Count;

            // remove all the cells except the last
            for (int i = 0; i < numberOfCells - 1; i++)
            {
               e.Item.Cells.RemoveAt(0); 
            }

            // create string to report number
            // of bugs found
            int numberOfBugs = dataGrid1.Items.Count;
            string msg;
            if (numberOfBugs > 0 ) 
            {
               msg = "<b>" + numberOfBugs.ToString(  ) + " bugs.</b>";
            }
            else
            {
               msg = "No bugs found.";
            }

            // get the one remaining cell
            TableCell msgCell = e.Item.Cells[0];
            msgCell.Text = msg;
            msgCell.ColumnSpan=numberOfCells;
            msgCell.HorizontalAlign = HorizontalAlign.Right;
         }
      }
      protected void OnItemDataBoundEventHandler(
        Object sender, DataGridItemEventArgs e) 
      {

         // Don't bother for header, footer and separator items
         ListItemType itemType = (ListItemType)e.Item.ItemType;
         if (itemType == ListItemType.Header || 
            itemType == ListItemType.Footer || 
            itemType == ListItemType.Separator)
            return;

         // e.Item.DataItem is the data for the item
         Bug bug = (Bug)e.Item.DataItem;

         // check the severity for this item
         // if it is high, set the cell to red
         if (bug.Severity == "High")
         {
            // this would make the entire entry red
            //  e.Item.ForeColor = Color.FromName("red");

            // get just the cell we want
            TableCell severityCell = (TableCell)e.Item.Controls[6];

            // set that cell's forecolor to red
            severityCell.ForeColor = Color.FromName("Red");
         }

         // get a reference to the HyperLink control in the first column
         TableCell linkCell = (TableCell)e.Item.Controls[0];

         // Controls[0]  the hyperlink
         HyperLink h = (HyperLink) linkCell.Controls[0];

         // create the link to the detail page
         h.NavigateUrl = "details.aspx?bugID=" + bug.BugID;
      }
      void BindGrid(  ) 
      {
         ArrayList bugs = new ArrayList(  );
         bugs.Add(
            new Bug(
            101,
            "Bad Property Value",
            "Jesse Liberty", 
            "XBugs",
            "0.01",
            "Property values incorrect when you enter a new type",
            DateTime.Now,
            "High"
            )
            );

         bugs.Add(
            new Bug(
            102,
            "Doesn't load properly",
            "Dan Hurwitz", 
            "XBugs",
            "0.01",
            "The system fails on load with error x2397",
            DateTime.Now,
            "High"
            )
            );

         bugs.Add(
            new Bug(
            103,
            "Hangs on exit",
            "Jack Ryan", 
            "XBugs",
            "0.01",
            "When you press close, it hangs",
            DateTime.Now,
            "High"
            )
            );

         bugs.Add(
            new Bug(
            104,
            "Wrong data",
            "Demetri Karamazov", 
            "XBugs",
            "0.01",
            "The data does not match the DB",
            DateTime.Now,
            "Medium"
            )
            );
   
         dataGrid1.DataSource=bugs;
         dataGrid1.DataBind(  );
         Session["bugList"] = bugs;

      }

    #region Web Form Designer generated code
      private void InitializeComponent(  )
      {    
         this.Load += new System.EventHandler(this.Page_Load);

      }
    #endregion
   }

   public class Bug
   {
      private int bugID;
      private string title;
      private string reporter;
      private string product;
      private string version;
      private string description;
      private DateTime dateCreated;
      private string severity;

      public Bug(int id, string title, string reporter,
         string product, string version, 
         string description, DateTime dateCreated, 
         string severity)
      {
         bugID = id;                      
         this.title = title;              
         this.reporter = reporter;        
         this.product = product;          
         this.version = version;
         this.description = description;
         this.dateCreated = dateCreated;
         this.severity = severity;
      }
      public int        BugID          { get { return bugID;  }}
      public string     Title          { get { return title;  }}
      public string     Reporter       { get { return reporter;}}
      public string     Product        { get { return product; }}
      public string     Version        { get { return version; }}
      public string     Description    { get { return description; }}
      public DateTime   DateCreated    { get { return dateCreated; }}
      public string     Severity       { get { return severity;  }}
   }
}
Example 10-10. Modified to handle footer and details page (VB.NET)
Public Class WebForm1
   Inherits System.Web.UI.Page
   Protected WithEvents dataGrid1 As System.Web.UI.WebControls.DataGrid

#Region " Web Form Designer Generated Code "

   'This call is required by the Web Form Designer.
   <System.Diagnostics.DebuggerStepThrough(  )> Private Sub InitializeComponent(  )

   End Sub

   Private Sub Page_Init(ByVal sender As System.Object)
      'CODEGEN: This method call is required by the Web Form Designer
      'Do not modify it using the code editor.
      InitializeComponent(  )
   End Sub

#End Region

   Private Sub Page_Load(ByVal sender As System.Object, _
                        ByVal e As System.EventArgs) Handles MyBase.Load
      If Not IsPostBack Then
         BindGrid(  )
      End If
   End Sub

   Private Sub BindGrid(  )
      Dim bugs As New ArrayList(  )
      bugs.Add(New Bug(101, _
         "BadProperty Value", _
         "Jesse Liberty", _
         "XBugs", _
         "0.01", _
         "Property values incorrect", _
         DateTime.Now, _
         "High") _
      )
      bugs.Add( _
         New Bug( _
         102, _
         "Doesn't load properly", _
         "Dan Hurwitz", _
         "XBugs", _
         "0.01", _
         "The system fails with error x2397", _
         DateTime.Now, _
         "High") _
      )

      bugs.Add( _
         New Bug( _
         103, _
         "Hangs on exit", _
         "Jack Ryan", _
         "XBugs", _
         "0.01", _
         "When you press close, it hangs", _
         DateTime.Now, _
         "High") _
         )

      bugs.Add( _
         New Bug( _
         104, _
         "Wrong data", _
         "Demetri Karamazov", _
         "XBugs", _
         "0.01", _
         "The data does not match the DB", _
         DateTime.Now, _
         "Medium") _
         )

      dataGrid1.DataSource = bugs
      dataGrid1.DataBind(  )
      Session("BugList") = bugs

   End Sub

   ' Event handler for when items are created
   Protected Sub OnItemCreatedEventHandler( _
   ByVal sender As System.Object, _
   ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) 
      Dim itemType As ListItemType
      itemType = e.Item.ItemType
      If itemType = ListItemType.Footer Then

         ' get the number of cells
         Dim numberOfCells As Int32
         numberOfCells = e.Item.Cells.Count

         ' remove all cells except the last
         Dim i As Integer
         For i = 0 To numberOfCells - 2
            e.Item.Cells.RemoveAt(0)
         Next

         ' create string to report number
         ' of bugs found
         Dim numberOfBugs As Int32
         numberOfBugs = dataGrid1.Items.Count
         Dim msg As String
         If numberOfBugs > 0 Then
            msg = "<b>" & numberOfBugs.ToString & " bugs.</b>"
         Else
            msg = "No bugs found"
         End If

         ' get the one remaining cell
         ' fill it with number bugs found
         Dim msgCell As TableCell
         msgCell = e.Item.Cells(0)
         msgCell.Text = msg
         msgCell.ColumnSpan = numberOfCells
         msgCell.HorizontalAlign = HorizontalAlign.Right

      End If
   End Sub

   ' handle item data bound event
   Protected Sub OnItemDataBoundEventHandler( _
   ByVal sender As System.Object, _
   ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) 

      Dim itemType As ListItemType
      itemType = e.Item.ItemType

      ' don't bother for header footer or separator
      If itemType = ListItemType.Header Or _
      itemType = ListItemType.Footer Or _
      itemType = ListItemType.Separator Then
         Exit Sub
      End If

      'e.item.dataitem is the data for the item
      Dim theBug As Bug
      theBug = e.Item.DataItem

      ' if the severity is high, color it red
      If theBug.Severity = "High" Then
         Dim severityCell As TableCell
         severityCell = e.Item.Controls(6)
         severityCell.ForeColor = Color.FromName("Red")
      End If

      ' get a reference to the hyperlink control in the first oclumn
      Dim linkCell As TableCell
      linkCell = e.Item.Controls(0)

      ' get the hyperlink
      Dim h As HyperLink
      h = linkCell.Controls(0)

      ' create a link to the detail page
      h.NavigateUrl = "details.aspx?bugID=" & theBug.BugID

   End Sub
End Class

Public Class Bug
   Private _bugID As Int32
   Private _title As String
   Private _reporter As String
   Private _product As String
   Private _version As String
   Private _description As String
   Private _dateCreated As DateTime
   Private _severity As String

   Sub New(ByVal theID As Int32, _
   ByVal theTitle As String, _
   ByVal theReporter As String, _
   ByVal theProduct As String, _
   ByVal theVersion As String, _
   ByVal theDescription As String, _
   ByVal theDateCreated As DateTime, _
   ByVal theSeverity As String)

      _bugID = theID
      _title = theTitle
      _reporter = theReporter
      _product = theProduct
      _version = theVersion
      _description = theDescription
      _dateCreated = theDateCreated
      _severity = theSeverity
   End Sub

   Public ReadOnly Property BugID(  ) As Int32
      Get
         BugID = _bugID
      End Get
   End Property

   Public ReadOnly Property Title(  ) As String
      Get
         Title = _title
      End Get
   End Property

   Public ReadOnly Property Reporter(  ) As String
      Get
         Reporter = _reporter
      End Get
   End Property

   Public ReadOnly Property Product(  ) As String
      Get
         Product = _product
      End Get
   End Property

   Public ReadOnly Property Version(  ) As String
      Get
         Version = _version
      End Get
   End Property

   Public ReadOnly Property Description(  ) As String
      Get
         Description = _description
      End Get
   End Property

   Public ReadOnly Property DateCreated(  ) As String
      Get
         DateCreated = _dateCreated
      End Get
   End Property

   Public ReadOnly Property Severity(  ) As String
      Get
         Severity = _severity
      End Get
   End Property
End Class
10.2.3.1 Summary footer

To add the summary, you must tell the data grid to show its footer. This is set declaratively, as an attribute in the data grid declaration, as follows:

<form runat="server" ID="Form1">
   <asp:DataGrid id="dataGrid1" 
     ShowFooter="True"
     FooterStyle-BackColor="Yellow"

To populate the footer you'll want to handle the ItemCreated event. This event is raised when an item in the data grid is created. You are particularly interested in the event that will be raised when the footer item is created, because you want to manipulate this item. For C#, you'll add an attribute to the DataGrid declaration for this event, just as you did for the ItemDataBound event. Here is the complete declaration of the data grid:

<asp:DataGrid id="dataGrid1" 
OnItemDataBound="OnItemDataBoundEventHandler" 
OnItemCreated="OnItemCreatedEventHandler"
AutoGenerateColumns="False" 
CellPadding="5" 
HeaderStyle-BackColor="Yellow" 
BorderWidth="5px" 
BorderColor="#000099" 
AlternatingItemStyle-BackColor="LightGrey" 
HeaderStyle-Font-Bold="True"
ShowFooter="True"
FooterStyle-BackColor="Yellow" 
runat="server">

In VB.NET, you do not add this attribute, but you do add the event handler, as you do in C#. The first step is to check that the item is of type ListItemFooter. If so, then you want to remove all the cells in the footer except one, and set that cell's span to encompass the entire row. You'll then get the count of items in the grid and write a right-aligned message into the cell such as 4 bugs, thus displaying the message in the lower right-hand corner of the grid.

ASP.NET has provided a programmatic interface to the attributes of the table cell in the DataGrid. This is just as if you had access to the <td> element and set its attributes accordingly, but you can do so dynamically at runtime, rather than statically at design time.

The event handler signature is just like the OnItemDataBoundEventHandler signature: it takes two parameters and returns void (or in VB.NET it is a Sub procedure). The parameters must be an Object and a DataGridItemEventArgs object, as shown in the following prototype in C#:

public void OnItemCreatedEventHandler(
   Object sender, DataGridItemEventArgs e)
{

In VB.NET, you implement the event handler with code that indicates that you are handling the ItemCreated event:

Public Sub dataGrid1_OnItemCreatedEventHandler( _
        ByVal sender As System.Object, _
        ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) _
        Handles dataGrid1.ItemCreated

You'll test the item type of the current item so that you take action only on the footer item, which in C# looks like this:

ListItemType itemType = (ListItemType)e.Item.ItemType;
if (itemType == ListItemType.Footer)
{

In VB.NET, it is:

Dim itemType As ListItemType
itemType = e.Item.ItemType
If itemType = ListItemType.Footer Then

You can determine the number of cells in the grid dynamically by asking the item for its Cells collection, which has a Count property. Once you know how many cells you have, you can remove all but one by calling the Cells collection's RemoveAt method, repeatedly removing the first cell until every one but the last has been removed. In C#, this looks like:

int numberOfCells = e.Item.Cells.Count;

// remove all the cells except the last
for (int i = 0; i < numberOfCells - 1; i++)
{
   e.Item.Cells.RemoveAt(0); 
}

And in VB.NET:

Dim numberOfCells As Integer
numberOfCells = e.Item.Cells.Count

Dim i As Integer
For i = 0 To numberOfCells - 2
   e.Item.Cells.RemoveAt(0)
Next

You next ask the data grid for its Items collection, which you will remember is a collection of all the items in the grid. You can use the Count property of that collection to determine how many items there are in the entire grid, and formulate your output message accordingly:

int numberOfBugs = dataGrid1.Items.Count;
string msg;
if (numberOfBugs > 0 ) 
{
    msg = "<b>" + numberOfBugs.ToString(  ) 
       + " bugs.</b>";
}
else
{
   msg = "No bugs found.";
}

In VB.NET, the code is:

Dim numberOfBugs As Integer = dataGrid1.Items.Count
Dim msg As String
If numberOfBugs > 0 Then
   msg = "<b>" & numberOfBugs.ToString & " bugs.</b>"
Else
   msg = "No bugs found"
End If

You are now ready to display the message in the cell. You obtain the TableCell object as you did in the previous example. The only remaining cell is the very first cell in the collection. You set that cell's Text property to the message you've created, and set its ColumnSpan property to the total number of cells that were in the row before you removed all the others. You then set the HorizontalAlign property to the enumerated value Right:

TableCell msgCell = e.Item.Cells[0];
msgCell.Text = msg;
msgCell.ColumnSpan=numberOfCells;
msgCell.HorizontalAlign = HorizontalAlign.Right;

The VB.NET code is:

Dim msgCell As TableCell
msgCell = e.Item.Cells(0)
msgCell.Text = msg
msgCell.ColumnSpan = numberOfCells
msgCell.HorizontalAlign = HorizontalAlign.Right

The result is to display the number of rows in the grid in the lower right-hand side of the footer row, as shown in Figure 10-3.

Figure 10-3. Summary in footer row
figs/pan2_1003.gif
10.2.3.2 Creating the details page

In the previous example you created a link to the details page, but you did not implement that page. To do so, you'll create a new .aspx page, details.aspx, which will have a fairly simple table to display the details of the bug, as shown in Figure 10-4.

Figure 10-4. The details page
figs/pan2_1004.gif

Each row will have two cells, one with a label in boldface, and the second with the data from the Bug object. The following code in the .aspx page creates a row:

<TR>
  <TD  width="30%">
    <b>BugID</b> 
  </TD>
  <TD align=left>
    <%# DataBinder.Eval(CurrentBug, "BugID") %>
  </TD>
</TR>

The DataBinder class provides a static method, Eval, that uses reflection to parse and evaluate a data binding expression against an object at runtime. In this case, we are passing in a Bug object and a property to retrieve from that object; Eval returns the value. The <%# syntax in the ASP.NET page binds the text to display in the cell to the string value returned by Eval. The complete details.aspx is shown in Example 10-11. The subsequent rows have been collapsed to save space.

Example 10-11. details.aspx
<%@ Page language="c#" 
Codebehind="details.aspx.cs" 
AutoEventWireup="false" 
Inherits="DataGridMasterDetailNew.details" %>

<HTML>
<HEAD>
   <meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">
   <meta name="CODE_LANGUAGE" Content="C#">
   <meta name=vs_defaultClientScript content="JScript">
   <meta name=vs_targetSchema content="Internet Explorer 5.0">
</HEAD>
<body MS_POSITIONING="GridLayout">
<form id="details" method="post" runat="server">
<asp:Panel ID="BugDetailsPanel" Runat="server">
   <TABLE style="FONT-SIZE: 8pt; COLOR: black; 
   FONT-FAMILY: Arial" cellSpacing=0 
   cellPadding=2 width="100%" border=0>
      <TR>
         <TD  width="30%">
            <b>BugID</b> 
         </TD>
         <TD align=left>
            <%# DataBinder.Eval(CurrentBug, "BugID") %>
         </TD>
      </TR>
      <tr><td><b>Title</b></td>
         <td><%# DataBinder.Eval(CurrentBug,"Title") %></td>
      </tr>
      <tr><td><b>Reported by</b></td>
         <td><%# DataBinder.Eval(CurrentBug,"Reporter") %></td>
      </tr><tr><td><b>Product</b></td>
         <td><%# DataBinder.Eval(CurrentBug,"Product") %></td>
      </tr>
      <tr><td><b>Version</b></td>
         <td><%# DataBinder.Eval(CurrentBug,"Version") %></td>
      </tr>
      <tr><td><b>Description</b></td>
         <td><%# DataBinder.Eval(CurrentBug,"Description") %></td>
      </tr>
      <tr><td><b>Date Created</b></td>
         <td><%# DataBinder.Eval(CurrentBug,"DateCreated") %></td>
      </tr>
      <tr><td><b>Severity</b></td>
         <td><%# DataBinder.Eval(CurrentBug,"Severity") %></td>
      </tr>
   </TABLE>
</asp:Panel>
</form>
</body>
</HTML>

The page works only if the CurrentBug value is set properly. This is done in the code-behind page, specifically in the Page_Load method. Page_Load retrieves the Request.QueryString collection, which contains all the query strings passed in with the URL.

When you write a URL and append a question mark (?), the string elements that follow the question mark are considered to be query strings. Consider what would happen if you wrote the URL as follows:

details.aspx?bugID=101

The first part, details.aspx, will be treated as the URL and the second part, bugID=101, will be considered the query string.

The complete C# source for the code-behind page for details.aspx is shown in C# in Example 10-12 and in VB.NET in Example 10-13.

Example 10-12. Code behind for details.aspx in C#
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace DataGridMasterDetailNew
{
  public class details : System.Web.UI.Page
  {
      private object currentBug;
      protected System.Web.UI.WebControls.Panel BugDetailsPanel;

      public object CurrentBug { get { return currentBug; } }
   
    public details(  )
    {
      Page.Init += new System.EventHandler(Page_Init);
    }

    private void Page_Load(object sender, System.EventArgs e)
    {
         string bugID = Request.QueryString["BugID"];
         if (bugID != null)
         {
            SetCurrentBug(Convert.ToInt32(bugID));
            BugDetailsPanel.DataBind(  );
         }
    }

      private void SetCurrentBug(int bugID)
      {
         ArrayList bugs = (ArrayList) Session["bugList"];
         foreach (Bug theBug in bugs)
         {
            if(theBug.BugID == bugID)
               currentBug = theBug;
         }

      }
    private void Page_Init(object sender, EventArgs e)
    {
      InitializeComponent(  );
    }

    #region Web Form Designer generated code
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent(  )
    {    
         this.Load += new System.EventHandler(this.Page_Load);

      }
    #endregion
  }
}
Example 10-13. Code behind for details.aspx in VB.NET
Public Class details
   Inherits System.Web.UI.Page
   Protected BugDetailsPanel As System.Web.UI.WebControls.Panel

#Region " Web Form Designer Generated Code "

   'This call is required by the Web Form Designer.
   <System.Diagnostics.DebuggerStepThrough(  )> Private Sub InitializeComponent(  )

   End Sub

   Private Sub Page_Init( _
   ByVal sender As System.Object, ByVal e As System.EventArgs) _
   Handles MyBase.Init
      InitializeComponent(  )
   End Sub

#End Region

   Private Sub Page_Load( _
      ByVal sender As System.Object, _
      ByVal e As System.EventArgs) _
      Handles MyBase.Load
      
      Dim bugID As String
      bugID = Request.QueryString("BugID")
      If bugID <> "" Then
         SetCurrentBug(CInt(bugID))
         BugDetailsPanel.DataBind(  )
      End If
   End Sub

   Private Sub SetCurrentBug(ByVal bugID As Int32)
      Dim bugs As ArrayList
      bugs = Session("bugList")
      Dim theBug As Bug
      For Each theBug In bugs
         If theBug.BugID = bugID Then
            _currentBug = theBug
         End If
      Next
   End Sub
   
   Private _currentBug As Object

   Public ReadOnly Property CurrentBug(  ) As Object
      Get
         CurrentBug = _currentBug
      End Get
   End Property

End Class

ASP.NET wraps the HTTP request in a Request object (familiar to ASP programmers) and makes this object available to your methods. The Request object has a QueryString property to retrieve the query strings. The QueryString property returns a NameValueCollection that can be treated like an array indexed on strings. Thus, you can retrieve the bugID queryString value (101) with this line of code:

string bugID = Request.QueryString["BugID"];

Or, in VB.NET, use this code:

Dim bugID As String
bugID = Request.QueryString("BugID")

If the bugID is not null, you will set a private variable, CurrentBug, to the value extracted (e.g., 101) and bind the data for display in the details page. In C#, the private variable CurrentBug is an int. In VB.NET, the private variable is an Integer named _currentBug. The value retrieved from Request.QueryString is a string, and so must be converted to an int, an operation performed by the following C# code fragment:

if (bugID != null)
   {
      SetCurrentBug(Convert.ToInt32(bugID));
      BugDetailsPanel.DataBind(  );
}

In VB.NET, the equivalent is:

If bugID <> "" Then
   SetCurrentBug(CInt(bugID))
   BugDetailsPanel.DataBind(  )
End If

The private method SetCurrentBug is responsible for setting the private variable currentBug (_curentBug). In the example, it does so by iterating over the Bug objects in the ArrayList and finding the matching bug:

private void SetCurrentBug(int bugID)
{
   ArrayList bugs = (ArrayList) Session["bugList"];
   foreach (Bug theBug in bugs)
   {
      if(theBug.BugID == bugID)
         currentBug = theBug;
   }
}

In VB.NET, it looks like this:

Private Sub SetCurrentBug(ByVal bugID As Integer)
   Dim bugs As ArrayList
   bugs = Session("bugList")
   Dim theBug As Bug
   For Each theBug In bugs
      If theBug.BugID = bugID Then
         _currentBug = theBug
      End If
   Next
End Sub

Because currentBug is a private variable, it is not available to the dataGrid. You will therefore create a CurrentBug property that returns the value of currentBug:

public object CurrentBug {get {return currentBug;}}

In VB.NET, it looks like this:

Public ReadOnly Property CurrentBug(  ) As Object
   Get
      CurrentBug = _currentBug
   End Get
End Property

C# is case-sensitive, and so the name for the variable (currentBug) and the property (CurrentBug) are not considered to be the same. The property is in Pascal Notation (initial cap), and the name for the variable is in camel Notation (initial not capitalized).

VB.NET is not case-sensitive, so you use an underscore in front of the variable name.

10.2.4 Version 4: Sorting and Paging

In the next version of this program, you'll integrate the details panel into the page with the DataGrid, and you'll add the ability to sort the columns, as well as to page through the results.

10.2.4.1 Results on one page

In the previous version, you created a panel to hold the details and displayed that panel in a second .aspx page. In this version, you will paste that entire panel, and all the code created within the panel, into the same .aspx page as the data grid.

You will remember that the data grid page ends with this HTML:

    </form>
  </body>
</html>

Just after the close form tag, </form>, and before the close body tag, </body>, insert the panel from the details page. Hey! Presto! When you click on the details, they'll show in the panel (once you modify the code a bit). The complete C# .aspx page is shown in Example 10-14.

Example 10-14. .aspx page for sorting and paging
<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" 
Inherits="DataGridDetailsInPage.WebForm1" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > 

<html>
  <head>
    <meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">
    <meta name="CODE_LANGUAGE" Content="C#">
    <meta name=vs_defaultClientScript content="JavaScript (ECMAScript)">
    <meta name=vs_targetSchema content="http://schemas.microsoft.com/intellisense/ie5">
  </head>
 <body MS_POSITIONING="GridLayout">
  
      <form runat="server" ID="Form1">
      <asp:DataGrid id="dataGrid1" 
      OnItemDataBound="OnItemDataBoundEventHandler" 
       OnItemCreated ="OnItemCreatedEventHandler"
       OnSelectedIndexChanged="OnSelectedIndexChangedHandler"
       OnSortCommand="OnSortCommandHandler"
       OnPageIndexChanged ="OnPageIndexChangedHandler"
         AllowPaging="True"
       PageSize ="2"
       AllowSorting="True"
      AutoGenerateColumns="False" 
      CellPadding="5" 
      HeaderStyle-BackColor="Yellow" 
      BorderWidth="5px" 
      BorderColor="#000099" 
      AlternatingItemStyle-BackColor="LightGrey" 
      HeaderStyle-Font-Bold="True"
       ShowFooter="True"
       FooterStyle-BackColor="Yellow" 
       DataKeyField="BugID"
      runat="server">
      
         <PagerStyle 
         HorizontalAlign="Right" 
         Mode="NextPrev">
         </PagerStyle>
         
        <Columns>
          <asp:ButtonColumn Text="Details" CommandName="Select"  />
          
          <asp:BoundColumn  
          HeaderText="Title" 
           DataField="Title"
           SortExpression="Title"
          />
          
          <asp:BoundColumn 
          HeaderText="Reported by" 
          Datafield="Reporter"
          SortExpression="Reporter"
          />
          
          
          <asp:BoundColumn DataField="Product" HeaderText="Product" />
          <asp:BoundColumn DataField="Version" HeaderText="Version" />
          
          <asp:BoundColumn 
          HeaderText="Date Created" 
          DataField="DateCreated"
          SortExpression="DateCreated"
          />
          
          <asp:BoundColumn DataField="Severity" HeaderText="Severity" />
        </Columns>
      </asp:DataGrid>
    </form>
<asp:Panel ID="BugDetailsPanel" Runat="server">
<TABLE style="FONT-SIZE: 8pt; COLOR: black; FONT-FAMILY: Arial" cellSpacing=0 
cellPadding=2 width="100%" border=0>
  <TR>
    <TD width="15%"><B>BugID</B> </TD>
    <TD align=left><%# DataBinder.Eval(currentBug, "BugID") %></TD></TR>
  <TR>
    <TD><B>Title</B></TD>
    <TD><%# DataBinder.Eval(currentBug,"Title") %></TD></TR>
  <TR>
    <TD><B>Reported by</B></TD>
    <TD><%# DataBinder.Eval(currentBug,"Reporter") %></TD></TR>
  <TR>
    <TD><B>Product</B></TD>
    <TD><%# DataBinder.Eval(currentBug,"Product") %></TD></TR>
  <TR>
    <TD><B>Version</B></TD>
    <TD><%# DataBinder.Eval(currentBug,"Version") %></TD></TR>
  <TR>
    <TD><B>Description</B></TD>
    <TD><%# DataBinder.Eval(currentBug,"Description") %></TD></TR>
  <TR>
    <TD><B>Date Created</B></TD>
    <TD><%# DataBinder.Eval(currentBug,"DateCreated") %></TD></TR>
  <TR>
    <TD><B>Severity</B></TD>
    <TD><%# DataBinder.Eval(currentBug,"Severity") %></TD></TR></TABLE> 
</asp:Panel>

  </body>

</html>

The complete source code for the C# version is shown in Example 10-15, and the VB.NET version is in Example 10-16.

Example 10-15. C# code-behind file for paging and sorting
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace DataGridDetailsInPage
{
   public class WebForm1 : System.Web.UI.Page
   {
      protected object currentBug;

      protected System.Web.UI.WebControls.DataGrid 
         dataGrid1;
      protected System.Web.UI.WebControls.Panel 
         BugDetailsPanel;

      public WebForm1(  )
      {
         Page.Init += new System.EventHandler(Page_Init);
      }

      private void Page_Load(
         object sender, System.EventArgs e)
      {
         if (!IsPostBack) 
         {
            BindGrid(  );
            UpdateBugDetails(  );
         }
      }

      private void Page_Init(object sender, EventArgs e)
      {
         InitializeComponent(  );
      }

      // Property: which column is sorted
      protected string SortColumn
      {
         get
         {
            object o = ViewState["SortColumn"];
            if (o != null)
            {
               return (string) o;
            }
            return "Title"; // default
         }
         set
         {
            ViewState["SortColumn"] = value;
         }
      }

      // Property: are we sorting ascending (true)
      // or descending (false)
      protected bool SortAscend
      {
         get
         {
            object o = ViewState["SortAscend"];
            if (o != null)
               return (bool)o;
            return true; // default
         }
         set
         {
            ViewState["SortAscend"] = value;
         }
      }
                  
      // handle new page request
      protected void OnPageIndexChangedHandler(
         Object sender, 
         DataGridPageChangedEventArgs e)
      {
         // set the new index
         dataGrid1.CurrentPageIndex = e.NewPageIndex;

         // rebind the data
         BindGrid(  );
         UpdateBugDetails(  );
      }

      // when a sort field title is clicked
      protected void OnSortCommandHandler(
         Object sender, 
         DataGridSortCommandEventArgs e)
      {
         // find out the current column being sorted
         string currentSortColumn = SortColumn;

         // set the property to the requested column
         SortColumn = e.SortExpression;

         // if the same column is clicked
         // reverse the sort
         if (currentSortColumn == SortColumn)
         {
            SortAscend = !SortAscend;
         }
         else  // otherwise sort ascending
         {
            SortAscend = true;
         }

         // rebind the data (sorted)
         BindGrid(  );
         UpdateBugDetails(  );
      }

      protected void OnItemCreatedEventHandler(
         Object sender, 
         DataGridItemEventArgs e)
      {
         ListItemType itemType = 
            (ListItemType)e.Item.ItemType;

         if (itemType == ListItemType.Header)
         {
            Label sortSymbol = new Label(  );
            sortSymbol.Text = SortAscend ? "5" : "6";
            sortSymbol.Font.Name = "Webdings";
      
            TableCell theCell = null;
            switch (SortColumn)
            {
               case "Title":
                  theCell = e.Item.Cells[1];
                  break;
               case "Reporter":
                  theCell = e.Item.Cells[2];
                  break;
               case "DateCreated":
                  theCell = e.Item.Cells[5];
                  break;
            }
            if (theCell != null)
               theCell.Controls.Add(sortSymbol);
         }

      }

      // the user has selected a row
      protected void OnSelectedIndexChangedHandler(
         Object sender, EventArgs e) 
      {
         UpdateBugDetails(  );
      }

      // If the user has selected a row
      // display the details panel
      private void UpdateBugDetails(  )
      {
         // find out which bug selected
         UpdateSelectedBug(  ); 
 
         // if there is a selected bug
         // display the details
         if (currentBug != null)
         {
            BugDetailsPanel.Visible=true;
            BugDetailsPanel.DataBind(  );
         }
         else
         {
            BugDetailsPanel.Visible=false;
         }
      }

      // compare the selected row with
      // the array list of bugs
      // return the selected bug
      private void UpdateSelectedBug(  )
      {
         int index = dataGrid1.SelectedIndex;
         currentBug = null;
         if (index != -1)
         {
            
            // get the bug id from the data grid
            int bugID =  (int) dataGrid1.DataKeys[index];
            
            // recreate the arraylist from the session state
            ArrayList bugs = (ArrayList) Session["bugList"];

            // find the bug with the selected bug id
            foreach (Bug theBug in bugs)
            {
               if(theBug.BugID == bugID)
                  currentBug = theBug;
            }
            
         }
      }

      // when items are bound to the grid
      // examine them and set high status to red
      protected void OnItemDataBoundEventHandler(
         Object sender, DataGridItemEventArgs e) 
      {

         // Don't bother for header, footer and separator items
         ListItemType itemType = (ListItemType)e.Item.ItemType;
         if (itemType == ListItemType.Header || 
            itemType == ListItemType.Footer || 
            itemType == ListItemType.Separator)
            return;

         // e.Item.DataItem is the data for the item
         Bug bug = (Bug)e.Item.DataItem;

         // check the severity for this item
         // if it is high, set the cell to red
         if (bug.Severity == "High")
         {
            // this would make the entire entry red
            //  e.Item.ForeColor = Color.FromName("red");

            // get just the cell we want
            TableCell severityCell = (TableCell)e.Item.Controls[6];

            // set that cell's forecolor to red
            severityCell.ForeColor = Color.FromName("Red");
         }

      }

      // create the bugs 
      // add them to the array list
      // bind the data grid to the array list
      void BindGrid(  ) 
      {

         DateTime d1 = new DateTime(2002,7,10,13,14,15);
         DateTime d2 = new DateTime(2002,7,4,12,55,03);
         DateTime d3 = new DateTime(2002,8,5,13,12,07);
         DateTime d4 = new DateTime(2002,12,16,12,33,05);
         ArrayList bugs = new ArrayList(  );
         
         bugs.Add(
            new Bug(
            101,
            "Bad Property Value",
            "Jesse Liberty", 
            "XBugs",
            "0.01",
            "Property values incorrect when you enter a new type",
            d1,
            "High")
            );
         
         bugs.Add(
            new Bug(
            102,
            "Doesn't load properly",
            "Dan Hurwitz", 
            "XBugs",
            "0.01",
            "The system fails on load with error x2397",
            d2,
            "High")
            );

         bugs.Add(
            new Bug(
            103,
            "Hangs on exit",
            "Jack Ryan", 
            "XBugs",
            "0.01",
            "When you press close, it hangs",
            d3,
            "High")
            );

         bugs.Add(
            new Bug(
            104,
            "Wrong data",
            "Demetri Karamazov", 
            "XBugs",
            "0.01",
            "The data does not match the DB",
            d4,
            "Medium")
            );
   
         
         Bug.BugComparer c = Bug.GetComparer(  );
         c.WhichField = SortColumn;
         c.Ascending = SortAscend;
         bugs.Sort(c);

         dataGrid1.DataSource=bugs;
         dataGrid1.DataBind(  );
         Session["bugList"] = bugs;

      }

    #region Web Form Designer generated code
      /// <summary>
      /// Required method for Designer support - do not modify
      /// the contents of this method with the code editor.
      /// </summary>
      private void InitializeComponent(  )
      {    
         this.Load += new System.EventHandler(this.Page_Load);

      }
    #endregion
   }

   // The bug class.
   // Implements IComparable for sorting
   // Has nested IComparer class
   public class Bug : IComparable
   {
      private int bugID;
      private string title;
      private string reporter;
      private string product;
      private string version;
      private string description;
      private DateTime dateCreated;
      private string severity;

      public Bug(int id, string title, string reporter,
         string product, string version, 
         string description, DateTime dateCreated, 
         string severity)
      {
         bugID = id;                      
         this.title = title;              
         this.reporter = reporter;        
         this.product = product;          
         this.version = version;
         this.description = description;
         this.dateCreated = dateCreated;
         this.severity = severity;
      }

      // static method returns dedicated IComparer
      public static BugComparer GetComparer(  )
      {
         return new Bug.BugComparer(  );
      }

      // implementing IComparable
      public int CompareTo(Object rhs)
      {
         Bug r = (Bug) rhs;
         return this.title.CompareTo(r.title);
      }

      // dedicated method for BugComparer to use
      public int CompareTo(
         Bug rhs, string field, bool ascending)
      {
         switch (field)
         {
            case "Title":
               if (ascending)
                  return this.title.CompareTo(rhs.title);
               else
               {
                  int retVal = 
                     this.title.CompareTo(rhs.title);
                  switch (retVal)
                  {
                     case 1:
                        return -1;
                     case -1:
                        return 1;
                     default:
                        return 0;
                  }
               }
            case "Reporter":
               if (ascending)
                  return this.Reporter.CompareTo(
                     rhs.Reporter);
               else
               {
                  int retVal = this.Reporter.CompareTo(
                     rhs.Reporter);
                  switch (retVal)
                  {
                     case 1:
                        return -1;
                     case -1:
                        return 1;
                     default:
                        return 0;
                  }
               }

            case "BugID":
               if (this.bugID < rhs.BugID)
                  return ascending ? -1 : 1;
               if (this.bugID > rhs.BugID)
                  return ascending ? 1 : -1;
               return 0;
            case "DateCreated":
               if (this.dateCreated < rhs.dateCreated) 
                  return ascending ? -1 : 1;
               if (this.dateCreated > rhs.dateCreated)
                  return ascending ? 1 : -1;
               return 0;
         }
         return 0;
      }

      // nested specialized IComparer         
      public class BugComparer : IComparer
      {
         public int Compare(object lhs, object rhs)
         {
            Bug l = (Bug) lhs;
            Bug r = (Bug) rhs;
            return l.CompareTo(r,whichField, ascending);
         }

         // Property: which field are we sorting
         public string WhichField
         {
            get
            {
               return whichField;
            }
            set
            {
               whichField=value;
            }
         }

         // Property: Ascending (true) or descending
         public bool Ascending
         {
            get
            {
               return ascending;
            }
            set
            {
               ascending = value;
            }

         }
         private string whichField;
         private bool ascending;
      }     // end nested class

      // Properties for Bugs 
      public int        BugID          { get { return bugID;  }}
      public string     Title          { get { return title;  }}
      public string     Reporter       { get { return reporter; }}
      public string     Product        { get { return product;  }}
      public string     Version        { get { return version;  }}
      public string     Description    { get { return description; }}
      public DateTime   DateCreated    { get { return dateCreated; }}
      public string     Severity       { get { return severity;  }}

   }

}
Example 10-16. VB.NET code-behind file for paging and sorting
Public Class WebForm1
   Inherits System.Web.UI.Page
   Protected WithEvents dataGrid1 As System.Web.UI.WebControls.DataGrid
   Protected BugDetailsPanel As System.Web.UI.WebControls.Panel

#Region " Web Form Designer Generated Code "

   'This call is required by the Web Form Designer.
   <System.Diagnostics.DebuggerStepThrough(  )> Private Sub InitializeComponent(  )

   End Sub

   Private Sub Page_Init(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) _
   Handles MyBase.Init
      InitializeComponent(  )
   End Sub

#End Region
   Private _currentBug As Object

   Private Sub Page_Load( _
   ByVal sender As System.Object, _
   ByVal e As System.EventArgs) _
   Handles MyBase.Load

      If Not IsPostBack Then
         BindGrid(  )
         UpdateBugDetails(  )
      End If
   End Sub

   Private Sub UpdateBugDetails(  )
      UpdateSelectedBug(  )
      If Not _currentBug Is Nothing Then
         BugDetailsPanel.Visible = True
         BugDetailsPanel.DataBind(  )
      Else
         BugDetailsPanel.Visible = False
      End If
   End Sub

   Protected Property SortColumn(  ) As String
      Get
         Dim o As Object
         o = ViewState("SortColumn")
         If Not o Is Nothing Then
            SortColumn = CStr(o)
         End If
      End Get
      Set(ByVal Value As String)
         ViewState("SortColumn") = Value
      End Set
   End Property

   Protected Property SortAscend(  ) As Boolean
      Get
         Dim o As Object
         o = ViewState("SortAscend")
         If Not o Is Nothing Then
            SortAscend = CBool(o)
         End If

      End Get
      Set(ByVal Value As Boolean)
         ViewState("SortAscend") = Value
      End Set
   End Property

   Public ReadOnly Property CurrentBug(  ) As Object
      Get
         CurrentBug = _currentBug
      End Get
   End Property

   Private Sub UpdateSelectedBug(  )
      Dim index As Int32
      index = dataGrid1.SelectedIndex
      _currentBug = Nothing
      If index <> -1 Then
         Dim bugID As Int32
         bugID = dataGrid1.DataKeys(index)
         Dim bugs As ArrayList
         bugs = Session("bugList")
         Dim theBug As Bug
         For Each theBug In bugs
            If theBug.BugID = bugID Then
               _currentBug = theBug
            End If
         Next
      End If
   End Sub

   Public Sub BindGrid(  )
      Dim bugs As New ArrayList(  )
      bugs.Add(New Bug(101, _
         "BadProperty Value", _
         "Jesse Liberty", _
         "XBugs", _
         "0.01", _
         "Property values incorrect", _
         DateTime.Now, _
         "High") _
      )
      bugs.Add( _
         New Bug( _
         102, _
         "Doesn't load properly", _
         "Dan Hurwitz", _
         "XBugs", _
         "0.01", _
         "The system fails with error x2397", _
         DateTime.Now, _
         "Medium") _
      )

      bugs.Add( _
         New Bug( _
         103, _
         "Hangs on exit", _
         "Jack Ryan", _
         "XBugs", _
         "0.01", _
         "When you press close, it hangs", _
         DateTime.Now, _
         "High") _
         )

      bugs.Add( _
         New Bug( _
         104, _
         "Wrong data", _
         "Demetri Karamazov", _
         "XBugs", _
         "0.01", _
         "The data does not match the DB", _
         DateTime.Now, _
         "Medium") _
         )

      Dim c As Bug.BugComparer = Bug.GetComparer(  )
      c.WhichField = SortColumn
      c.Ascending = SortAscend
      bugs.Sort(c)

      dataGrid1.DataSource = bugs
      dataGrid1.DataBind(  )
      Session("BugList") = bugs

   End Sub

   Protected Sub OnItemCreatedEventHandler( _
   ByVal sender As System.Object, _
   ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) 
      Dim itemType As ListItemType
      itemType = e.Item.ItemType

      If itemType = ListItemType.Header Then
         Dim sortSymbol As New Label(  )
         If SortAscend = True Then
            sortSymbol.Text = "5"
         Else
            sortSymbol.Text = "6"
         End If
         sortSymbol.Font.Name = "Webdings"

         Dim theCell As TableCell
         theCell = Nothing
         Select Case SortColumn
            Case "Title"
               theCell = e.Item.Cells(1)
            Case "Reporter"
               theCell = e.Item.Cells(2)
            Case "DateCreated"
               theCell = e.Item.Cells(5)
         End Select
         'If SortColumn = "Title" Then
         '   theCell = e.Item.Cells(1)
         'End If
         'If SortColumn = "Reporter" Then
         '   theCell = e.Item.Cells(2)
         'End If
         'If SortColumn = "DateCreated" Then
         '   theCell = e.Item.Cells(5)
         'End If
         If Not theCell Is Nothing Then
            theCell.Controls.Add(sortSymbol)
         End If

      End If
   End Sub

   Protected Sub OnItemDataBoundEventHandler( _
   ByVal sender As System.Object, _
   ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) 

      Dim itemType As ListItemType
      itemType = e.Item.ItemType

      If itemType = ListItemType.Header Or _
      itemType = ListItemType.Footer Or _
      itemType = ListItemType.Separator Then
         Exit Sub
      End If

      Dim theBug As Bug
      theBug = e.Item.DataItem
      If theBug.Severity = "High" Then
         Dim severityCell As TableCell
         severityCell = e.Item.Controls(6)
         severityCell.ForeColor = Color.FromName("Red")
      End If

      'Dim linkCell As TableCell
      'linkCell = e.Item.Controls(0)
      'Dim h As HyperLink
      'h = linkCell.Controls(0)
      'h.NavigateUrl = "details.aspx?bugID=" & theBug.BugID

   End Sub

   Protected Sub OnPageIndexChangedHandler( _
   ByVal source As Object, _
   ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs)
      dataGrid1.CurrentPageIndex = e.NewPageIndex
      BindGrid(  )
      UpdateBugDetails(  )
   End Sub

   Protected Sub OnSortCommandHandler( _
   ByVal source As Object, _
   ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) _
      Dim currentSortColumn As String = SortColumn
      SortColumn = e.SortExpression
      If currentSortColumn = SortColumn Then
         If SortAscend = True Then
            SortAscend = False
         Else
            SortAscend = True
         End If
      Else
         SortAscend = True
      End If
      BindGrid(  )
      UpdateBugDetails(  )
   End Sub

   Protected Sub OnSelectedIndexChangedHandler( _
     ByVal sender As Object, e As EventArgs)
      UpdateBugDetails(  )
   End Sub
End Class

Public Class Bug : Implements IComparable

   Private _bugID As Int32
   Private _title As String
   Private _reporter As String
   Private _product As String
   Private _version As String
   Private _description As String
   Private _dateCreated As DateTime
   Private _severity As String

   Sub New(ByVal theID As Int32, _
   ByVal theTitle As String, _
   ByVal theReporter As String, _
   ByVal theProduct As String, _
   ByVal theVersion As String, _
   ByVal theDescription As String, _
   ByVal theDateCreated As DateTime, _
   ByVal theSeverity As String)

      _bugID = theID
      _title = theTitle
      _reporter = theReporter
      _product = theProduct
      _version = theVersion
      _description = theDescription
      _dateCreated = theDateCreated
      _severity = theSeverity
   End Sub

   ' nested class
   Public Class BugComparer
      Implements IComparer

      Dim _whichField As String
      Dim _ascending As Boolean

      Public Function Compare( _
      ByVal lhs As Object, ByVal rhs As Object) _
      As Integer _
      Implements IComparer.Compare
         Dim l As Bug
         Dim r As Bug
         l = lhs
         r = rhs
         Compare = l.CompareTo(r, _whichField, _ascending)
      End Function

      Public Property WhichField(  ) As String
         Get
            WhichField = _whichField
         End Get
         Set(ByVal Value As String)
            _whichField = Value
         End Set
      End Property

      Public Property Ascending(  ) As Boolean
         Get
            Ascending = _ascending
         End Get
         Set(ByVal Value As Boolean)
            _ascending = Value
         End Set
      End Property
   End Class   ' end nested class

   Public Shared Function GetComparer(  ) As BugComparer
      GetComparer = New Bug.BugComparer(  )
   End Function

   Public Function CompareTo(ByVal rhs As Object) As Integer _
   Implements IComparable.CompareTo
      Dim r As Bug = rhs
      CompareTo = Me.Title.CompareTo(r.Title)
   End Function

   Public Function CompareTo( _
   ByVal rhs As Bug, _
   ByVal field As String, _
   ByVal ascending As Boolean) As Int32
      CompareTo = 0
      Select Case field
         Case "Title"
            If ascending = True Then
               CompareTo = Me.Title.CompareTo(rhs.Title)
            Else
               Dim retVal As Int32
               retVal = Me.Title.CompareTo(rhs.Title)
               If retVal = 1 Then CompareTo = -1
               If retVal = -1 Then CompareTo = 1
            End If
         Case "Reporter"
            If ascending = True Then
               CompareTo = Me.Reporter.CompareTo(rhs.Reporter)
            Else
               Dim retVal As Int32
               retVal = Me.Title.CompareTo(rhs.Reporter)
               If retVal = 1 Then CompareTo = -1
               If retVal = -1 Then CompareTo = 1
            End If
         Case "BugID"
            If Me.BugID < rhs.BugID Then
               If ascending = True Then
                  CompareTo = -1
               Else
                  CompareTo = 1
               End If
            End If
            If Me.BugID > rhs.BugID Then
               If ascending = True Then
                  CompareTo = 1
               Else
                  CompareTo = -1
               End If
            End If
         Case "DateCreated"
            If Me.DateCreated < rhs.DateCreated Then
               If ascending = True Then
                  CompareTo = -1
               Else
                  CompareTo = 1
               End If
            End If
            If Me.DateCreated > rhs.DateCreated Then
               If ascending = True Then
                  CompareTo = 1
               Else
                  CompareTo = -1
               End If
            End If
      End Select
   End Function

   Public ReadOnly Property BugID(  ) As Int32
      Get
         BugID = _bugID
      End Get
   End Property

   Public ReadOnly Property Title(  ) As String
      Get
         Title = _title
      End Get
   End Property

   Public ReadOnly Property Reporter(  ) As String
      Get
         Reporter = _reporter
      End Get
   End Property

   Public ReadOnly Property Product(  ) As String
      Get
         Product = _product
      End Get
   End Property

   Public ReadOnly Property Version(  ) As String
      Get
         Version = _version
      End Get
   End Property

   Public ReadOnly Property Description(  ) As String
      Get
         Description = _description
      End Get
   End Property

   Public ReadOnly Property DateCreated(  ) As String
      Get
         DateCreated = _dateCreated
      End Get
   End Property

   Public ReadOnly Property Severity(  ) As String
      Get
         Severity = _severity
      End Get
   End Property

End Class

You don't want the panel to be displayed if the user has not requested details on any particular bug. You'll create a method, UpdateBugDetails, that will set the panel's Visible property to false until the user selects a bug. When a bug is selected, UpdateBugDetails will set the panel's Visible property to true, and the panel will appear below the DataGrid. The following code shows the source code for the UpdateBugDetails method:

private void UpdateBugDetails(  )
{
   UpdateSelectedBug(  ); 

   if (currentBug != null)
   {
      BugDetailsPanel.Visible=true;
      BugDetailsPanel.DataBind(  );
   }
   else
   {
      BugDetailsPanel.Visible=false;
   }
}

In VB.NET, the code is:

Private Sub UpdateBugDetails(  )
   UpdateSelectedBug(  )
   If Not _currentBug Is Nothing Then
      BugDetailsPanel.Visible = True
      BugDetailsPanel.DataBind(  )
   Else
      BugDetailsPanel.Visible = False
   End If
End Sub

UpdateBugDetails starts by calling UpdateSelectedBug, whose job is to set the currentBug member variable to the Bug object the user has chosen, or to null if no bug has been chosen.

UpdateBugDetails tests the currentBug and, if it is not null, it displays the details panel and binds the data. The call to the panel's DataBind method causes the panel to evaluate the currentBug properties and display them, as seen earlier.

To set the current bug, UpdateSelectedBug gets the SelectedIndex property from the DataGrid control. This value will be -1 if the user has not selected an item, or it will be the item ID of the selected item. You use that item ID as an index into the DataKeys collection of the DataGrid to extract the BugID of the bug represented by the selected row in the grid.

The DataKeys collection is created by adding a DataKeyField attribute to the DataGrid declaration in your .aspx file:

DataKeyField="BugID"

When the data grid is created and Bug objects are added, the DataGrid creates a DataKeys collection, populating it with the bugID for each bug for each row.

With the bugID, you can iterate over the ArrayList that represents your data and find the matching bug.

The following is the C# source code for the UpdateSelectedBug function:

private void UpdateSelectedBug(  )
{
   int index = dataGrid1.SelectedIndex;
   currentBug = null;
   if (index != -1)
   {
      // get the bug id from the data grid
      int bugID =  (int) dataGrid1.DataKeys[index];
      
      // recreate the arraylist from the session state
      ArrayList bugs = (ArrayList) Session["bugList"];

      // find the bug with the selected bug id
      foreach (Bug theBug in bugs)
      {
         if(theBug.BugID == bugID)
            currentBug = theBug;
      }
   }
}

In VB.NET, the code is:

Private Sub UpdateSelectedBug(  )
   Dim index As Int32
   index = dataGrid1.SelectedIndex
   _currentBug = Nothing
   If index <> -1 Then
      Dim bugID As Int32
      bugID = dataGrid1.DataKeys(index)
      Dim bugs As ArrayList
      bugs = Session("bugList")
      Dim theBug As Bug
      For Each theBug In bugs
         If theBug.BugID = bugID Then
            _currentBug = theBug
         End If
      Next
   End If
End Sub

You also need to add the currentBug field to your class, along with its property:

private object currentBug;
public object CurrentBug { get { return currentBug;}}

In VB.NET, the equivalent is:

Private _currentBug As Object
Public ReadOnly Property CurrentBug(  ) As Object
   Get
      CurrentBug = _currentBug
   End Get
End Property

Now that you can display the details of the bug on the same page as the data grid, let's take a look at how you sort the columns in the grid. To start, you must add a couple of attributes to the data grid itself:

AllowSorting="True"
OnSortCommand="OnSortCommandHandler"

The first tells the DataGrid to allow columns to be sorted; the second creates an event handler for the Sort command event. The Sort command event is fired by the user clicking on the header of a sortable column. You mark a column as sortable by adding a few attributes to the BoundColumn tag:

<asp:BoundColumn DataField="Title" 
HeaderText="Title" 
SortExpression="Title"
/>

HeaderText sets (or gets) the text displayed in the column header. DataField, as seen earlier, gets or sets the field in the data item to which this column will be bound.

SortExpression sets (or gets) the field to pass to the OnSortCommand method when a column is selected. By setting the SortExpression, the DataGrid knows to display the header as a link. Clicking on the link fires the SortCommand event, passing in the designated field.

10.2.4.2 Implementing the OnSortCommand event handler

The OnSortCommand event handler must evaluate whether the user has clicked on the currently selected column or another column. When a user clicks on a column, the items in that column are sorted. If the user clicks on the currently selected column, however, then the column is sorted in reverse order. That is, if you click on Title, the titles are sorted alphabetically in ascending order, but if you click on Title again, then the titles are sorted in descending order.

To manage this, you will create a property of the form named SortColumn, which will be responsible for knowing the currently selected column. This property will need to store the selection in view state so that the current selection will survive a round trip to the server. The C# code for the SortColumn property is this:

protected string SortColumn
{
   get
   {
      object o = ViewState["SortColumn"];
      if (o != null)
      {
         return (string) o;
      }
         return "Title"; // default
   }
   set
   {
      ViewState["SortColumn"] = value;
   }
}

The VB.NET version is this:

Protected Property SortColumn(  ) As String
    Get
       Dim o As Object
       o = ViewState("SortColumn")
       If Not o Is Nothing Then
          SortColumn = CStr(o)
       End If
    End Get
    Set(ByVal Value As String)
       ViewState("SortColumn") = Value
    End Set
 End Property

The logic of this property's Get method is to retrieve the value from view state. View state returns an object, as explained in Chapter 4. If that object is not null, you cast it to a string and return the string as the property; otherwise, you return Title as a default value for the property. The Set method adds the value to view state.

While you are at it, you'll create a second property, SortAscend, which will mark whether the current sort is in ascending order (SortAscend == true) or in descending order (SortAscend == false). The C# code for the SortAscent property is as follows:

protected bool SortAscend
{
   get
   {
      object o = ViewState["SortAscend"];
      if (o != null)
         return (bool)o;
      return true; // default
   }
   set
   {
      ViewState["SortAscend"] = value;
   }
}

In VB.NET, the code is as follows:

Protected Property SortAscend(  ) As Boolean
   Get
      Dim o As Object
      o = ViewState("SortAscend")
      If Not o Is Nothing Then
         SortAscend = CBool(o)
      End If

   End Get
   Set(ByVal Value As Boolean)
      ViewState("SortAscend") = Value
   End Set
End Property

The logic is nearly identical: you attempt to get the current value from the ViewState. If no value is in view state, the object will be null and you return true; otherwise, you return the current value. The set logic is just to stash away the value assigned in the view state.

In the OnSortCommand event handler (dataGrid1_SortCommand in VB.NET), you first set a temporary string variable to the SortColumn property. You then set the SortColumn property to the value passed in via the DataGridSortCommandEventArgs object, which in C# is done as follows:

string currentSortColumn = SortColumn;
SortColumn = e.SortExpression;

In VB.NET, you write:

Dim currentSortColumn As String = SortColumn
SortColumn = e.SortExpression

You can now compare the current sort column value with the new sort column value, and if they are the same, you set the SortAscend property to the reverse of its current value; otherwise, you will sort the new column in ascending order. This is shown in the following C# code fragment:

if (currentSortColumn == SortColumn)
{
   SortAscend = !SortAscend;
}
else  // otherwise sort ascending
{
   SortAscend = true;
}

In VB.NET, the code is:

If currentSortColumn = SortColumn Then
   SortAscend = Not SortAscend
Else
   SortAscend = True
End If

You are now ready to bind the DataGrid and update the details panel, as the following code shows:

BindGrid(  );
UpdateBugDetails(  );

Clearly something else must be going on. You've marked a couple of properties, but where did you actually sort the grid? You haven't yet; that work is delegated to the BindGrid method.

Inside BindGrid, just before you set the data source, you'll want to sort the array list. ArrayList implements the Sort method, but it wants you to pass in an object implementing IComparer. You will extend your Bug class to implement IComparable, and also to nest a class, BugComparer, which implements the IComparer class, as explained in the sidebar. You can then instantiate the BugComparer class, set its properties to sort on the appropriate field, and invoke Sort on the ArrayList object, passing in the BugComparer object:

Bug.BugComparer c = Bug.GetComparer(  );
c.WhichField = SortColumn;
c.Ascending = SortAscend;
bugs.Sort(c);

In VB.NET, you'd write:

Dim c As Bug.BugComparer = Bug.GetComparer(  )
c.WhichField = SortColumn
c.Ascending = SortAscend
bugs.Sort(c)

With the ArrayList object sorted, you are ready to set the DataSource for the DataGrid and to calltheDataBind method.

10.2.4.3 Adding a sort symbol

You may want to add a visible indication of the direction of the sort, as shown in Figure 10-5 and Figure 10-6. Clicking on the column not only reverses the sort, it changes the symbol, as shown in Figure 10-6.

Figure 10-5. The Title column when sorted in ascending order
figs/pan2_1005.gif
Figure 10-6. The Title column when sorted in descending order
figs/pan2_1006.gif

To accomplish this, you will implement the OnItemCreatedEventHandler, as you have in the past. This time, you will check to see if you are creating the header. If so, you will put this widget into the correct column, determined by checking the value of the SortColumn property.

You start by creating the label to add to the cell:

if (itemType == ListItemType.Header)
{
   Label sortSymbol = new Label(  );

The VB.NET equivalent is:

If itemType = ListItemType.Header Then
   Dim sortSymbol As New Label(  )

The text to add to this label is the symbol itself, which is a Webding text symbol with the value of either 5 or 6 for ascending and descending, respectively:

sortSymbol.Text = SortAscend ? "5" : "6";
sortSymbol.Font.Name = "Webdings";

In VB.NET, the code is:

If SortAscend = True Then
   sortSymbol.Text = "5"
Else
   sortSymbol.Text = "6"
End If
sortSymbol.Font.Name = "Webdings"

You will add the label to the appropriate cell. To do so, you create an instance of a TableCell object:

TableCell theCell = null;

In VB.NET, the equivalent is:

Dim theCell As TableCell
theCell = Nothing

You will assign the correct cell to that variable, based on the value of the SortColumn property:

switch (SortColumn)
{
   case "Title":
      theCell = e.Item.Cells[1];
      break;
   case "Reporter":
      theCell = e.Item.Cells[2];
      break;
   case "DateCreated":
      theCell = e.Item.Cells[5];
      break;
}

In VB.NET, use:

Select Case SortColumn
   Case "Title"
      theCell = e.Item.Cells(1)
   Case "Reporter"
      theCell = e.Item.Cells(2)
   Case "DateCreated"
      theCell = e.Item.Cells(5)
End Select

You must then test that you have a valid cell, and if so, add the label to that cell:

if (theCell != null)
   theCell.Controls.Add(sortSymbol);

In VB.NET, the code is:

If Not theCell Is Nothing Then
   theCell.Controls.Add(sortSymbol)
End If

As you will remember, each cell has a Controls collection. You don't care what is already in that collection (presumably, it contains the label for the header). You will simply add your label to the collection. When the cell is displayed the current contents are displayed and then your label is displayed.

10.2.4.4 Implementing paging

While the current version of this program uses an array list of Bug objects, a typical program will draw the objects from a database. It is possible that you may have a large number of Bug reports. (You, of course, will never have a large number of bugs, but other, lowly, careless programmers may have a large number of bugs, and we explain this for their sake.)

Rather than filling the data grid with tens of thousands of bug reports (can you think of anything more depressing?), you'll want to add paging so that you are only forced to confront a limited number of bugs at any one time. To accomplish this, you add yet a few more attributes to your DataGrid declaration:

OnPageIndexChanged ="OnPageIndexChangedHandler" 
AllowPaging="True" 
PageSize ="2"

The OnPageIndexChanged attribute assigns the event handler to be called when the user clicks on the page navigation links. The AllowPaging attribute turns paging on, and the PageSize attribute sets the maximum number of items to be displayed in any single page. Because we have very few items in the array list, you'll set this to "2," although "10" is a more realistic real-world number.

You will add a new element to the DataGrid control: the PagerStyle element, which determines the style of paging the DataGrid will provide. Attributes to the PagerStyle element determine the alignment of the page navigation links and the mode. Two modes are supported: NextPrev, which provides two links, < to navigate backwards, and > to navigate forward; and NumericPages, which provides numeric values for each page. If you choose NumericPages, you'll want to add another attribute, PageButtonCount, which determines the maximum number of paging buttons to appear on the grid at one time.

You will remember that we were previously filling the footer with the number of bugs found. You'll need to remove that code so that you can now fill the footer with the page navigation features.

10.2.4.5 Handling the event for page navigation

Each time the user clicks on the page navigation links, a PageIndexChanged event is raised, which is caught by your handler. The event handler is passed two arguments. The second is a DataGridPageChangedEventArgs object, which contains a NewPageIndex property that is the index of the selected page. You assign that value to the DataGrid object's CurrentPageIndex property and then redraw the page. The data grid takes care of the work of finding the right objects to display. The code for the OnPageIndexChangedHandler is the following:

public void OnPageIndexChangedHandler(
   Object sender, DataGridPageChangedEventArgs e)
{
   // set the new index
   dataGrid1.CurrentPageIndex = e.NewPageIndex;

   // rebind the data
   BindGrid(  );
   UpdateBugDetails(  );
}

In VB.NET, you'd write:

Protected Sub OnPageIndexChangedHandler( _
 ByVal source As Object, _
 ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) _
    dataGrid1.CurrentPageIndex = e.NewPageIndex
    BindGrid(  )
    UpdateBugDetails(  )
 End Sub

The data grid uses the index value as an index into your complete set of data items (bugs, in the case of our example). For this to work, your data source must provide all the items, even though you will only display one page worth.

To avoid this, you can take explicit control over the page display by setting the DataGrid object's AllowCustomPaging property to true (the default is false). With this set, you are responsible for telling the data grid the total number of values in your data source, which you do by setting the VirtualItemCount property. The advantage of custom paging is that you can minimize the number of values you retrieve in each query; you can get just the values for a single page.

    Previous Section Next Section


    JavaScript Editor Javascript validator     Javascripts 




    ©