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
.
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 :)).
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:
- Code to update data on click of Update button.
- Add paging and code to preserve edit while moving between pages in pageble
GridView
. - 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");
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)
{
TextBox tbF = (TextBox)row.FindControl("tbFirstName");
TextBox tbL = (TextBox)row.FindControl("tbLastName");
TextBox tbW = (TextBox)row.FindControl("tbWeb");
TextBox tbE = (TextBox)row.FindControl("tbEmail");
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)
{
int iEditRowID = int.Parse((ViewState["editRowIndex"] ==
null ? "-2" :
ViewState["editRowIndex"].ToString()));
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");
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();
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