Click here to Skip to main content
Click here to Skip to main content

Inplace Edit in GridView

, 24 Sep 2009
Rate this:
Please Sign up or sign in to vote.
A slightly different way to look at GridView Edit

Introduction

In this article, I am presenting a different way to look at EditItemTemplate, using which user navigation for editing is reduced and … it looks very different (see screen shots) than regular implementation of GridView.

Inline Edit

Background

Few days ago, I got an assignment. I needed to add a few new fields in a DataView and let the user edit those (along with all existing fields).

I thought it was a very small change, just new fields in ItemTemplate and EditItemTemplate and perform few basic validations on it using JavaScript and I'm done! I was dead wrong. As soon as I modified EditItemTemplate, a Horizontal scrollbar started to appear on the page. One of the UI rules in our entire application on minimum resolution 1024x678 and with default font size, is that no Horizontal Scrollbar should appear on any of the pages. So I started thinking and creating new concepts. Here we had a few options.

Various Options

Option 1

Create a new page for edit. When user clicks on the edit button, we redirect the user to a new page and when users clicks on Update/Close, we bring them back to the list page.

Problem

It was a lengthy process to create a new page, validate and bring them back, Also in this case, the link between current work and edit was broken as a user was moving back and forth for editing.

Option 2

Create a popup window. When user clicks on edit button, we create a popup window and let the user edit in a new window. When a user clicks on update/close, the user comes back to the list page.

Problem

As a modern design approach, we were moving away from popup windows (which opens as a new window) and try to find some other alternate for these popup windows. While talking about this option, I also threw AjaxControlToolkit Model Popup but it had the same problem as option 1. The link between current data and editing data was broken.

Option 3

Create an inline form which gets displayed between two rows in GridView. Let the user make any modification required there and when the user clicks on update/close, bring back the same row. This is not a new technique but there are many companies that offer this kind of facility (and charge big bucks for that). As this was a bit easy to implement, I thought of trying to create it on our own (and save some bucks, of course for the company Smile | :) ).

Problem

NONE !!!

My IT Director was very happy to see this prototype. She suggested that I implement this style. And the journey begins …

There are a few things which generate this facility.

As you already know, GridView gets rendered as a table and each column definition in GridView generates one column; so we defined only one column. To create an illusion of Table, we defined only one column and created a table in it. We created the required columns in this new inner table. This gets rendered as one table per DataRow with all columns we specified.

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" CellPadding="4"
ForeColor="#333333" GridLines="Both" Width="60%" OnRowEditing="GridView1_RowEditing">
<RowStyle BackColor="#EFF3FB" />
<Columns>
     <asp:TemplateField>
     ...
     ...
     <ItemTemplate>
        <table width="100%" cellpadding="0" cellspacing="0" >
            <tr>
                <td style="width:15%;" align="left">
                    <%#DataBinder.Eval(Container.DataItem, "FirstName")%>
                </td>
                <td style="width:15%;" align="left">
                    <%#DataBinder.Eval(Container.DataItem, "LastName")%>
                </td>
                <td style="width:35%;" align="left">
                    <%#DataBinder.Eval(Container.DataItem, "Web")%>
                </td>
                <td style="width:35%;" align="left">
                    <%#DataBinder.Eval(Container.DataItem, "Email")%>
                </td>
            </tr>
        </table>
    </ItemTemplate>

