Introduction
This article describes about how to create your own custom column like asp:BoundField & asp:ButtonField and adding them dynamically to a grid view.
In one of my project I am being asked to develop code for a requirement
- The number of columns is not known till the execution time. The columns can be of any number.
- Some columns in the grid need to be editable.
- Should be able to display controls like check box/list box in one of the column.
In this article I am going to describe about how to achieve the above said requirements by creating a custom column of my own and dynamic adding columns in the grid view. Here I have used the DataTable as the datasource. As data table provides more extendable features.
Let�s walk through the steps.
Code walkthrough
Create a grid view in the page,
- Drag and drop the GridView control on to the page.
Or
- Manually type GridView control�s definition in the page.
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td><strong>How to create custom columns dynamically in a grid view</strong></td>
</tr>
<tr>
<td>
<asp:GridView ID="GrdDynamic" runat="server" AutoGenerateColumns="False" ShowHeader="true" ShowFooter="true">
<Columns>
</Columns>
</asp:GridView>
</td>
</tr>
<tr>
<td>
<asp:Button ID="cmdSave" runat="server" OnClick="cmdSave_Click" Text="Save" />
</td>
</tr>
</table>
With this we are done with creating a GridView in the page.
Let�s move on to the code-beside to understand the background history of the page.
Here I will describe about, how to create our own custom bound field column.
//Iterate through the columns of the datatable to set the data bound field dynamically.
foreach (DataColumn col in dt.Columns)
{
//Declare the bound field and allocate memory for the bound field.
CustomBoundField bfield = new CustomBoundField();
//Initalize the DataField value.
bfield.DataField = col.ColumnName;
//Initialize the HeaderText field value.
bfield.HeaderText = col.ColumnName;
//Check if any property has been defined for the editing.
if (col.ExtendedProperties["Editable"] != null)
{
bfield.Editable = Convert.ToBoolean(col.ExtendedProperties["Editable"]);
}
//Check if any property has been defined for displaying the checkboxes.
if (col.ExtendedProperties["CheckBox"] != null)
{
bfield.ShowCheckBox = Convert.ToBoolean(col.ExtendedProperties["CheckBox"]);
}
//Add the newly created bound field to the GridView.
GrdDynamic.Columns.Add(bfield);
}
Let�s start dissecting right from the top,
- Create a DataTable, which will hold the table definition and data for the Grid View. This table is used as a Data Source for the Grid View.
DataTable dt = new DataTable();
- Once the Data Table is created, let�s add few columns to the Data Table.
- The logic behind creating dynamic column starts by creating a CustomBoundField instance.
- Once the CustomBoundField is created, I am initializing the DataField and HeaderText properties of the newly created CustomBoundField.We will come back to the CustomBoundField class in detail again.
- Once the creation of the dynamic column and assigning values to the column is complete. Add the column to the Grid View.
How to create the CustomBoundField
Till now the above-described method is the same old story as before, but the interesting part of this story starts when you look at the class CustomBoundField.
public class CustomBoundField : DataControlField
{
public CustomBoundField()
{
//
// TODO: Add constructor logic here
//
}
#region Public Properties
/// <summary>
/// This property describe weather the column should be an editable column or non editable column.
/// </summary>
public bool Editable
{
get
{
object value = base.ViewState["Editable"];
if (value != null)
{
return Convert.ToBoolean(value);
}
else
{
return true;
}
}
set
{
base.ViewState["Editable"] = value;
this.OnFieldChanged();
}
}
/// <summary>
/// This property is to describe weather to display a check box or not.
/// This property works in association with Editable.
/// </summary>
public bool ShowCheckBox
{
get
{
object value = base.ViewState["ShowCheckBox"];
if (value != null)
{
return Convert.ToBoolean(value);
}
else
{
return false;
}
}
set
{
base.ViewState["ShowCheckBox"] = value;
this.OnFieldChanged();
}
}
/// <summary>
/// This property describe column name, which acts as the primary data source for the column.
/// The data that is displayed in the column will be retreived from the given column name.
/// </summary>
public string DataField
{
get
{
object value = base.ViewState["DataField"];
if (value != null)
{
return value.ToString();
}
else
{
return string.Empty;
}
}
set
{
base.ViewState["DataField"] = value;
this.OnFieldChanged();
}
}
#endregion
#region Overriden Life Cycle Methods
/// <summary>
/// Overriding the CreateField method is mandatory if you derive from the DataControlField.
/// </summary>
/// <returns></returns>
protected override DataControlField CreateField()
{
return new BoundField();
}
/// <summary>
/// Adds text controls to a cell's controls collection. Base method of DataControlField is
/// called to import much of the logic that deals with header and footer rendering.
/// </summary>
/// <param name="cell">A reference to the cell</param>
/// <param name="cellType">The type of the cell</param>
/// <param name="rowState">State of the row being rendered</param>
/// <param name="rowIndex">Index of the row being rendered</param>
public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
{
//Call the base method.
base.InitializeCell(cell, cellType, rowState, rowIndex);
switch (cellType)
{
case DataControlCellType.DataCell:
this.InitializeDataCell(cell, rowState);
break;
case DataControlCellType.Footer:
this.InitializeFooterCell(cell, rowState);
break;
case DataControlCellType.Header:
this.InitializeHeaderCell(cell, rowState);
break;
}
}
#endregion
#region Custom Protected Methods
/// <summary>
/// Determines which control to bind to data. In this a hyperlink control is bound regardless
/// of the row state. The hyperlink control is then attached to a DataBinding event handler
/// to actually retrieve and display data.
///
/// Note: This control was built with the assumption that it will not be used in a gridview
/// control that uses inline editing. If you are building a custom data control field and
/// using this code for reference purposes key in mind that if your control needs to support
/// inline editing you must determine which control to bind to data based on the row state.
/// </summary>
/// <param name="cell">A reference to the cell</param>
/// <param name="rowState">State of the row being rendered</param>
protected void InitializeDataCell(DataControlFieldCell cell, DataControlRowState rowState)
{
//Check to see if the column is a editable and does not show the checkboxes.
if (Editable & !ShowCheckBox)
{
string ID = Guid.NewGuid().ToString();
TextBox txtBox = new TextBox();
txtBox.Columns = 5;
txtBox.ID = ID;
txtBox.DataBinding += new EventHandler(txtBox_DataBinding);
cell.Controls.Add(txtBox);
}
else
{
if (ShowCheckBox)
{
CheckBox chkBox = new CheckBox();
cell.Controls.Add(chkBox);
}
else
{
Label lblText = new Label();
lblText.DataBinding += new EventHandler(lblText_DataBinding);
cell.Controls.Add(lblText);
}
}
}
void lblText_DataBinding(object sender, EventArgs e)
{
// get a reference to the control that raised the event
Label target = (Label)sender;
Control container = target.NamingContainer;
// get a reference to the row object
object dataItem = DataBinder.GetDataItem(container);
// get the row's value for the named data field only use Eval when it is neccessary
// to access child object values, otherwise use GetPropertyValue. GetPropertyValue
// is faster because it does not use reflection
object dataFieldValue = null;
if (this.DataField.Contains("."))
{
dataFieldValue = DataBinder.Eval(dataItem, this.DataField);
}
else
{
dataFieldValue = DataBinder.GetPropertyValue(dataItem, this.DataField);
}
// set the table cell's text. check for null values to prevent ToString errors
if (dataFieldValue != null)
{
target.Text = dataFieldValue.ToString();
}
}
protected void InitializeFooterCell(DataControlFieldCell cell, DataControlRowState rowState)
{
CheckBox chkBox = new CheckBox();
cell.Controls.Add(chkBox);
}
protected void InitializeHeaderCell(DataControlFieldCell cell, DataControlRowState rowState)
{
Label lbl = new Label();
lbl.Text = this.DataField;
cell.Controls.Add(lbl);
}
void txtBox_DataBinding(object sender, EventArgs e)
{
// get a reference to the control that raised the event
TextBox target = (TextBox)sender;
Control container = target.NamingContainer;
// get a reference to the row object
object dataItem = DataBinder.GetDataItem(container);
// get the row's value for the named data field only use Eval when it is neccessary
// to access child object values, otherwise use GetPropertyValue. GetPropertyValue
// is faster because it does not use reflection
object dataFieldValue = null;
if (this.DataField.Contains("."))
{
dataFieldValue = DataBinder.Eval(dataItem, this.DataField);
}
else
{
dataFieldValue = DataBinder.GetPropertyValue(dataItem, this.DataField);
}
// set the table cell's text. check for null values to prevent ToString errors
if (dataFieldValue != null)
{
target.Text = dataFieldValue.ToString();
}
}
#endregion
}
Any class that should be used as a custom bound field column should be inherited from the DataControlField class.
DataControlField serves as the base class for all data control field types, which represent a column of data in tabular data-bound controls such as Details View and Grid View.
In the newly created class, I have created properties
- Editable
- ShowCheckBox
- DataField
Editable
This property describe weather the column should be an editable column or non editable column.
ShowCheckBox
This property is to describe weather to display a check box or not.
DataField
This property describe column name, which acts as the primary data source for the column. The data that is displayed in the column will be retreived from the given column name.
Now, we have to override one important property i.e. InitializeCell to create the required format for the column.
InitializeCell:
Types derived from DataControlField implement the InitializeCell method to add text and controls to a DataControlFieldCell object that belongs to a data control that uses tables to display a user interface (UI). These data controls create the complete table structure row by row when their respective CreateChildControls methods are called.
The InitializeCell method is called by the InitializeRow method of data controls such as DetailsView and GridView.
In this article, lets have a look at the initializeCell method�s definition
switch (cellType)
{
case DataControlCellType.DataCell:
this.InitializeDataCell(cell, rowState);
break;
case DataControlCellType.Footer:
this.InitializeFooterCell(cell, rowState);
break;
case DataControlCellType.Header:
this.InitializeHeaderCell(cell, rowState);
break;
}
Based on the passed cell type, invoking the corresponding methods to initialize the cells data accordingly. This method would be called for every row that is created in the Grid View.
InitializeFooterCell:
InitializeFooterCell is a private method, which will be called while creating the footer row in the Grid View. To see how the footer row looks like, make sure you have the property ShowFooter="true". Then only the footer row can be seen. The code displays the checkboxes in the footer row.
InitializeHeaderCell:
InitializeHeaderCell is a private method, which will be called while creating the header row in the Grid View. To see how the header row looks like make sure you have the property ShowHeader="true". While creating the header or footer you can create more controls than what I have shown in the example like hiddencontrols, table control etc.
InitializeDataCell:
InitializeDataCell is a private method, where most of the magic will be done. In this method at first, I am checking to see if the column is an editable and does not show the checkboxes. If the condition is met, I am creating a text box control and adding the control to the table�s cell, otherwise I am checking weather I have to show the checkbox or label control. If you notice one of the interesting funda is the DataBinding event of the text box or label control. The DataBinding event occurs when the server control binds to a data source. Since I am overriding the default behaviour of the system. I have to manually set the value for the text box also. Have a look at the attached code for more details on how to read the data from the container and set the values in to the text box.
The various advantages with this approach are
- Customize the appearance of the control.
- Format how the UI should look like. Can format the header, footer and item
- Can add JavaScript support like the way we support from the code beside.
That�s your entire dynamic GridView is ready. I hope this information would be helpful.
Enjoy programming.
PAN style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">
InitializeHeaderCell:
InitializeHeaderCell is a private method, which will be called while creating the header row in the Grid View. To see how the header row looks like make sure you have the property ShowHeader="true". While creating the header or footer you can create more controls than what I have shown in the example like hiddencontrols, table control etc.
InitializeDataCell:
InitializeDataCell is a private method, where most of the magic will be done. In this method at first, I am checking to see if the column is an editable and does not show the checkboxes. If the condition is met, I am creating a text box control and adding the control to the table�s cell, otherwise I am checking weather I have to show the checkbox or label control. If you notice one of the interesting funda is the DataBinding event of the text box or label control. The DataBinding event occurs when the server control binds to a data source. Since I am overriding the default behaviour of the system. I have to manually set the value for the text box also. Have a look at the attached code for more details on how to read the data from the container and set the values in to the text box.
The various advantages with this approach are
- Customize the appearance of the control.
- Format how the UI should look like. Can format the header, footer and item
- Can add JavaScript support like the way we support from the code beside.
That�s your entire dynamic GridView is ready. I hope this information would be helpful.
Enjoy programming.