AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=1125&pId=-1
Dynamically Templated GridView with Edit, Delete and Insert Options
page
by G. Mohyuddin
Feedback
Average Rating: 
Views (Total / Last 10 Days): 204073/ 179

Introduction

In many situations we want to bind a fully, in-place operational GridView with a dynamically changing data source. This article shows how GridView can be templated programmatically at run time and how is it possible to do edit, insert and delete operations on it. Although just binding the GridView with some dynamically changing datatsource is a simple and straight job which does not need templated GridView, it cannot facilitate us with the aforementioned operations. To make the GridView simultaneously an operational one, we need to template it dynamically which also requires dynamically generated queries for corresponding operations according to the GridView's data source.

It is useful in many applications especially those that require data manipulation functionality of Enterprise Manager on client side i.e. displaying any table of any database with options for all possible operations on client side. This solution provided in this article will be the best and probably the only way to make it all possible. This article shows how ITemplate is implemented effectively to achieve all of this.

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>

 

I have implemented ITemplate in a separate class, DynamicallyTemplatedGridViewHandler, for reusability and better understanding. I will explain working and usage of this class since they play a key role in achieving all of this.

We can divide the whole working in two parts:

·         Writing a class which implements ITemplate

·         Using the class which implements ITemplate

Writing a class which implements ITemplate

Here is the skeleton of class DynamicallyTemplatedGridViewHandler implementing ITemplate with a list of data members and methods.

Listing 2

public class DynamicallyTemplatedGridViewHandler : ITemplate
{
    ListItemType ItemType;
    string FieldName;
    string InfoType;
 
    public DynamicallyTemplatedGridViewHandler(ListItemType Item_type,
    string field_name, string control_type);
    public void InstantiateIn(System.Web.UI.Control Container);
    private void OnDataBinding(object sender, EventArgs e);
}

This class has three data members:

    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.

InstantiateIn takes a "Container," a Control type object as a parameter. Container's control collection is filled with all controls in which items of each type are instantiated.  Its implementation in the current scenario is a little lengthy, yet it is quite easy as we have to do a similar type of job with each control; instantiate it, set its text property with FieldName and add it into Container's control collection.

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);
 
// Similarly, add a button for Cancel
 
  }
  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;
}
}

When InfoType is not a Command it means we have to instantiate it with a label as in GridView's normal mode when each cell text of GridView's rows is displayed in label. Therefore, by default, control is instantiated with Label and is added into the Container. Since we have no more info types (other than command and string) then no further checks are required. We come to the outer check when ItemType is EditItem. Now, we need an inner check for info type (command or string). The template field will be instantiated in Button if the info type is Command; otherwise it requires TextBox for the cell text of edit item.

It is important to know that the CommandName of insert_button is set to "Edit" just to take advantage of the Edit mode that provides text boxes for all editable items. If the these text boxes are emptied, they can be used for insertion instead of editing without dedicating an extra row for it. This is easy and handy as it becomes a better approach when it is not known in advance how many columns there are in GridView's data source. I have taken a session variable InsertFlag that is set to 0 and 1 for Edit and Inert operations respectively.

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);

DataBinding Event Handler     

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.   

Using the class which implements ITemplate

Now I will show you how to use the class DynamicallyTemplatedGridViewHandler which implements ITemplate.

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

The following aspx source shows this.

Listing 7

<asp:GridView ID="TableGridView"  
      OnRowEditing ="TableGridView_RowEditing" 
      OnRowCancelingEdit="TableGridView_RowCancelingEdit" 
      OnRowUpdating="TableGridView_RowUpdating"
      OnRowDeleting="TableGridView_RowDeleting"
      runat="server"  AutoGenerateColumns="False" >
</asp:GridView>

The default.cs has method PopulateDataTable, CreateTemplatedGridView, GenerateUpdateQuery, GenerateDeleteQuery, GenerateUpdateQuery and the aforementioned event handlers.

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

This is an important method which shows how to use the class DynamicallyTemplatedGridViewHandler. It first invokes the PopulateDataTable method, initializes the Table data member and clears the column collection of the TableGridView to avoid repetitive addition of columns on post backs. It initializes ServerName, USerName, Password, DatabaseName and TableName with their corresponding Session variables. Using the information on the aforementioned variables, it generates a connection string and initializes the SqlConnection object "Connection." The SQLDataAdapter object uses this connection object and select query for selecting all rows from the desired table and populate Table using the Fill method of SqlDataAdapter. The definition of PopulateDataTable is below.

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);
 }            

 ....

After initializing the Header,ItemTemplate and EditItemTemplate for a field, we have to add it to the column collection of the GridView. Now, all fields of the GridView have been templated successfully and we have to assign the Table to the GridView's DataSource property and bind it. Simply add the following two lines to the code.

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.

Working with GridView Operations

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.

How Edit Operation 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;
   
}

How Insert Operation works

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

string query = "";

if (Table.Columns[0].DataType.Name == "String" || Table.Columns[0].DataType.Name == "DateTime")
   query = "Delete from " + TableName + " where " + Table.Columns[0].ColumnName +
   "='" + <span class=Bold>Table.Rows[index][0].</span>ToString() + "'";
    

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.

Downloads
Conclusion

Creating a dynamically templated GridView loaded with in-place functionalities of edit, delete and insert is a great use in many situations. You may need it when you have no knowledge in advance of the structure of the table and you not only want to bind it with GridView, but also want to manipulate the table through GridView.

I have tried to explain everything that is important to understand concerning the entire working, yet there are some trivial details which I have not given just to make the article as concise as possible. There is one area which I thought trivial to explain, the query generation methods. You may check the code detail and working of these methods.

Please note that the code assumes that the primary key of the table bound with GridView is in its first column(Table.Columns[0]). Actually it has been left upto user to decide which column of the table; he decides to be the primary key field. I have not disabled it for it will no longer be editable even if first column is not primary key field. You may change it accordingly or may add small code to check for primary key dynamically and disable it.

Happy GridViewing!


Product Spotlight
Product Spotlight 

©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-05-04 6:06:00 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search