The above ItemTemplate will generate one table per row. Now let's see how we can make inline editing possible.

 <EditItemTemplate>
    <center>
        <ajx:RoundedCornersExtender ID="rce" runat="server" TargetControlID="pnlHead" 
        Radius="6" Corners="Top" BorderColor="black" />
        <asp:Panel ID="pnlHead" runat="server" Font-Bold="true" ForeColor="white" 
        BackColor="#507CD1"
            Width="50%">
            <center>
                <table>
                    <tr>
                        <td>
                            Updating
                            <%#DataBinder.Eval(Container.DataItem, "FirstName")%>,
                            <%#DataBinder.Eval(Container.DataItem, "LastName")%>
                        </td>
                    </tr>
                </table>
            </center>
        </asp:Panel>
        <ajx:RoundedCornersExtender ID="rceDetail" runat="server" 
		TargetControlID="pnlDetail"
            Radius="6" Corners="Bottom" BorderColor="black" />
        <asp:Panel ID="pnlDetail" runat="server" Width="50%">
            <table width="100%">
                <tr>
                    <td align="right" style="width: 30%">
                        First Name:
                    </td>
                    <td style="padding-right:10px;">
                        <asp:TextBox ID="tbFirstName" Width="100%" runat="server" 
                        Text='<%#DataBinder.Eval(Container.DataItem, "FirstName")%>'>
                        </asp:TextBox>
                    </td>
                </tr>
                <tr>
                    <td align="right">
                        Last Name:
                    </td>
                    <td style="padding-right:10px;">
                        <asp:TextBox ID="TextBox1" runat="server" Width="100%" 
                        Text='<%#DataBinder.Eval(Container.DataItem, "LastName")%>'>
                        </asp:TextBox>
                    </td>
                </tr>
                <tr>
                    <td align="right">
                        Web:
                    </td>
                    <td style="padding-right:10px;">
                        <asp:TextBox ID="TextBox2" runat="server" Width="100%" 
                        Text='<%#DataBinder.Eval(Container.DataItem, "Web")%>'>
                        </asp:TextBox>
                    </td>
                </tr>
                <tr>
                    <td align="right">
                        eMail:
                    </td>
                    <td style="padding-right:10px;">
                        <asp:TextBox ID="TextBox3" runat="server" Width="100%" 
                        Text='<%#DataBinder.Eval(Container.DataItem, "eMail")%>'>
                        </asp:TextBox>
                    </td>
                </tr>
            </table>
            <div style="padding-top: 3px; border-top: thin solid black;" id="divUpdate">
                <asp:Button CssClass="btnNormal" runat="server" ID="bUpdate" 
                Text="Update" OnClick="bUpdate_Click" />    
                <asp:Button OnClick="bCancel_Click" CssClass="btnNormal" 
                runat="server" ID="bCancel" Text="Cancel" />
            </div>
        </asp:Panel>
    </center>
</EditItemTemplate>
</asp:TemplateField>

From our ItemTemplate, it was clear that in edit mode we will get only one column, so just like in ItemTemplate, here too we defined edit layout whichever way we wanted. In our case, we wanted rounded corner center aligned table, with different style on header. To achieve this, we used AjaxControlToolkit's RoundedCornerExtension. It has a facility to define which corners we want as rounded corners, so as header we defined only top corners as rounded and for content part we defined only bottom corners as rounded corners. And using table, we defined a layout of form for editing. This gives us a nice looking form when the user clicks on the edit button. And as a result, we got the following GridView:

Image 2: Edit View in inline Grid View

As a final touch, we also needed the header to match the given columns, so we used the same type of style we used on ItemTemplate.

    <HeaderTemplate>
        <table width="100%" cellpadding="0" cellspacing="0">
            <tr>
                <td style="width:15%;" align="left">
                    First Name
                </td>
                <td style="width:15%;" align="left">
                    Last Name
                </td >
                <td style="width:35%;" align="left">
                    Web
                </td>
                <td style="width:35%;" align="left">
                    Email
                </td>
            </tr>
        </table>
    </HeaderTemplate>

Note: Please make sure that ItemTemplate table definition matches the header (specially width of each column)  as it will give one table look.

