Fully Editable GridView in ASP.NET 2 using C#

 
 

How to Make a Fully Editable GridView in ASP.NET 2

This article describes how I made a fully editable gridview in C# using ASP.NET 2. The idea is that the GridView looks and works like an Excel spreadsheet. You see all the cells in the table, and you can edit any of the cells you like, and they are automatically updated (i.e. saved to the database). This is very often how people expect data tables to work in web pages, and I've often seen people clicking in vain on un-editable gridview cells, somehow expecting to edit them, and not understanding that they need to click an Edit button at the end of the row.

How to make a fully editable gridview

Here is how to make it:

  1. Make a datasource and link it to your database table. I used an AccessDataSource in this example, but a SqlDataSource will work just as well.
  2. Make a GridView and assign its datasource to be the datasource you just made.
  3. Edit each of the columns of the GridView and turn them all into Template Fields.
  4. Now got to Edit Templates and edit each template field in turn. Go to the ItemTemplate for each column. You will see that the GridView has put a label into each ItemTemplate. This is the label that displays the cell data for the grid. Delete the Label and put a TextBox in there instead. Edit the DataBindings of the textbox and set the Field Binding of the Text property to be the database table field that this column corresponds to.

By this point you will have a fully editable gridview with just one problem... None of the data that you edit will be saved to the database. Our challenge is to make it so that the GridView looks for changed data and saves it when you:

  • Move from one field to another field
  • Click a big friendly "Save" button at the bottom of the table
  • Go to the next page in the table if the data is paged

I tackled these factors like this:

  1. I ignore what happens when people move from one field to another. I only save the data when they click Save or when they move to another page.
  2. I Created a "Save" button below the GirdView that does a postback when you click it. I made it so that doing a postback saves all the data.
  3. I found that moving to another page causes a postback, and so this will trigger my save routine too.

This is how it all works:

First, I edited my source code to get rid of unwanted TextBoxes that are automatically put in there by the GUI. There is no need for EditItem TextBoxes, as our GridView never goes into edit mode.

I also put in a HiddenField in the first column of my GridView and bound this to the Primary Key of the table (in my case "ID"). I did this so that I can find the primary key of the row that needs saving when I get to my save routine.

I also changed the border style of the TextBoxes so that they don't have borders. I think this makes things less cluttered and look more like an excel spreadsheet.

The final source code for the GridView ended up like this:

<asp:GridView ID="GridView1" runat="server" AllowPaging="True" AutoGenerateColumns="False"
BackColor="White" BorderColor="#CCCCCC" BorderStyle="None" BorderWidth="1px"
CellPadding="3" DataKeyNames="id" DataSourceID="AccessDataSource1">
<FooterStyle BackColor="White" ForeColor="#000066" />
<Columns>
<asp:BoundField DataField="id" HeaderText="id" InsertVisible="False" ReadOnly="True"
SortExpression="id" />
<asp:TemplateField HeaderText="project_number" SortExpression="project_number">
<ItemTemplate>
<asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("project_number") %>'
OnTextChanged="TextBox_TextChanged" BorderStyle="None"></asp:TextBox>
<asp:HiddenField ID="HiddenField1" runat="server" Value='<%# Eval("id") %>' />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="description" SortExpression="description">
<ItemTemplate>
<asp:TextBox ID="TextBox2" runat="server" Text='<%# Bind("description") %>'
OnTextChanged="TextBox_TextChanged" BorderStyle="None"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="manager" SortExpression="manager">
<ItemTemplate>
<asp:TextBox ID="TextBox3" runat="server" Text='<%# Bind("manager") %>'
OnTextChanged="TextBox_TextChanged" BorderStyle="None"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
</Columns>
<RowStyle ForeColor="#000066" />
<SelectedRowStyle BackColor="#669999" Font-Bold="True" ForeColor="White" />
<PagerStyle BackColor="White" ForeColor="#000066" HorizontalAlign="Left" />
<HeaderStyle BackColor="#006699" Font-Bold="True" ForeColor="White" />
</asp:GridView>

As you can see, I've made a TextChanged function, and assigned each TextBox to have its OnTextChanged event point to this function. The idea here is that every time a TextBox value is changed, the TextChanged event fires, and we know that a certain cell has been edited. What we actually do is this:

  • We have an array of booleans values called rowChanged.
  • In Page_Load we define the size of this array to match the number of rows in the GridView, and assign false to each element in the array.
  • Each time a TextBox_TextChanged event fires, we work out which row the event fired from and mark the corresponding value in rowChanged to be true.
  • In Page_PreRender (one of the last events to fire before a page is actually drawn) we look through the rowChanged array for all true values. For each row that we now know has changed, we find the new values in the TextBoxes for that row. We get the primary key value from the HiddenField, and all the cell values from the TextBox.Text values. Amazingly, during Page_PreRender, the textboxes have the changed values that you put in them from before the PostBack, probably thanks to the ViewState of the GridView. Once we have the values, we have enough info to be able to construct an SQL statement to update the row with the new data.
  • Because clicking the Save button causes a postback, and also moving to the next page of a GridView causes a postback, the data automatically gets saved when you do either of these operations. The bug is that data does not get saved if you click Back in your browser.

Here is the source code:

public partial class fully_editable_gridview : System.Web.UI.Page
{
bool [] rowChanged;
protected void Page_Load(object sender, EventArgs e)
{
int totalRows = GridView1.Rows.Count;
rowChanged = new bool[totalRows];
if (Page.IsPostBack)
{
// Do Page load code...
}
}

protected void TextBox_TextChanged(object sender, EventArgs e)
{
TextBox thisTextBox = (TextBox)sender;
GridViewRow thisGridViewRow = (GridViewRow)thisTextBox.Parent.Parent;
int row = thisGridViewRow.RowIndex;
rowChanged[row] = true;
}

protected void Page_PreRender(object sender, EventArgs e)
{
if (Page.IsPostBack)
{
int totalRows = GridView1.Rows.Count;
for (int r = 0; r < totalRows; r++)
{
if (rowChanged[r])
{
GridViewRow thisGridViewRow = GridView1.Rows[r];
HiddenField hf1 = (HiddenField)thisGridViewRow.FindControl("HiddenField1");
string pk = hf1.Value;
TextBox tb1 = (TextBox)thisGridViewRow.FindControl("TextBox1");
string cell1 = tb1.Text;
TextBox tb2 = (TextBox)thisGridViewRow.FindControl("TextBox2");
string cell2 = tb2.Text;
TextBox tb3 = (TextBox)thisGridViewRow.FindControl("TextBox3");
string cell3 = tb3.Text;

// We know the Primary Key and data values for this gridview row, so save this row to the database...
OleDbConnection con = new OleDbConnection(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=P:\PROJECTMANAGEMENT\asp\oldprojects.mdb");
con.Open();
string sql = "update project set project_number='" + cell1 + "', description='"+cell2+"', manager='"+cell3+"' where id="+pk;
OleDbCommand sqlcommand = new OleDbCommand(sql, con);
sqlcommand.ExecuteNonQuery();
con.Close();
}
}
GridView1.DataBind();
}
}
}

All in all I am quite happy with this first attempt at a fully editable GridView. My next modifications to it are to make it look a little more like a spreadsheet, and to experiment with different types of controls such as dropdown lists in the cells instead of just textboxes. I'll keep you posted.

Last updated 8 November 2006