Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Practical Guide to .NET DataTables, DataSets and DataGrids - Part 4

0.00/5 (No votes)
23 Feb 2004 5  
The purpose of this document is to provide a practical guide to using Microsoft�s .NET DataTables, DataSets and DataGrid

5 Data Grid

A DataGrid is used to display an independent Table or a collection of Tables contained in a DataSet. It also provides a UI for editing, deleting and inserting records along with a set of event notifications for programmatic response and for data change tracking. In addition it supports a collection of DataGrid Table Styles that provides custom presentation for each table in its DataSource. WebForms and WinForms can both display a DataGrid bound and unbound to a database; however, there are significant differences in usage.

This section will present how to work with a DataGrid and its many features and different use models.

5.1 Methods and Properties

Methods

Description

Collapse

Collapse a specified row or all rows.

Expand

Expand a specified row or all rows

IsExpanded

Returns true if a specified row is expanded otherwise false.

NavigateBack

Navigates back to the previously displayed table in the grid.

NavigateTo

Navigate to a table in the grid.

SetDataBinding

Binds a dataset to the DataSource and selects a table.

Select

This method selects row and highlights it. It is not necessarily the same as the current row.

UnSelect

This method unselects a row and turns of highlighting. It does not change the current row.

Properties

Description

AllowSorting

Set to True allows column sorting using the column headers.

AllowNavigation

Allows navigation within the data grid using for example the arrow or tab keys.

AlternatingBackColor

This sets a background color for alternating rows in the DataGrid making it easier to read across a row.

DataSource

This returns or sets the source of data for the DataGrid. It is either a DataSet or a DataTable.

DataMember

This sets the table to be displayed. When the DataSource is a DataSet the DataGrid display is in a hierarchical mode, setting this to one of the Tables in the DataSet automatically forces it to be displayed.

CurrentCell

Gets or Sets the currently active cell including the CurrentRowIndex

CurrentRowIndex

Gets or Sets the current row

5.2 Assigning Data Sources

The DataSource for a DataGrid is either a Table or a DataSet. For example, a previously created DataTable dt object is assigned to the DataGrid as using the DataGrid�s DataSource member follows:

dg.DataSource = dt; // DataSource contains only a

// DataTable object

A DataSet that contains multiple tables in its table�s collection can be assigned using the DataGrid�s DataSource member as follows::

DataSet ds = new DataSet();
ds.Tables.Add(dt1);
ds.Tables.Add(dt2);
...
ds.Tables.Add(dtn);

dg.DataSource = ds; // DataSource contains a DataSet object

By default the first table in the collection is displayed in the DataGrid, but a particular table can be selected using the DataGrid�s DataMember property as follows:

dg.DataMember = dt2.TableName;

or equivalently:

dg.DataMember = ds.Tables[1].TableName; // zero-based index

or a DataSet can be assigned and a Table selected at the same time using the SetDataBinding() method as follows:

dg.SetDataBinding(ds, dt2.TableName);

Note : The only restriction for DataSets is that tables must have a primary key, if not then an error will occur at the time of assigning the DataSet to the DataSource.

5.3 Formatting

5.3.1 DataGridTableStyle - WinForms

A DataTable is used to hold a collection of Column definitions and a collection of rows containing data for each column. A DataSet contains a collection of Tables and a DataGrid provides the interactive UI for the presentation of a table from a DataTable or from one contained in a DataSet.

The rendering of each table being managed by the DataGrid is carried out through the collections of individual table styles (DataGridTableStyle) where each table style contains a collection of column styles. Refer to Figure 1 for a pictorial view of these relationships.

Microsoft�s .NET IDE provides DataGridTextBoxColumn or DataGridBoolColumn objects that are as there name implies a TextBox and Boolean or CheckBox column respectively. The TextBox column is the default column type used when declaring a DataGrid.

Unique rendering for each column is provided through the column style properties and methods. In addition it is possible to modify the .NET provided column styles or define new column styles such as ComboBoxes and ImageControls that are inherited from the base classes.

Note: DataGridTableStyle is not available for WebForms instead one must use itemstyles � see WebForm example.

In the following example assume the following:

A DataTable dt is defined

  • A DataSet ds is defined and contains dt at index equal 0 in its Tables collection
  • A DataGrid dg is defined with DataSource containing ds

Initially the DataGrid dg does not have any TableStyles contained in the TableStyles collection, which can be seen through the Count property value:

dg.TableStyles.Count;

Also, the dg.Controls collection only contains Vertical and Horizontal Scrollbar controls.

// First, a DataGridTableStyle object is declared

// that will hold a collection of

// GridColumnStyles.

System.Windows.Forms.DataGridTableStyle DGStyle =
   new DataGridTableStyle();
// In this example the .NET DataGridTextBoxColumn class is used.

DataGridTextBoxColumn textColumn;
// Loop through each Column in table dt to get a DataColumn

// object that will be used

// to define properties for its TextBoxColumn style.