Update: After publishing this article, I got several requests about few things:

  1. Code to update data on click of Update button.
  2. Add paging and code to preserve edit while moving between pages in pageble GridView.
  3. Disable all Edit button in Edit state (so user cannot 'by accident' update another row.

So here is the code ...

1. Code to Update Data on Click of Update Button

The main purpose for this article was to present a different way to use ItemTemplate. I understand that there may be a few questions about updating data which is beyond the scope of this article. Here is the sample code for how to access updated value when user clicks on the Update Button.

protected void bUpdate_Click(object sender, EventArgs e) {
        TextBox tbF = 
	(TextBox)GridView1.Rows[GridView1.EditIndex].FindControl("tbFirstName");
        TextBox tbL = 
	(TextBox)GridView1.Rows[GridView1.EditIndex].FindControl("tbLastName");
        TextBox tbW = (TextBox)GridView1.Rows[GridView1.EditIndex].FindControl("tbWeb");
        TextBox tbE = (TextBox)GridView1.Rows[GridView1.EditIndex].FindControl("tbEmail");
 
        // now you have all values entered by user
        // set those values in dataset and update
        // OR generate UPDATE sql statement here and save values in db.
        // more details / example can be found at 
        // http://msdn.microsoft.com/en-us/library/ms972948.aspx 
        // OR
        // http://www.aspdotnetcodes.com/GridView_Insert_Edit_Update_Delete.aspx
        refreshData();
}
 
private void refreshData()
{
        GridView1.EditIndex = -1;
        ViewState["EditRowID"] = null;
        GridView1.DataSource = CreateTable();
        GridView1.DataBind();
}

The trick here is to use FindControl on currently editing row and pass ID of control which you want to access. And to access currently editing row, you can use GridView1.Rows[GridView1.EditIndex]. Once we are done updating data, change EditIndex to -1 (indicating no row is in edit state); this will restore all rows of grid back to view state (i.e. hide edit template).

For more details on how to update data into the database, please refer to this link.

2. Add Paging and Code to Preserve Edit while Moving Between Pages in Pageble GridView.

 private int iCurrentEdit = -1; 
 protected void GridView1_RowEditing(Object sender, GridViewEditEventArgs e) {
        GridView1.EditIndex = e.NewEditIndex;
        GridView1.DataSource = CreateTable();
        GridView1.DataBind();
        ViewState.Add("editRowIndex", e.NewEditIndex);
        ImageButton editButton;
 
        foreach (GridViewRow row in GridView1.Rows)
        {
                if (row.RowIndex != e.NewEditIndex)
                {
                        editButton = (ImageButton)(row.Cells[1].Controls[0]);
                        editButton.ImageUrl = "./edit_off.gif";
                        editButton.Enabled = false;
                }
                else
                {
                        ViewState.Add("EditRowID", row.DataItemIndex.ToString());
                        if (ViewState["fname"] != null) 
                        // indicates we have values from previous editing 
		      // session which are not saved
                        {
                                // get elements from edit template
                                TextBox tbF = (TextBox)row.FindControl("tbFirstName");
                                TextBox tbL = (TextBox)row.FindControl("tbLastName");
                                TextBox tbW = (TextBox)row.FindControl("tbWeb");
                                TextBox tbE = (TextBox)row.FindControl("tbEmail");
 
                                // restore previous values
                                tbF.Text = ViewState["fname"].ToString();
                                tbL.Text = ViewState["lname"].ToString();
                                tbW.Text = ViewState["web"].ToString();
                                tbE.Text = ViewState["email"].ToString();
                        }
                }
        }
}
 
protected void GridView1_PageIndexChanging(Object objDGName, GridViewPageEventArgs e) {
        
        GridView1.DataSource = CreateTable();
        GridView1.PageIndex = e.NewPageIndex;
        GridView1.EditIndex = -1;
        GridView1.DataBind();
 
        if (iCurrentEdit != -1)
        {
                GridView1_RowEditing(GridView1, new GridViewEditEventArgs(iCurrentEdit));
        }
}
 
protected void GridView1_RowDataBound(Object sender, GridViewRowEventArgs e) {
        if (e.Row.RowType == DataControlRowType.DataRow ||
                e.Row.RowType == DataControlRowType.EmptyDataRow)
        {
                int iEditRowID = int.Parse((ViewState["EditRowID"] == null ? "-2" : 
                ViewState["EditRowID"].ToString()));
                if (iEditRowID != -2 && e.Row.DataItemIndex == iEditRowID)
                {
                        iCurrentEdit = e.Row.RowIndex;
                }
                else if (iEditRowID != -2)
                {
                        ImageButton editButton;
                        editButton = (ImageButton)(e.Row.Cells[1].Controls[0]);
                        editButton.ImageUrl = "./edit_off.gif";
                        editButton.Enabled = false;
                }
        }
}

There are a few things here.

We defined a class level variable called iCurrentEdit with initial value of -1 indicating no row is in edit state.

When user clicks on Edit button,

protected void GridView1_RowEditing(Object sender, GridViewEditEventArgs e)

we loop through the entire grid to find DataItemIndex and when we find it, we store DataItemIndex into ViewState using:

ViewState.Add("EditRowID", row.DataItemIndex.ToString()); 

Now when user clicks to change page,

protected void GridView1_PageIndexChanging(Object objDGName, GridViewPageEventArgs e)

first we call GridView1.DataBind(); which will call RowDataBound for each row.

Finally in RowDataBound event,

protected void GridView1_RowDataBound(Object sender, GridViewRowEventArgs e)

we retrieve value of currently editing DataItemIndex:

int iEditRowID = int.Parse((ViewState["EditRowID"] == null ? "-2" : 
				ViewState["EditRowID"].ToString()));

and then we compare this value against current row's DataItemIndex. If it is the same, then this indicates this is the row in edit state, hence we store RowIndex into iCurrentEdit so; when if (iCurrentEdit != -1) gets executed in PageIndexChanging, there will be a value in iCurrentEdit (if a row is in edit state). If this is the case, then we call:

GridView1_RowEditing(GridView1, new GridViewEditEventArgs(iCurrentEdit)); 

to simulate rowEditClick.

In the above code, there is one potential problem. If the user clicks on edit and changes First Name (or any other field for that matter); does not click on update button and goes to another page to view some information and comes back, the above code will take care of putting the correct row in edit mode, but will not preserve values entered by user. To save those values temporarily, we can use ViewState.

 protected void Page_Load(object sender, EventArgs e) {
        if (!IsPostBack)
        {
                GridView1.DataSource = CreateTable();
                GridView1.DataBind();
        }
        else
        {
                if (ViewState["editRowIndex"] != null) 
                // indicates we have values from edit which needs to be saved in viewstate
                {
                        int iEditRowID = int.Parse((ViewState["editRowIndex"] == 
								null ? "-2" : 
                        ViewState["editRowIndex"].ToString())); // get current 
							// edit row index
 
                        // get all elements from edit template
                        TextBox tbF = (TextBox)GridView1.Rows
					[iEditRowID].FindControl("tbFirstName");
                        TextBox tbL = (TextBox)GridView1.Rows
					[iEditRowID].FindControl("tbLastName");
                        TextBox tbW = (TextBox)GridView1.Rows
					[iEditRowID].FindControl("tbWeb");
                        TextBox tbE = (TextBox)GridView1.Rows
					[iEditRowID].FindControl("tbEmail");
 
                        // save all values into viewstate for future use
                        if (tbF != null)
                        {
                                ViewState.Add("fname", tbF.Text);
                                ViewState.Add("lname", tbL.Text);
                                ViewState.Add("web", tbW.Text);
                                ViewState.Add("email", tbE.Text);
                        }
                }
        }
}
 
private void refreshData()
{
        GridView1.EditIndex = -1;
        ViewState["EditRowID"] = null;
        GridView1.DataSource = CreateTable();
        GridView1.DataBind();
 
        // clear viewstate (this is called from UpdateButtonClicked 
        // and CancelButtonClicked
        ViewState["fname"] = null;
        ViewState["lname"] = null;
        ViewState["web"] = null;
        ViewState["email"] = null;
        ViewState["editRowIndex"] = null;
}

3. Disable All Edit Buttons in Edit State (So User Can Not 'By Accident' Update Another Row

When a user clicks on edit button for any row; the user can not (or should not) be able to click on the edit button of any other row; for that we need to disable all edit buttons when user is in edit mode.

        protected void GridView1_RowEditing(Object sender, GridViewEditEventArgs e)
        {
        ...
        ...
        ImageButton editButton;
                foreach (GridViewRow row in GridView1.Rows)
                {
                        if (row.RowIndex != e.NewEditIndex)
                        {
                                editButton = (ImageButton)(row.Cells[1].Controls[0]);
                                editButton.ImageUrl = "./edit_off.gif";
                                editButton.Enabled = false;
                        }
                }
        ...
        ...
        }
        protected void GridView1_RowDataBound(Object sender, GridViewRowEventArgs e)
        {
        ...
        ...
        else if (iEditRowID != -2)
        {
                ImageButton editButton;
                editButton = (ImageButton)(e.Row.Cells[1].Controls[0]);
                editButton.ImageUrl = "./edit_off.gif";
                editButton.Enabled = false;
        }
}

Here, when the user clicks on Edit button, we loop through the entire grid and Disable edit button if its index does not match with the current edit row index. To access edit button, we use:

editButton = (ImageButton)(e.Row.Cells[1].Controls[0]); 

As you already know, we have created an entire edit template in one table cell, hence the second cell (at cell index 1) will be for edit/update/cancel buttons, and from that, we access the first control which is the edit button. We disable it and change image icon to indicate this row is not available for editing. We are doing an almost similar thing on row bound which takes care of rows which are not on the current page. When user goes from currently editing page to another page, we wanted to disable editing which we took care of in the RowBound event.

Summary

I hope this will help you to create an easy to use GridView.

Happy coding.

History

  • Initial submission on September 14, 2009
  • Updated code for various events on September 23, 2009

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Jay Thakar
Software Developer Dimensional Strategies Inc
Canada Canada
He is a results oriented professional building on 10 years of progressive accomplishments in Software Development and Information Technology. He loves to make and help making user friendly web applications which provides enhanced user experience.


To contact Jay, email him at jay@thakar.info


Visit his blog a Day as {Developer} where you will find many of his articles.


View his LinkedIn Profile


View his Website Jay.Thakar.Info

Comments and Discussions

 
GeneralRe: Always holding the edit panel when paging Pinmemberlqluo20-Sep-09 17:04 
AnswerRe: Always holding the edit panel when paging PinmemberJay Thakar23-Sep-09 15:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140718.1 | Last Updated 24 Sep 2009
Article Copyright 2009 by Jay Thakar
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid