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.
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 |
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;
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;
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;
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.
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.
System.Windows.Forms.DataGridTableStyle DGStyle =
new DataGridTableStyle();
DataGridTextBoxColumn textColumn;
foreach (DataColumn dc in dt.Columns)
{
textColumn = new DataGridTextBoxColumn();
textColumn.MappingName = dc.ColumnName;
textColumn.HeaderText = dc.Caption;
textColumn.Width = 200;
textColumn.Alignment = System.Windows.Forms.HorizontalAlignment.Left ;
textColumn.ReadOnly = true;
DGStyle.GridColumnStyles.Add(textColumn);
}
DGStyle.MappingName = ds.Tables[0].TableName;
DGStyle.AlternatingBackColor = Color.Gainsboro;
DGStyle.AllowSorting = false;
DGStyle.ReadOnly = true;
dg.TableStyles.Clear();
dg.TableStyles.Add(DGStyle);
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
dgConversionTable.TableStyles[0].GridColumnStyles.Clear();
DataGridTextBoxColumn textColumn;
foreach (DataColumn dc in dt.Columns)
{
textColumn = new DataGridTextBoxColumn();
textColumn.MappingName = dc.ColumnName;
textColumn.HeaderText = dc.Caption;
textColumn.Width = 200;
textColumn.Alignment = System.Windows.Forms.HorizontalAlignment.Left ;
textColumn.ReadOnly = true;
dgConversionTable.TableStyles[0].GridColumnStyles.Add(textColumn);
}
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.
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;
}
}
}
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�;
}
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)
{
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)
{
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;
foreach (DataColumn c in dt.Columns)
{
sf = g.MeasureString("W"+ c.Caption, f);
cWidths.Add(sf.Width);
}
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();
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;
}
}
}
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);
if (prevSumWidths < sumWidths)
{
prevSumWidths = sumWidths;
prevSumWidths += dg.RowHeaderWidth;
if (dg.VisibleRowCount < dt.Rows.Count)
prevSumWidths += 16;
if (prevSumWidths > (float)dg.Parent.Width)
prevSumWidths = (float)dg.Parent.Width - 16 - 5;
}
if (dg.VisibleRowCount >= dt.Rows.Count)
{
calcH = (dg.VisibleRowCount)*maxHeight+2*dg.PreferredRowHeight+16;
}
else
{
calcH = (dt.Rows.Count)*maxHeight+2*dg.PreferredRowHeight+16;
if (calcH < dg.Parent.Height)
prevSumWidths -= 16;
}
if (calcH >=dg.Parent.Height)
{
calcH = dg.Parent.Height � dg.Top - 25;
}
}
dg.Size = new Size((int)prevSumWidths,calcH);
}
}
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
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);
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;
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 |
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�);
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.
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;
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 );
}
}
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 );
}
}
}
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;
}
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:";
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;
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);
}
}
}
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;
dtSource = dsSource.Tables[0];
}
else
if (dgTable.DataSource.GetType() == typeof(DataTable))
dtSource = (DataTable)dgTable.DataSource;
if (dtSource != null)
{
dt = dtSource.Clone();
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;
}
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.
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.
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>
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();
}
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
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
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"];
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++)
{
TextBox tbox = (TextBox)e.Item.Cells[i+2].Controls[0];
if (!tbox.ReadOnly)
dr[dt.Columns[i].Caption] = tbox.Text;
}
if (ds.HasChanges())
{
DataSet dsChanges = ds.GetChanges();
UpdateTerminalTable(dsChanges, "CTerminal") != null)
ds.Merge(dsChanges);
}
dgCTerminalGroups.EditItemIndex = -1;
dgCTerminalGroups.DataSource=ds;
dgCTerminalGroups.DataBind();
}
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++)
{
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;
dg.Items[j].Cells[i+2].ForeColor = Color.DarkBlue;
dg.Items[j].Cells[i+2].BackColor = Color.LightGray;
}
}
}
}
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
- 1. Microsoft Visual Studio .NET IDE help system
- Microsoft Developer Network online help system
- Programming Microsoft Windows with C#, Charles Petzold, Microsoft Press, 2002.
- ASP.NET Data Web Controls, Scott Mitchell, SAMS, 2003