foreach (DataColumn dc in dt.Columns)
{
  textColumn = new DataGridTextBoxColumn();
  // the MappingName must correspond to the Table Column Name

  // in order to establish the relationship between them

  textColumn.MappingName = dc.ColumnName;
  // the HeaderText value is displayed in Header for the column, here

  // the Caption value is used.

  textColumn.HeaderText = dc.Caption;
  // specify some other property values

  textColumn.Width = 200;
  textColumn.Alignment = System.Windows.Forms.HorizontalAlignment.Left ;
  textColumn.ReadOnly = true;
  // Add this column object with its specifications to the

  // GridColumnStyles collection

  DGStyle.GridColumnStyles.Add(textColumn);
}
// The name of the DataGridTableStyle must match that of the table

// Since the DataGrid can contain multiple tables,

// similar the TableStyles collection

// can contain multiple DataGridTableStyles, one for each table.

DGStyle.MappingName = ds.Tables[0].TableName;
DGStyle.AlternatingBackColor = Color.Gainsboro;
DGStyle.AllowSorting = false;
DGStyle.ReadOnly = true;
// The Clear() method is called to ensure that

// the previous style is removed.

dg.TableStyles.Clear();
// Add the new DataGridTableStyle collection to

// the TableStyles collection

dg.TableStyles.Add(DGStyle);

5.3.2 Change only one table�s DataGridTableStyles

The following shows how to change only one Table�s DataGridTableStyles. Assume the table�s style is contained in the TableStyles collection at index equal 0

// Clear only a single table of its GridColumnStyles

dgConversionTable.TableStyles[0].GridColumnStyles.Clear();
DataGridTextBoxColumn textColumn;
// Loop through each Column in table dt to get a

// DataColumn object that will be used

// to define properties for its TextBoxColumn style.

foreach (DataColumn dc in dt.Columns)
{
  textColumn = new DataGridTextBoxColumn();
  // the MappingName must correspond to the Table Column Name

  // in order to establish the relationship between them

  textColumn.MappingName = dc.ColumnName;
  // the HeaderText value is displayed in Header for the column, here

  // the Caption value is used.

  textColumn.HeaderText = dc.Caption;
  // specify some other property values

  textColumn.Width = 200;
  textColumn.Alignment = System.Windows.Forms.HorizontalAlignment.Left ;
  textColumn.ReadOnly = true;
  // Add this column object with its specifications to

  // the GridColumnStyles collection

  // for TableStyles[0]

  dgConversionTable.TableStyles[0].GridColumnStyles.Add(textColumn);
}

5.3.3 Change a single column in a Table�s DataGridTableStyle

One or more columns in an existing Table�s DataGridTableStyles collection can be changed using code to similar to the following. In this example, the first method uses the HeaderText to find the column that is to be changed in a table that is named �Demo�. Once the desired object is obtained, the current HeaderText value for the TextBoxColumn �old header text� is changed to �new header text�. Also, the Width of the column is changed to 150. In the second example the Column MappingName must be used instead of the HeaderText if they are different.

5.3.3.1 Method 1 � for-loop through collection

In this example

GridColumnStylesCollection gcsColl =
  dgConversionTable.TableStyles[�Demo�].GridColumnStyles;
for (int i=0; i< gcsColl.Count; i++)
{
  if(gcsColl[i].GetType() == typeof(DataGridTextBoxColumn))
  {
   DataGridTextBoxColumn textColumn = (DataGridTextBoxColumn)gcsColl[i];
   If (textColumn.HeaderText == �old header text�)
   {
     textColumn.Width = 150;
     textColumn.HeaderText = �new header text�;
     break;
   }
  }
}

5.3.3.2 Method 2 � Direct access using column string name

This method can be used if you only want to change a specific column. Be sure to use the column MappingName and not its Caption or HeaderText if they are different.

GridColumnStylesCollection gcsColl =
   dgConversionTable.TableStyles[�Demo�].GridColumnStyles;
if(gcsColl.Contains("mappingName"))
if(gcsColl["mappingName "].GetType() == typeof(DataGridTextBoxColumn))
{
  DataGridTextBoxColumn textColumn =
  (DataGridTextBoxColumn)gcsColl["mappingName"];
  textColumn.Width = 150;
  textColumn.HeaderText = �new header text�;
}

5.3.4 Create a DataGridTableStyle for Tables Based upon its Data

Each Table within a DataSet can have its own presentation and property style. The formatDG method below provides an example of how to programmatically define a style for each table based upon the data type for each column. It enforces these business rules:

Width of a column is greater than whichever is larger - the width of the ColumnName text string or the maximum of the string length of the content of each cell in that column. The maximum width is set to the width of 100 characters. The letter �W� is added to each string for padding since it is usually the widest character in the alphabet.

  • Columns of DataType System.String or System.Guid have Column alignment = HorizontalAlignment.Left and Readonly= false
  • Columns of DataType of System.DateTime or typeof(int) have Column Alignment = HorizontalAlignment.Center and Readonly= false
  • Columns of DateType System.Double, System.Float or System.Decimal have Column Alignment = HorizontalAlignment.Rignt and Readonly= false and a format of �0.00�
  • The NullText property is set to string.empty. That is whenever a field has a null value then no text is displayed. This is only true for text columns and not for Boolean columns.
  • Style Column MappingName is the column�s ColumnName that matches the name of the field in the corresponding database table while its HeaderText is set to the more readable string contained in the column�s Caption property.
  • The name of the DataGridTableStyle is the same name as for the table. That is, both the DataSet Table and DataGridTableStyle collections are in sync through the use of the table name, which means that when the DataGrid displays the Table it will use the corresponding TableStyle.
