Dynamic Gridview C#
Dynamic Gridview C#
Figure 1
How it works
First, we should know what a template is. Generally, a template is description of how a
particular item will be rendered at run time. It determines the layout and binding of
ASP.NET server control(s) contained by it. We may put this description at design time or
at run time according to our need. At design time we can define templates declaratively
using inline tags in aspx source of GridView (the following listing shows this). Since we
are going for a case where we don’t know the number of fields of a table and their
description (Name, Data Type etc) in advance therefore we need to create the templates
dynamically according to fields of the particular table. This is where the concept of
dynamically templated GridView comes. This is a GridView that can be bound to any
table of any database of any server, providing Insert, Edit and Delete option
simultaneously. As long as we know the name of fields in advance we don’t need ,even,
to know about ITemplate but to create templates dynamically we have to implement
ITemplate interface. We need to generate ItemTemplate and EditItemTemplate
dynamically for each field of the table, plus a field for buttons required for each
command. The former lets to specify how an item will look like in normal mode (usually
displayed in labels) while the later lets to specify how an item will change when it is put
into Edit mode (displayed in text boxes).
ITemplate has one method named InstantiateIn, its explanation will come later. The class
DynamicallyTemplatedGridViewHandler which implements this interface, exposes any
template property of GridView as its own property, hence the default layout of a template
is overridden.
The following listing shows how to create template fields at design time. It shows that we
must know number of fields and their name in advance to create their templates. This is
NOT required in our case.
Listing 1
<asp:GridView ID="TableGridView"
runat="server" AutoGenerateColumns="False" >
<Columns>
<asp:TemplateField HeaderText="Name">
<ItemTemplate>
<asp:Label id="lblName" Runat="Server"
Text='<%# Eval("pub_name") %>'/>
</ItemTemplate>
<EditItemTemplate>
<asp:TextBox id="tbName" Runat="Server"
Text='<%# Bind("pub_name") %>'/>
</EditItemTemplate>
</asp:TemplateField>
....
</Columns
</asp:GridView>
Listing 2
public class DynamicallyTemplatedGridViewHandler : ITemplate
{
ListItemType ItemType;
string FieldName;
string InfoType;
ItemType keeps the type of a list item type: Item, EditItem, Header, Footer,
AlternatingItem, Pager, SelecetdItem or Separator. In this demo version we need only
three of these; we need Header (For heading of each column), Item (for showing fields
when GridView will be in normal mode) and EditItem (for showing fields when
GridView will be in Edit mode).
FieldName keeps the name of each template field that will be displayed in the header.
InfoType keeps an indicator in string form for a type of information within a template
field i.e. whether a template field has information of "Command" or "String" so that later
data retrieval and data binding of that particular child control will be made accordingly. A
"Command" (Edit, Delete, and Insert) requires instantiation in the Button control while
he "String" requires a Label or TextBox.
Now, coming to the member methods listed above, there is a constructer which simply
sets the aforementioned data members with those passed as parameters.
Listing 3
public DynamicallyTemplatedGridViewHandler(ListItemType item_type, string field_name,
string control_type)
{
ItemType = item_type;
FieldName = field_name;
InfoType = info_type;
}
Here is the explanation of InstantiateIn the only method of ITemplate being implemented
by our class DynamicallyTemplatedGridViewHandler.
InstantiateIn
InstantiateIn ensures that the list item type of each template is created in its appropriate
control. For better understanding of functionality of this method see its name
"InstantiateIn." It means "Instantiate Item In Literal/Label/TextBox/Button/." The choice
will be according to the requirement. Like in the case of a Header, it is instantiated in
Literal control as shown in this part of the implementation of InstantiateIn.
For example, below the code of InstantiateIn shows that if the ItemType is a Header, then
it creates a literal object called header_literal. After making it bold, set the text property
of header_literal with FieldName. Finally, add this control to the control collection of the
Container control passed as parameter to InstantiateIn method.
Similarly, we have to write instantiation code for ItemType if it is "Item" and "EditItem."
In case the ItemType is "Item" (fields look when GridView is in normal mode), we need
one more check inside it to see that InfoType tells whether the Item will be instantiated
with a Button (Edit, Insert, and Delete) or Label. If InfoType is a Button then it creates
three buttons for the aforementioned tasks. It is simple to do; create a button object, set its
all properties accordingly, also add the button's click event handler and finally, add it into
the control collection of the control (Container) passed as an argument.
Listing 4
public void InstantiateIn(System.Web.UI.Control Container)
{
switch (ItemType)
{
case ListItemType.Header:
Literal header_ltrl = new Literal();
header_ltrl.Text = "<b>" + FieldName + "</b>";
Container.Controls.Add(header_ltrl);
break;
case ListItemType.Item:
switch (InfoType)
{
case "Button":
ImageButton edit_button = new ImageButton();
edit_button.ID = "edit_button";
edit_button.ImageUrl = "~/images/edit.gif";
edit_button.CommandName = "Edit";
edit_button.Click += new ImageClickEventHandler(edit_button_Click);
edit_button.ToolTip = "Edit";
Container.Controls.Add(edit_button);
/*Similarly, add button for delete just set its
command to equal to "Delete." It is important to know when
"insert" button is added, its CommandName is set to "Edit" like
that of the "edi" button because we want the GridView to enter into
Edit mode and this time we also want the text boxes for corresponding fields
empty*/
ImageButton insert_button = new ImageButton();
insert_button.ID = "insert_button";
insert_button.ImageUrl = "~/images/insert.bmp";
insert_button.CommandName = "Edit";
insert_button.ToolTip = "Insert";
insert_button.Click += new ImageClickEventHandler(insert_button_Click);
Container.Controls.Add(insert_button);
default:
Label field_lbl = new Label();
field_lbl.ID = FieldName;
field_lbl.Text = String.Empty;
field_lbl.DataBinding += new EventHandler(OnDataBinding);
Container.Controls.Add(field_lbl);
break;
}
break;
case ListItemType.EditItem:
if (InfoType == "Button")
{
ImageButton update_button = new ImageButton();
update_button.ID = "update_button";
update_button.CommandName = "Update";
update_button.ImageUrl = "~/images/update.gif";
update_button.ToolTip = "Update";
update_button.OnClientClick =
"return confirm('Are you sure to update the record?')";
Container.Controls.Add(update_button);
}
else
// if other key and non key fields then bind textboxes with texts
{
TextBox field_txtbox = new TextBox();
field_txtbox.ID = FieldName;
field_txtbox.Text = String.Empty;
// if to update then bind the textboxes with coressponding field texts
//otherwise for insert no need to bind it with text
if ((int)new Page().Session["InsertFlag"] == 0)
field_txtbox.DataBinding += new EventHandler(OnDataBinding);
Container.Controls.Add(field_txtbox);
}
break;
}
}
Since we have to bind the labels in Item template and text boxes in EditItem template
with corresponding cell values, the data binding event handler OnDataBinding of both
label and text box populates the fields with cell values accordingly.
And you might also want to know how text boxes for each field get emptied when Insert
button is clicked. The solution is simple; do not bind them with a database and apply a
check while adding the data binding event handler of text box. Do not call
OnDataBinding if the insert button is clicked.
Listing 5
if ((int)new Page().Session["InsertFlag"] == 0)
field_txtbox.DataBinding += new EventHandler(OnDataBinding);
The implementation of the data binding event handler "OnDataBindin"' is simple. First,
we get the "bound_value_object" that is returned by the static method Eval of DataBinder
class which takes two parameters. One is of type object called "data_item_container"
(containing the DataItem that is assigned with sender control's NamingConatiner) and
other is the string expression, FieldName. Once we get this bound_value_object, we
assign its value (string) to the Text property of Label (if ItemType is Item; for normal
mode) and TextBox (if ItemType is EditItem; for Edit mode).
Listing 6
private void OnDataBinding(object sender, EventArgs e)
{
object bound_value_obj = null;
Control ctrl = (Control)sender;
IDataItemContainer data_item_container =
(IDataItemContainer)ctrl.NamingContainer;
bound_value_obj = DataBinder.Eval(data_item_container.DataItem, FieldName);
switch (ItemType)
{
case ListItemType.Item:
Label field_ltrl = (Label)sender;
field_ltrl.Text = bound_value_obj.ToString();
break;
case ListItemType.EditItem:
TextBox field_txtbox = (TextBox)sender;
field_txtbox.Text = bound_value_obj.ToString();
break;
}
}
The implementation of ITemplate is complete, although I want to mention that there are
some event handlers, "insert_button_Click" and "edit_button_Click" for Insert and Edit
buttons respectively. They do nothing except the former sets the Session[InsertFlag] to 1
and the later sets it to 0.
Add a new page to your project; in the demo it is default.aspx. Drop GridView control on
it. Name it TableGridView and set its AutoGenerateColumns property to false and add
the following event handlers to it.
• OnRowEditing
• OnRowCancelingEdit
• OnRowUpdating
• OnRowDeleting
Listing 7
<asp:GridView ID="TableGridView"
OnRowEditing ="TableGridView_RowEditing"
OnRowCancelingEdit="TableGridView_RowCancelingEdit"
OnRowUpdating="TableGridView_RowUpdating"
OnRowDeleting="TableGridView_RowDeleting"
runat="server" AutoGenerateColumns="False" >
</asp:GridView>
Let us take a look at the most important method CreateTemplatedGridView. This method
is responsible for initializing the object of DynamicallyTemplatedGridViewHandler and
creating the templated GirdView. Before going into detail of this method I want to
mention the use of two global variables taken in _Default class
Public static DataTable Table- It stores the data source table that will be bound with the
dynamically templated GridView.
ArrayList ParameterArray- It keeps the new field values while editing or inserting the
fields of a particular row. It is to be used by the queries.
Defining CreateTemplatedGridView
Listing 8
void PopulateDataTable()
{
Table = new DataTable();
TableGridView.Columns.Clear();
string ServerName = (string)Session["Server"];
string UserName = (string)Session["UserName"];
string Password = (string)Session["Password"];
string DatabaseName = (string)Session["DatabaseSelected"];
string TableName = (string)Session["TableSelected"];
SqlConnection Connection = new System.Data.SqlClient.SqlConnection("
Data Source=" + ServerName + ";
Initial Catalog=" + DatabaseName + ";
User ID=" + UserName + ";
Password=" + Password + ";
Connect Timeout=120");
SqlDataAdapter adapter = new SqlDataAdapter("Select * from " + TableName,
Connection);
try
{
adapter.Fill(Table);
}
catch (Exception ex)
{
msg_lbl.Text = ex.ToString();
}
}
We have, so far, populated the data source which will be bound by the TableGridView
through invoking PopulateDataTable in CreateTemplatedGridView. Now, coming to the
definition of CreateTemplatedGridView, we have to template the GridView. It is
important to know that a TemplateField object has the following template properties that
should be set on need basis.
• HeaderTemplate
• ItemTemplate
• EditItemTemplate
• FooterTemplate
• InsertItemTemplate
• AlternatingItemTemplate
We need only the first three template properties as we implemented the ITemplate for
them only.
The first column of the TableGridView is basically command column. It only has buttons
for different operations on a row, which is the same for every table bound to the
TableGridView. Take the TemplateField object BtnTmpField and initialize it. Then set the
template properties (HeaderTemplate,ItemTemplate and EditItemTemplate) as the
following code shows.
Listing 9
void CreateTemplatedGridView()
{
//fill the table which is to be bound with the GridView
PopulateDataTable();
TemplateField BtnTmpField = new TemplateField();
BtnTmpField.ItemTemplate =
new DynamicallyTemplatedGridViewHandler(ListItemType.Item, "...", "Command");
BtnTmpField.HeaderTemplate =
new DynamicallyTemplatedGridViewHandler(ListItemType.Header, "...",
"Command");
BtnTmpField.EditItemTemplate =
new DynamicallyTemplatedGridViewHandler(ListItemType.EditItem, "...",
"Command");
TableGridView.Columns.Add(BtnTmpField);
....
These three template properties are initialized using the constructor of the
DynamicallyTemplatedGridViewHandler class, which sets the data members
ItemType, FieldName and InfoType with the arguments provided. You might have
noted that I have given no title for this field (it is just "'…") and you may give a common
title or whatever you want. I have set the InfoType with "Command" (that will be an
indicator for the InstantiateIn method to inform it about the type of information the
template field contains) which will definitely be instantiated in buttons i.e. Edit, Insert
and Delete in normal mode while Save and Cancel in Edit mode. Finally, add
BtnTemplateField in the column collection of the GridView as the last line of the code
shows. Now, we are finished with the first field of the dynamically templated GridView
which is common to any table being bound to the GridView.
Next, we have to traverse the table to get the name and type of each column (which we
do not know in advance) so it is passed to the constructor (second and third arguments for
column name and type respectively) for initializing the three template properties of each
field.
Listing 10
....
for (int i = 0; i < Table.Columns.Count; i++)
{
TemplateField ItemTmpField = new TemplateField();
// create the header
ItemTmpField.HeaderTemplate =
new DynamicallyTemplatedGridViewHandler(ListItemType.Header,
Table.Columns[i].ColumnName,
Table.Columns[i].DataType.Name);
// create ItemTemplate
ItemTmpField.ItemTemplate =
new DynamicallyTemplatedGridViewHandler(ListItemType.Item,
Table.Columns[i].ColumnName,
Table.Columns[i].DataType.Name);
// create EditItemTemplate
ItemTmpField.EditItemTemplate =
new DynamicallyTemplatedGridViewHandler(ListItemType.EditItem,
Table.Columns[i].ColumnName,
Table.Columns[i].DataType.Name);
// add to the GridView
TableGridView.Columns.Add(ItemTmpField);
}
....
Listing 11
....
TableGridView.DataSource = Table;
TableGridView.DataBind();
}
This finishes the definition of CreateTemplatedGridView and also completes the creation
of the templated GridView dynamically. This method has been invoked in Page_Load
only for post backs! This may seem a bit strange. Why is it done for all post backs and
not just the first time? Actually, at each post back a dynamically templated GridView
cannot be presented from ViewState so we have to create and bind it explicitly using this
method on all post backs.
Now let us take a look at the three operations (Edit, Insert and Delete) and their
corresponding event handlers one by one, which will help you better understand how this
demo works.
When a user clicks on the pencil image button used for editing, the GridView enters into
Edit mode and the OnRowEditing event is fired that has been handled just to set the
EditIndex of the GridView to new EditIndex explicitly, bind it and then save the
NewEditIndex in session so that it can be used later when needed.
Listing 12
public void TableGridView_RowEditing(object sender, GridViewEditEventArgs e)
{
TableGridView.EditIndex = e.NewEditIndex;
TableGridView.DataBind();
Session["SelecetdRowIndex"] = e.NewEditIndex;
}
Figure 2
The figure above shows how it looks when the GridView enters into Edit mode. For
every field, data bound textboxes are displayed. The row now contains two buttons, an
Update (one with check image) and Cancel (one with cross image). When the user makes
desired changes and clicks the update button, OnRowUpdating event is fired. First, we
get all information needed to generate the connection string that includes the server name,
user name, and password and database name that are gotten from their corresponding
Session variable. Next, get the particular GridView row which is being edited so that the
new values of all fields of that row could be added in ArrayList ParameterArray; one we
have already taken for this purpose.
Listing 13
GridViewRow row = TableGridView.Rows[e.RowIndex];
for (int i = 0; i < Table.Columns.Count; i++)
{
string field = ((TextBox)row.FindControl(Table.Columns[i].ColumnName)).Text;
ParameterArray.Add(field);
Later we need these field values from ParameterArray to generate Update and Insert
Query accordingly. Since Insert Operation has been implemented through edit mode of
the GridView, it definitely uses the OnRowUpdating event. So, here we have to check
which operation is intended and whether the Insert or Edit button has been clicked. This
check is made using the flag value from the Session variable Session["InsertFlag"] as
shown in the following part of OnRowUpdating.
Listing 14
....
string Query = "";
if ((int)Session["InsertFlag"] == 1)
Query = GenerateInsertQuery();
else
Query = GenerateUpdateQuery();
SqlCommand Command = new System.Data.SqlClient.SqlCommand(Query, Connection);
try
{
Connection.Open();
Command.ExecuteNonQuery();
Session["InsertFlag"] = (int)Session["InsertFlag"] == 1 ? 0 : 1;
}
catch (SqlException se)
{
msg_button.Visible = true;
msg_lbl.Text = se.ToString();
}
TableGridView.EditIndex = -1;
CreateTemplatedGridView();
After the command runs successfully, Session[InserFlag] is reset. You may have noticed
that I have cancelled the GridView's Edit mode by setting its Edit Index equal to -1 which
brings the GridView back to the normal mode so that the changes could be viewable. We
have to call CreateTemplatedGridView after any manipulation on table through the
GridView as we again want the templated GridView to be created with updated
information. Simply setting the DataSource of the GridView and calling DataBind
method cannot work in this scenario.
It is important to know how the cancel button works. When clicked, the event
TableGridView_RowCancelingEdit gets fired which sets the EditIndex to -1, binds the
GridView and resets the index of the selected row in the session variable as shown below.
Listing 15
public void TableGridView_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e)
{
TableGridView.EditIndex = -1;
TableGridView.DataBind();
Session["SelecetdRowIndex"] = -1;
When the Insert button is clicked, the GridView enters into Edit mode. However, this
time it has empty text boxes for all corresponding fields. It has been shown in the
definition of InstantiateIn. None of the event handlers specific to insert operation solely
have been implemented! It is just the utilization of the event handlers for edit operation,
but with common sense, which, as elders say, is not common! There is another point to be
noted here, it may be a small drawback, but it is negligible; the row for insertion replaces
the exiting row just visually and after insertion it reappears along with the newly inserted
one.
Figure 3
How Delete Operation works
When the Delete button is clicked a confirmation dialogue box appears (one also added
for Update button). It is a small piece of JavaScript code added in the button's
OnClientClick event. When user select "Ok" the OnRowDeleting event gets fired and the
delete query runs for that particular row.
Listing 16
....
string Query = GenerateDeleteQuery(e.RowIndex);
SqlCommand Command = new System.Data.SqlClient.SqlCommand(Query, Connection);
try
{
if(Connection.State==ConnectionState.Closed)
Connection.Open();
Command.ExecuteNonQuery();
}
catch (SqlException se)
{
msg_button.Visible = true;
msg_lbl.Text = se.ToString();
Connection.Close();
}
CreateTemplatedGridView();
First, the delete query is generated by invoking the GenerateDeleteQuery method which
takes the index of the row to be deleted as parameter. This index is used to find the
particular row's first field (being assumed that the Primary Key lies in first column) for
the parameter in WHERE clause for delete query. GenerateDeleteQuery uses this index as
shown below.
Listing 17
Again, after any manipulation on the database through GridView, we need to call
CreateTemplatedGridView. We also have to do this after the deletion completes
successfully.