public void formatDG(DataGrid dg)
{
  // Assume DataGrid DataSource is a DataSet that

  // contains at least one table.

  if (dg.DataSource.GetType() == typeof(DataSet))
    {
    DataSet ds = (DataSet)dg.DataSource;
    if (ds != null)
    {
     if(dg.TableStyles.Count > 0)
      dg.TableStyles.Clear();
     float prevSumWidths = 0.0f;
     int maxHeight = 0;
     int calcH = 0;
     foreach (DataTable dt in ds.Tables)
     {
       // DataTable dt = ds.Tables[tableName];

       DataGridTableStyle DGStyle = new DataGridTableStyle();
       DGStyle.MappingName = dt.TableName;
       DGStyle.AllowSorting = false;
       DGStyle.AlternatingBackColor = Color.Gainsboro;
       DataGridTextBoxColumn textColumn;
       int nbrColumns = dt.Columns.Count;
       DataRow dr;
       ArrayList cWidths = new ArrayList();
       Graphics g = dg.CreateGraphics();
       Font f = dg.Font;
       SizeF sf;
       // get widths for each column header

       foreach (DataColumn c in dt.Columns)
       {
         // "W"is used as padding

         sf = g.MeasureString("W"+ c.Caption, f);
         cWidths.Add(sf.Width);
       }
       // loop through each row comparing against initial column

       // width and then against each new maximum width based upon

       // data contained in each row for that column.

       string strTemp;
       for(int i=0; i < dt.Rows.Count; i++)
       {
        dr = dt.Rows[i];
        for (int j=0; j < nbrColumns; j++)
       {
       if(!Convert.IsDBNull( dr[j]))
       {
        strTemp = dr[j].ToString();
        // careful strings can get

        // very long, limit it to 100 characters

        if(dr[j].GetType() ==
        typeof(System.String) && strTemp.Length > 100)
         sf = g.MeasureString("W"+
         strTemp.Substring(0,100), f);
       else
         sf = g.MeasureString("W"+ strTemp, f);
       if(sf.Width > (float)cWidths[j])
       cWidths[j] = sf.Width;
      }
    }
  }
  // set each column with its determined width

  // set alignment for each column

  // set format for decimal numbers

  float sumWidths = 0.0f;
  foreach (DataColumn c in dt.Columns)
   {
    if(c.DataType == typeof(bool))
    {
       DataGridBoolColumn boolColumn =
       new DataGridBoolColumn();
       boolColumn.MappingName = c.ColumnName;
       boolColumn.HeaderText = ""+ c.Caption;
       boolColumn.Width = 50;
       boolColumn.ReadOnly = false;
       boolColumn.Alignment = HorizontalAlignment.Center ;
       DGStyle.GridColumnStyles.Add(boolColumn);
       sumWidths += boolColumn.Width;
    }
   else
  {
    textColumn= new DataGridTextBoxColumn();
    textColumn.MappingName = c.ColumnName;
    textColumn.HeaderText = ""+ c.Caption;
    textColumn.ReadOnly = false;
    textColumn.NullText = string.empty;
    if(c.DataType == typeof(System.Guid) ||
       c.DataType == typeof(System.String))
    {
       textColumn.Alignment = HorizontalAlignment.Left ;
    }
    else
    if(c.DataType == typeof(System.DateTime))
       textColumn.Alignment =HorizontalAlignment.Center ;
    else
       if(c.DataType == typeof(int))
       {
        textColumn.Alignment =HorizontalAlignment.Center ;
       }
    else
    {
      textColumn.Alignment = HorizontalAlignment.Right ;
      if(c.DataType == typeof(System.Double)
      || c.DataType ==typeof(System.Decimal)
      || c.DataType == typeof(float))
      textColumn.Format = "0.00";
    }
    textColumn.Width = (int)(float)cWidths[c.Ordinal];
    DGStyle.GridColumnStyles.Add(textColumn);
    sumWidths += textColumn.Width;
    if (maxHeight < textColumn.TextBox.Height)
       maxHeight = textColumn.TextBox.Height;
    }
  }
  dg.TableStyles.Add(DGStyle);
  // Adjust width of DataGrid to match calculated widths

  // select maximum width of all tables in the dataset

  // Adjust width to fit inside the parent container�s width

  if (prevSumWidths < sumWidths)
  {
    prevSumWidths = sumWidths;
    prevSumWidths += dg.RowHeaderWidth;
    if (dg.VisibleRowCount < dt.Rows.Count)
       prevSumWidths += 16; // add width of scrollbar

    // check to see if it is greater than the width of its parent

    // e.g. a panel or tabpage control.

    if (prevSumWidths > (float)dg.Parent.Width)
    // allow room for the scroll bar and provide a little padding.

    prevSumWidths = (float)dg.Parent.Width - 16 - 5;
  }
  // Adjust height of DataGrid based upon row visiblity

  if (dg.VisibleRowCount >= dt.Rows.Count)
  {
    calcH = (dg.VisibleRowCount)*maxHeight+2*dg.PreferredRowHeight+16;
  }
  else
  {
   calcH = (dt.Rows.Count)*maxHeight+2*dg.PreferredRowHeight+16;
   // make a correction since all rows will now be visible

   if (calcH < dg.Parent.Height)
     prevSumWidths -= 16;
   }
  if (calcH >=dg.Parent.Height)
  {
    calcH = dg.Parent.Height � dg.Top - 25; // some arbitrary value

  }
 }
 dg.Size = new Size((int)prevSumWidths,calcH);
 }
}

5.3.5 Defining DataGridTableStyles through Visual Studio .NET IDE

In the WinForm UI editor where a DataGrid control is placed onto the Form, the DataGridTableStyle�s collection property can be selected that will bring up the DataGridTableStyle Collection Editor dialog box shown in Figure 3. Clicking on the Add button will create a new DataGridStyle that can be named. Shown in member�s section of the dialog box are two styles names DGStyleElements and DGStyleIsotopes where the property values for the former are shown. Note that DGStyleElements has a MappingName of Elements and whenever the DataGrid MappingName is also Elements then this style will be used to render the DataTable with the same name.

Clicking on the GridColumnStyles collection property (indicated by Red Arrow) launches the DataGridColumnStyle Collection Editor dialog shown in Figure 4. In the member�s section is a list of column styles, one for each column that is in the Elements Table. Shown in the dialog are the property values for the TextBoxColumn style that is linked to the Atomic Number column in the Elements Table through the MappingName property.

The two dialog boxes in Figures 3 and 4 clearly show the DataGrid style collections of collections model that the DataGrid uses when rendering tables.

Figure 3 DataGridTableStyle Collection Editor Dialog Box

Figure 4 DataGridColumnStyle Collection Editor Dialog Box

5.4 Navigation

5.4.1 CurrencyManager

The CurrencyManager is especially useful when the DataSet contains linked tables and there is a need to make sure records are being added correctly or in navigating between them. The CurrencyManager�s Position property contains the zero-based index row number of the currently selected row of the table that it is bound to.

In a linked system such as the Element -> Isotope table relationship and the DataGrid DataMember property is set to the Isotope table, then the DataGrid CurrentRowIndex reflects the currently selected row in the Isotope table and the only way to know the row index in the Element table to which it is linked can only be determined by using the CurrencyManger for the Elements table.

The CurrencyManager can also be used to move to different rows in its bound table by using the Count property in conjunction with changing the value of the Position property; thus MoveNext, MovePrev, MoveFirst and MoveLast navigation methods can be created. Also when the position is changed the CurrencyManager�s CurrentChanged and PositionChanged events can be monitored.

For example a CurrencyManger object with event handling can be declared as follows:

private CurrencyManger cmElements;
cmElements = (CurrencyManager)this.BindingContext[
  dg.DataSource, "Elements"];
cmElements.CurrentChanged += new System.EventHandler(
  this.cmElements_CurrentChanged);
cmElements.PositionChanged += new System.EventHandler(
  this.cmElements_PositionChanged);

5.4.2 CurrentCell

The DataGridCell class constructor can be used to change the currently selected cell and row in the table currently active as specified by dg.DataMember. It also has two members that can be used to independently get or set the current table column and row numbers. For example:

dg.CurrentCell = new DataGridCell(row, column);

row = dg.CurrentCell.RowNumber;

column = dg.CurrentCell.ColumnNumber;

5.4.3 Selecting Rows

When coordinating a DataGrid with other types of UI components the CurrentRowIndex, Select and UnSelect methods need to be called.

Method

Action

dg.CurrentRowIndex=newRow;

Sets or Gets the new current row and moves the cursor indicator to that row. Refer to figure 3.

dg.Select(newRow);

Select() highlights the NewRow. Refer to figure 3.

dg.UnSelect(previousRow);

UnSelect() un-highlights the previousRow

5.4.4 Expand and Collapse linked tables

Figures 5 to 7 show tables in different states after calling the Collapse and Expand methods.

Figure 5 dg.Collapse(-1); // Collapses all rows

Figure 6 dg.Expand(-1); // Expands all rows

Figure 7 dg.Expand(1); // Expands only row 2 (zero based index)

Figure 8 illustrates the result of using the DataGrid NavigateTo(arg1, arg1) method where arg1 is an integer row index in the primary table and arg2 is a string specifying the name of the table to Navigate to. In this example, Mercury is at row index = 79 in the Elements table and isotopes are contained in the link table �Isotopes� through the Atomic Number primary key-foreign key relationship.

Figure 8 dg.NavigateTo(79, �Isotopes�);

5.5 Copy DataGrid to the Clipboard

The following sections contain Copy to Clipboard routines that access all tables or a specified table from a DataGrid and then they call a routine (TableToString) that formats table data into a string, which is copied to the Clipboard. Once the clipboard contains this string it can be pasted into Excel or into any other application that accepts text data. For example, in Excel or Winword the string data will be parsed back into tables.

Each of the methods checks the DataGrid data source member to determine whether it is a DataSet containing a collection of Tables or whether it is simply a Table Object.

5.5.1 Copy selected table in DataGrid to clipboard

public void CopyDGtoClipboard(DataGrid dg, string tableName)
{
  if (dg.DataSource != null)
  {
    DataTable dt = null;
    if (dg.DataSource.GetType() == typeof(DataSet))
    {
      DataSet ds = (DataSet)dg.DataSource;
      // need to use tableName when DataSet contains more than

      // one table

      if (ds.Tables.Contains(tableName))
      dt = ds.Tables[tableName];
    }
    else
    if (dg.DataSource.GetType() == typeof(DataTable))
    {
      dt = (DataTable)dg.DataSource;
      if (dt.TableName != tableName)
      {
       dt.Clear();
       dt = null;
    }
  }
  if (dt != null)
  Clipboard.SetDataObject(TableToString (dt), true );
 }
}

5.5.2 Copy all tables in DataGrid to clipboard

public void CopyDGtoClipboard(DataGrid dg)
{
  if (dg.DataSource != null)
  {
   if (dg.DataSource.GetType() == typeof(DataSet))
   {
    DataSet ds = (DataSet)dg.DataSource;
    if (ds.Tables.Count > 0)
    {
     string strTables = string.Empty;
     foreach (DataTable dt in ds.Tables)
     {
        strTables += TableToString (dt);
        strTables += "\r\n\r\n";
     }
    if (strTables != string.Empty)
     Clipboard.SetDataObject(strTables, true );
    }
  }
   else
    if (dg.DataSource.GetType() == typeof(DataTable))
    {
      DataTable dt = (DataTable)dg.DataSource;
      if (dt != null )
         Clipboard.SetDataObject(TableToString(dt),
         true );
    }
  }  
}

5.5.3 Format table data into a string

This method returns a string containing the data in a DataTable object. The first line contains the name of the table, the second line contains the name of each column separated by a tab character and the remaining lines, one for each row in the table, contains the corresponding column data separated by a tab character.

private string TableToString(DataTable dt)
{
  string strData = dt.TableName + "\r\n";
  string sep = string.Empty;
  if (dt.Rows.Count > 0)
  {
    foreach (DataColumn c in dt.Columns)
    {
      if(c.DataType != typeof(System.Guid) &&
      c.DataType != typeof(System.Byte[]))
      {
        strData += sep + c.ColumnName;
        sep = "\t";
      }
    }
    strData += "\r\n";
    foreach(DataRow r in dt.Rows)
    {
      sep = string.Empty;
      foreach(DataColumn c in dt.Columns)
      {
        if(c.DataType != typeof(System.Guid) &&
        c.DataType != typeof(System.Byte[]))
        {
          if(!Convert.IsDBNull(r[c.ColumnName]))
          strData += sep +
          r[c.ColumnName].ToString();
        else
          strData += sep + "";
          sep = "\t";
        }
      }
      strData += "\r\n";
    }
  }
  else
    strData += "\r\n---> Table was empty!";
return strData;
}

5.6 Exporting to a Tabbed delimited Text File

The following two methods tabTextFile and SaveDataGridData along with the TableToString method defined in an another section can be used to save all data contained in the DataGrid to a tab delimited text file specified through a SaveAs Dialog box.

public void tabTextFile(DataGrid dg)
{
  OpenFileDialog openFileDialog1 = new OpenFileDialog();
  openFileDialog1.InitialDirectory = "file:///c://">c:\\";

  openFileDialog1.Filter =
    "Text Files (*.txt)|*.txt| All files (*.*)|*.*";
  openFileDialog1.RestoreDirectory = true ;
  openFileDialog1.Title="Export DataGrid to Text File";
  System.Windows.Forms.DialogResult res = openFileDialog1.ShowDialog();
  if(res == DialogResult.OK)
  {
    DataSet ds = new DataSet();
    DataTable dtSource = null;
    DataTable dt = new DataTable();
    DataRow dr;
    if(dg.DataSource != null)
    {
    if (dg.DataSource.GetType() == typeof(DataSet))
    {
      DataSet dsSource = (DataSet)dg.DataSource;
      // assume DataSet contains desired table at index 0

      if (dsSource.Tables.Count > 0)
      {
        string strTables = string.Empty;
        foreach (DataTable dt in dsSource.Tables)
        {
          strTables += TableToString (dt);
          strTables += "\r\n\r\n";
        }
      if (strTables != string.Empty)
      SaveDataGridData (strTables,openFileDialog1.FileName);
      }
    }
    else
    {
        if (dg.DataSource.GetType() == typeof(DataTable))
        dtSource = (DataTable)dg.DataSource;
        if (dtSource != null )
        SaveDataGridData (TableToString(dtSource),
        openFileDialog1.FileName);
    }
  }
 }
}
private void SaveDataGridData(string strData, string strFileName)
{
  FileStream fs;
  TextWriter tw = null;
  try
  {
    if (File.Exists(strFileName))
    {
      fs = new FileStream(strFileName, FileMode.Open);
    }
  else
  {
    fs= new FileStream(strFileName, FileMode.Create);
  }
  tw = new StreamWriter(fs);
  tw.Write(strData);
}
finally
{
  if (tw != null)
  {
    tw.Flush();
    tw.Close();
    MessageBox.Show("DataGrid Data has been saved to: "+ strFileName
    ,"Save DataGrid Data As", System.Windows.Forms.MessageBoxButtons.OK
    , System.Windows.Forms.MessageBoxIcon.Information);
  }
  }
}

5.7 Cloning Table contained in a DataGrid

This example shows how to clone a table in a DataGrid and then fill the cloned table with the content from the original table and return it in a new DataSet. Note that it checks to determine whether the DataSource is a DataSet or a Table.

private DataSet CloneDataTable(DataGrid dgTable)
{
 DataSet ds = new DataSet();
 DataTable dtSource = null;
 DataTable dt = new DataTable();
 DataRow dr;
 if(dgTable.DataSource != null)
  {
  if (dgTable.DataSource.GetType() == typeof(DataSet))
  {
   DataSet dsSource = (DataSet)dgTable.DataSource;
   // assume DataSet contains desired table at index 0

   dtSource = dsSource.Tables[0];
  }
  else
   if (dgTable.DataSource.GetType() == typeof(DataTable))
   dtSource = (DataTable)dgTable.DataSource;
   if (dtSource != null)
   {
    dt = dtSource.Clone();
    // dgConversionTable.CaptionText;

    dt.TableName = dtSource.TableName;
    dt.BeginLoadData();
    foreach (DataRow drSource in dtSource.Rows)
    {
     dr = dt.NewRow();
     foreach (DataColumn dc in dtSource.Columns)
     {
      dr[dc.ColumnName] = drSource[dc.ColumnName];
     }
     dt.Rows.Add(dr);
    }
   dt.AcceptChanges();
   dt.EndLoadData();
   ds.Tables.Add(dt);
   }
  }
return ds;
}

5.8 Comparison of WinForms & WebForms

This section will show differences between using a DataGrid in WinForms and WebForms. A table with a DataGrid caption of C-Terminal Groups will be used in the comparisons.

5.8.1 WinForms

In Figures 9 and 10, it can be seen that the C-Terminal table contains text, decimal numbers and integers that have been formatted using a procedure similar to formatDG(DataGrid dg) that us DataGridTableStyles. This DataGrid was formatted as follows:

Integers are centered within the column

  • Text strings are left justified. In the Formula column, which is generated from the values in the element decomposition columns, the electronic formula string has a space between each element instead of using numeric subscripts
  • Decimal numbers have 4 decimal places and are right justified.
  • The ID column is the primary key for the table.
  • NOTE: dc.Caption is used as column headers in the DataGrid, in this case dc.Caption is equal to dc.ColumnName

Figure 9 WinForm DataGrid displaying C-Terminal Groups

In Figure 10, row 3, Hydrazine in the Name column is being edited. That is, editing is performed directly within each cell by simply moving the cursor to the cell and clicking on the left mouse button to set the focus. The dark blue arrow on the left margin of the table moves to the row being edited. If there are more rows than can be displayed in the DataGrid view then a scroll bar is used to bring other rows into view.

Figure 10 WinForm DataGrid editing directly in a cell

To add more rows, the procedure is to enter new values into each cell in the row marked with an asterisk. The table class manages all new cell and row entries. One of the available methods will return a value indicating that changes to the table has occurred and then other methods can be used to determine the types of changes and to create a table containing only the changes. Events handlers can also be implemented to respond as changes are being made.

5.8.2 WebForms

Figures 11 � 13 illustrate the same table as in the Web Form, but there are differences. First and foremost is that the DataGrid is really an HTML Table. This can be readily seen by looking at the Formula column in Figures 11 and 12. The numeric subscript values are formatted using the HTML <sub>�</sub> tags with numbers being obtained from the element decompositions columns {C, H, N, O, S, P, Cl, Br, F, I}. That is, all cell entries can modified using all style elements available in HTML. Below Figure 11 is listed the HTML code generated by Microsoft�s Visual Studio .NET IDE for the DataGrid. It contains user defined and default property values. Notice in the HTML code that the blue background with white letters is defined for the header as well as the additional Edit and Delete columns that are used when modifying row values. Also, since an HTML table does not have scroll bars, a paging mechanism (Prev, Next) can be used with the number of lines per page defined with the PageSize attribute.

Figure 11 WebForm DataGrid being displayed as an HTML Table

HTML code that was generated for the DataGrid by Visual Studio .Net

<asp:datagrid id="dgCTerminalGroups"style="Z-INDEX: 103; LEFT: 72px;
POSITION: absolute; TOP: 464px"runat="server"
Font-Size="Small"Font-Names="Arial"HorizontalAlign="Left"
Width="656px"AllowPaging="True"PageSize="10"CellPadding="3">
<AlternatingItemStyle BackColor="Gainsboro"></AlternatingItemStyle>
<ItemStyle Font-Size="Smaller"Font-Names="Arial"HorizontalAlign="Center">
</ItemStyle>
<HeaderStyle Font-Size="Smaller"HorizontalAlign="Center"
ForeColor="White"BackColor="Navy">
</HeaderStyle>
<Columns>
<asp:EditCommandColumn ButtonType="LinkButton"UpdateText="Update"
HeaderText="Edit"CancelText="Cancel"EditText="Edit">
</asp:EditCommandColumn>
<asp:ButtonColumn Text="Delete"HeaderText="Delete"
CommandName="Delete"></asp:ButtonColumn>
</Columns>
<PagerStyle NextPageText="Next"Font-Size="Smaller"PrevPageText="Prev"
HorizontalAlign="Center"ForeColor="White"BackColor="Navy"></PagerStyle>
</asp:datagrid>

5.8.2.1 Edit a row

When a rows Edit linkbutton is clicked each cell in the row is converted to a TextBox control with its TextBox.text property initialized to the value obtained in the table cell. This is illustrated in row 3 of Figure 12 where each TextBox Control has been formatted as presented in the section of the dg_PreRender(object sender, System.EventArgs e, string strTableName) procedure code that follows Figure 13 where the EditItemIndex member is greater than �1. Note that the ID and Formula columns TextBox control is readonly, DarkGray colored text and the Border Width is zero.

Also when a rows Edit linkbutton is clicked the EditCommand handler is called. All that this handler does is set the EditItemIndex member and then calls the DataBind() method.

private void dgCTerminalGroups_EditCommand(object source,
System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
  dgNTerminalGroups.EditItemIndex = e.Item.ItemIndex;
  dgNTerminalGroups.DataBind();
}

5.8.2.2 Cancel editing a row

The edit operation can be canceled by clicking on the Cancel linkbutton where its CancelCommand event handler sets the EditItemIndex to �1 and then calls the DataBind() method.

private void dgNTerminalGroups_CancelCommand(object source,
System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
  dgNTerminalGroups.EditItemIndex = -1;
  dgNTerminalGroups.DataBind();
}

Figure 12 Editing C-Terminal Group row ID equal 3

5.8.2.3 Entering a new row

In the WinForm DataGrid a new row can be entered in the line designated with an asterisk, but this mechanism does not exist in WinForms. This implementation has created a special last row with a Key Phrase �[New CTerm-Group]� that lets the user know that this line is similar to the WinForm line with an asterisk and Edit will have the same behavior as the other lines. This line empty line was added to the DataTable after the object was filled from the database query and the update handler will remove this line prior to updating the database if no changes were made to the line other wise it will be added to the database table.

Figure 13 Adding a new C-Terminal Group row

5.8.2.4 Updating a row

This UpdateCommand event handler does the following:

Delete the last row ([New Cterm-Group]) from the DataSet Table if it is not the one being updated

  • If it was deleted, then the AcceptChanges() is called so there will be no attempt to delete from the table in the database since it really does not exist there.
  • Next get the value from each TextBox control and update the corresponding cell in the DataSet Table. Note the TextBox control has index equal zero in the Cells control collection.
  • Update the CTerminal Database table with the row changes or new row addition
  • Reset the EditItemIndex to �1, Set the DataSource to the modified DataSet and Call DataBind()

Note: if the update does not appear to work even though all of the event handlers are being called, check to see if you are using the IsPostBack method in the Page_OnLoad() event handler. For example

if (!Page.IsPostBack)
  dgCTerminalGroups.DataBind();

private void dgCTerminalGroups_UpdateCommand(object source,
System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
  DataRow dr;
  DataSet ds;
  ds = (DataSet)dgCTerminalGroups.DataSource;
  DataTable dt = ds.Tables["CTerminal"];
  // this code segment simply deletes the blank row at the

  // end of the table if it is not the row being modified/updated.

  if (e.Item.DataSetIndex < dt.Rows.Count-1)
  {
   dt.Rows[dt.Rows.Count-1].Delete();
   dt.AcceptChanges();
  }
  dr = dt.Rows[e.Item.DataSetIndex];
  for(int i=0; i< dt.Columns.Count; i++)
  {
   // The TextBox is the 0th element of the Controls collection.

   // the edit, delete columns are at cells i=0

   // and i=1 respectively, skip those

   TextBox tbox = (TextBox)e.Item.Cells[i+2].Controls[0];
  if (!tbox.ReadOnly) // skip readonly cells;

  //set in PreRender() event handler.

  dr[dt.Columns[i].Caption] = tbox.Text;
   }
  if (ds.HasChanges())
  {
   DataSet dsChanges = ds.GetChanges();
   // call an update database function (code for method not shown)

   // merge the changes

   // with the DataSet contained by the DataGrid

   UpdateTerminalTable(dsChanges, "CTerminal") != null)
   ds.Merge(dsChanges);
  }
 // Switch out of edit mode.

 dgCTerminalGroups.EditItemIndex = -1;
 dgCTerminalGroups.DataSource=ds;
 dgCTerminalGroups.DataBind();
}

5.8.2.5 Setting Cell Style values

When individual cells in the DataGrid Table need to be formatted beyond global grid and column settings, a user defined DataGrid PreRender event handler can be defined. The following code provides an example of a handler that can be used to set individual cell style values. The result of formatting based upon data types contained in the DataGrid can be seen by comparing Figures 13 and 14. The formatting includes

  • setting horizontal alignment properties for all cells
  • setting the [New Term Group] row to be in Italic and to have the ForeColor and BackColor properties set to Color.DarkGray and Color.White respectively
  • setting the ForeColor and BackColor to Color.DarkBlue and Color.LightGray respectively for decimal numbers.

This is a powerful technique that can be used for example to reflect cell edit changes by changing the individual cell font and colors of a changed cell.

This example further illustrates formatting using HTML CssStyles and control properties in the section of the code where it specifically handles the row currently being edited which is contained in the EditItemIndex member.

Note

In the dg_PreRender code, the for-loop

for (int j=0; j < dg.Items.Count; j++)

the maximum number of Items will be less than or equal to the dg.PageSize. To use j as an index for a table row, j needs to be augmented by dg.PageSize*dg.CurrentPageIndex. For example, column i for Item j in table dt is accessed as

dt.Rows[j + dg.PageSize*dg.CurrentPageIndex][i]

where dg.CurrentPageIndex contains a zero-based index.

private void dg_PreRender(object sender,
  System.EventArgs e, string strTableName)
{
  DataGrid dg = (DataGrid)sender;
  DataTable dt = null;
  if (dg.DataSource.GetType() == typeof(DataSet))
  {
   DataSet ds = (DataSet)dg.DataSource;
   dt = ds.Tables[strTableName];
  }
  else
  if (dg.DataSource.GetType() == typeof(DataTable))
   dt = (DataTable)dg.DataSource;
   if (dt != null)
  {
  for (int j=0; j <dg.Items.Count; j++)
   {
    if (j == (dg.Items.Count-1) &&
     ((string)dt.Rows[j+dg.PageSize*dg.CurrentPageIndex][
     "Name"]).IndexOf("[New") > -1)
    bNewGroupRow = true;
  else
    bNewGroupRow = false;
  for(int i=0; i<(dt.Columns.Count); i++)
  {
   // accentuate [New Term Group] row with font

   // and color modifications

   if(bNewGroupRow && dg.EditItemIndex == -1)
   {
    dg.Items[j].Cells[i+2].ForeColor = Color.DarkGray;
    dg.Items[j].Cells[i+2].BackColor = Color.White;
    FontInfo fi = dg.Items[j].Cells[i+2].Font;
    fi.Italic = true;
   }
   if(dt.Columns[i].DataType == typeof(System.Guid) ||
   dt.Columns[i].DataType == typeof(System.String))
   {
    dg.Items[j].Cells[i+2].HorizontalAlign =
      HorizontalAlign.Left ;
    dg.Items[j].Cells[i+2].Wrap = true ;
   }
   else
   if(dt.Columns[i].DataType == typeof(System.DateTime))
    dg.Items[j].Cells[i+2].HorizontalAlign =
    HorizontalAlign.Center ;
  else
   if(dt.Columns[i].DataType == typeof(int))
   {
    dg.Items[j].Cells[i+2].HorizontalAlign =
     HorizontalAlign.Center ;
   }
  else
 {
  if(dt.Columns[i].DataType == typeof(System.Double)
  || dt.Columns[i].DataType ==typeof(System.Decimal)
  || dt.Columns[i].DataType == typeof(float))
  {
   dg.Items[j].Cells[i+2].HorizontalAlign =
   HorizontalAlign.Right;
   // set the text color to DarkBlue and background color

    // LightGray

   dg.Items[j].Cells[i+2].ForeColor = Color.DarkBlue;
   dg.Items[j].Cells[i+2].BackColor = Color.LightGray;
   }
  }
 }
}
// check to see if a row is being edited, if it is

// then the EditItemIndex member is non-negative

// Each cell in the row now has an edit control,

// this code gets the EditControl object for

// the cell and then sets its cell specific attributes.

// This example uses the Caption text as

// a filter to select the cell type and then sets its

// individual style properties. Two cells

// ID and Forumla are readonly and their text color is

// set to DarkGray. The example also uses

// CssStyle to set global style attributes for each cell.

if (dg.EditItemIndex > -1)
{
  string strCaption;
  for(int i=2; i<=(dt.Columns.Count+1); i++)
  {
   TextBox tbox =
   (TextBox)dg.Items[dg.EditItemIndex].Cells[i].Controls[0];
   tbox.BackColor = Color.White;
   tbox.ForeColor = Color.DarkBlue;
   System.Web.UI.AttributeCollection
     tboxAttributes = tbox.Attributes;
   tboxAttributes.CssStyle.Add("font-weight", "bold");
   tboxAttributes.CssStyle.Add("text-align", "center");
   strCaption = dt.Columns[i-2].Caption.Trim();
   if (strCaption == "ID")
   {
      tbox.ReadOnly = true;
      tbox.ForeColor = Color.DarkGray;
      tbox.BorderWidth = 0;
      tbox.Width = 30;
   }
 else
  if (
   strCaption == "C"|| strCaption == "H"||
   strCaption == "N"|| strCaption == "O"||
   strCaption == "S"|| strCaption == "P"||
   strCaption == "Br"|| strCaption == "Cl"||
   strCaption == "F"|| strCaption == "I")
   {
    tbox.Width = 30;
   }
 else
   if (strCaption == "Formula")
   {
    tbox.Width = 150;
    tbox.ReadOnly = true;
    tbox.ForeColor = Color.DarkGray;
    tbox.BorderWidth = 0;
    tbox.Text = string.Empty;
   }
  else
   if (strCaption == "AvgMass"|| strCaption == "MonoMass")
   {
    tbox.Width = 75;
   }
 else
 if (strCaption == "Name")
 {
   tbox.Width = 150;
 }
 else
   tbox.Width = 75;
   }
  }
 }
}

Figure 14 Formatting DataGrid using PreRender method

6 References

  1. 1. Microsoft Visual Studio .NET IDE help system
  2. Microsoft Developer Network online help system
  3. Programming Microsoft Windows with C#, Charles Petzold, Microsoft Press, 2002.
  4. ASP.NET Data Web Controls, Scott Mitchell, SAMS, 2003

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here