The In-Place Editing Feature

The In-Place Editing Feature

In-place editing in a DataGrid control is ruled by a special type of column named EditCommandColumn. You need to add exactly one column of that type to the grid to enable in-place editing. The text displayed in each cell of the column cannot be a data-bound or templated expression. It must be a literal string such as "Edit" or something you can define by using HTML tags, such as an image. You configure the EditCommandColumn class by specifying the text displayed for the Edit command button and for the link buttons that will let the user save or cancel an operation. For these three events—edit, update, and ­cancel—you must also provide handlers that control how the in-place editing begins, commits, or rolls back, respectively.

The DataGrid control provides a built-in mechanism for editing rows, but you are still responsible for supplying the actual code that updates the underlying data source. You also must provide any start-up or cleanup operation that needs to be accomplished when the editing begins or is canceled. I’ll say more about this in a moment.

You need to be acquainted with the following EditCommandColumn properties: EditText, UpdateText, and CancelText. They let you configure the appearance of the column that users click to switch to edit mode. The text you assign to these properties is typically rendered as a link button. You can change the button style from a link button to a push button by using the ButtonType property. An EditCommandColumn also features graphical properties typical of other column types, including HeaderText, HeaderStyle, FooterText, FooterStyle, and ItemStyle.

Enabling In-Place Editing

You enable in-place editing in a DataGrid control by adding an EditCommandColumn column anywhere in the grid. The following code snippet adds a captionless column with a yellow background. As shown in Figure 4-1, the display text is Edit for all items.

<asp:EditCommandColumn runat="server"
    <itemstyle BackColor="yellow" />
Figure 4-1
A DataGrid control with a captionless column.

When the user clicks the Edit command button, the link button raises a server-side event. The page is posted back to the Web server and processed. The page is then sent back to the client with the clicked row rendered in a different manner—the Edit link button in Figure 4-1 is replaced by two link buttons: one to save changes and one to cancel any updates.

Registering Event Handlers

To edit in a DataGrid control, you must register three event handlers. You can do this declaratively by using the OnEditCommand, OnUpdateCommand, and OnCancelCommand attributes.

<asp:DataGrid id="grid" runat="server"  

You can start with the following basic code when writing these handlers in your applications. Figure 4-2 shows how the grid changes as a result of this code.

public void EditCommand(Object sender, 
                        DataGridCommandEventArgs e) 
    // Set the current item to edit mode
    grid.EditItemIndex = e.Item.ItemIndex;

    // Refresh the grid

public void UpdateCommand(Object sender, 
                          DataGridCommandEventArgs e) 
    // TODO: Retrieve new text and update the data source

    // Reset the edit mode for the current item
    grid.EditItemIndex = -1;

    // Refresh the grid

public void CancelCommand(Object sender, 
                          DataGridCommandEventArgs e) 
    // Reset the edit mode for the current item
    grid.EditItemIndex = -1;

    // Refresh the grid
Figure 4-2
A DataGrid control in edit mode.

In Figure 4-2, notice that the Edit link button has been replaced by a pair of OK and Cancel link buttons. You control the text of these link buttons by using the EditText, UpdateText, and CancelText properties.

The DataGrid control’s EditItemIndex property gets or sets the index of the item currently being edited. When the in-place editing begins, you set this property to a valid index, which is the index of the item responsible for the postback event. When the editing phase terminates—no matter the reason—you set EditItemIndex to -1.

The value of the EditItemIndex property affects primarily the DataBind method. The DataBind method scrolls the grid’s Items collection and draws the corresponding table row for each element that falls in the range of visible items. If an item’s index matches the value in EditItemIndex, the corresponding row is rendered in edit mode by using text boxes instead of literals. If EditItemIndex is set to -1, no item is rendered in edit mode.

Understanding Read-Only Columns

Notice in Figure 4-2 that not all fields are editable. In particular, the ID and the Employee Name columns are not affected by in-place editing. The default editing mechanism does not work with all types of columns; it applies only to columns of the BoundColumn type. In the sample code we’re examining, the Employee Name column is a templated column. The ID column is explicitly marked as a read-only column.

<asp:BoundColumn runat="server" 
    DataField="employeeid" HeaderText="ID" Readonly="true">												
    <itemstyle BackColor="lightblue" 
        HorizontalAlign="right" />

Button and link columns are not editable, although in-place editing can be performed on their contents if you define them to use templates. You must define the editing layout of templated columns to make them updatable. I’ll cover this in detail in the next section.

Changing the Style of the Row

The DataGrid control’s EditItemStyle property lets you modify the default appearance of the row being edited. For example, you can set a contrasting background color, change the font family, or even adjust the text alignment. Look at Figure 4-3 to see the effect of the following declaration:

<EditItemStyle BackColor="yellow" Font-Bold="true" />
Figure 4-3
The EditItemStyle property changes the appearance of the edited row.

Notice that in Figure 4-3, the font style of the Employee Name cell is replaced by boldface and the ID cell in the selected row is unaffected by the new font style. A column that is marked read-only (in this case, the ID cell) is ignored when new styles are applied. A templated column, as well as any other type of column, is subject to the precedence rules for styles. The EditItemStyle has the highest priority in a grid, so it ends up overriding any other common settings for all columns. From highest to lowest priority, other styles follow like this: SelectedItemStyle, AlternatingItemStyle, ItemStyle.

Using Images

The value of the text properties EditText, UpdateText, and CancelText can include any HTML tag, and this can lead to a number of interesting application enhancements. For example, you can employ small images instead of, or together with, plain text to let users know where to click to begin editing. The following code shows how to proceed. Figure 4-4 illustrates the final result.

<asp:EditCommandColumn runat="server" 
    EditText="<img src=edit.gif border=0 align=absmiddle 
        alt='Edit this item'>"
    UpdateText="<img src=ok.gif border=0 align=absmiddle 
        alt='Save changes'>"
    CancelText="<img src=cancel.gif border=0 align=absmiddle 
        alt='Cancel editing'>">
    <itemstyle BackColor="yellow" HorizontalAlign="center" />

For better graphical results, the <img> tag’s border attribute has been set to 0 and the image alignment to absmiddle.

Figure 4-4
An editable grid that uses images and tooltips.
Reading Text from Edited Fields

So far you know how to set up an editable row and how to control the events that govern the overall editing process. In most cases, you don’t need to execute code in the EditCommand and CancelCommand event handlers that is more complicated than the basic code shown earlier in this chapter. Where you certainly do need to add significant code is in the UpdateCommand event handler, which runs when the user clicks the OK button to save changes. To add this code, you must accomplish two tasks. The first is to retrieve the text that is currently in the text boxes. The second is to update to the data source.

To retrieve the new text that users enter in the text boxes, you have to access the text boxes generated by the DataGrid control. Usually this is an easy task that can be elegantly solved by using the FindControl method on the page. The FindControl method, however, requires you to pass the control’s ID. But what’s the ID of a control that has been automatically generated?

You don’t really need to know the ID to locate a control in a grid row. The HTML structure of the generated row is pretty simple and, more important, can be easily understood from the source code of the client page.

<td><input name="..." value="..."></td>

From within a grid event handler, you access a particular cell of the current item using the following expression. Of course, nColumnIndex represents the 0-based index of the column.


With this expression, you address the <td> tag that refers to the column you want to read from. This <td> tag contains only one control—just the <input> tag you are searching for. You get the corresponding ASP.NET text box control by using this code:


The UpdateCommand event handler for the sample application we are examining changes as follows. Figure 4-5 shows the effect of this code.

public void UpdateCommand(Object sender, 
                DataGridCommandEventArgs e) 
    // Retrieve the new text 
    int nColPositionIndex = 2; // 0-based position of the 
                               // column
    int nColFromIndex = 3;     // 0-based position of the 
                               // column

    TextBox txtPosition, txtFrom;
    txtPosition = (TextBox) e.Item.Cells[nColPositionIndex].Controls[0];
    txtFrom = (TextBox) e.Item.Cells[nColFromIndex].Controls[0];

    // Show the current text
    StringBuilder sb = new StringBuilder("");
    sb.Append("<b>[Position] will contain:</b> ");
    sb.Append("<b>[From] will contain:</b> ");
    lblCurrent.Text = sb.ToString();

    // Update the underlying data source
    // TODO

    // Reset the edit mode for the current item
    grid.EditItemIndex = -1;

    // Refresh the grid
Figure 4-5
After the user edits a row and clicks the OK button, the grid is restored to its original appearance. The label at the bottom of the grid explains that new text was successfully detected.

After the new text has been successfully read, you should proceed with the update and then make sure the grid’s data source reflects the current state of the underlying data store.

Updating the Data Source

To update the data source, you can use a single SQL statement or update the grid’s data set in memory and proceed with a batch update later. Whatever route you take has little to do with the DataGrid control itself. Choosing between single SQL statements or a batch update is more an application choice. Frequent and numerous changes, together with few other clients updating the table, suggest you opt for a batch update. Other scenarios—for example, a high level of concurrency—suggest you do the opposite.

In both cases, for the sake of consistency in the DataGrid control, make sure you properly update the data source that the grid is bound to. This might require a trip to the database. If you know for certain that no trigger, expression-based column, or auto-increment column is part of the database, you can avoid querying again. If there are no other side effects to updating the database that you are aware of, you can update the grid’s data source directly in memory with the text read from the text boxes.

Using Stored Procedures

As an alternative to the approaches for updating the data source described in the previous section, you could use a stored procedure with one or more output parameters that returns the updated values read directly from the database. For example, the following stored procedure sets the Title and Country fields of the specified employee in the Employees table of Northwind. (There are only two editable columns in the grid.) In addition, the stored procedure re-reads and returns the content from the fields. If any server-side code modifies the fields when the fields are updated, the changes are instantly detected and returned to your ASP.NET code.

    @nEmpID int,                      -- EmployeeID
    @sPosition nvarchar(30),          -- Value to update field 
                                      --  TITLE 
    @sCountry nvarchar(15),           -- Value to update field 
                                      --  COUNTRY 
    @NewPosition nvarchar(30) OUTPUT, -- returns current value 
                                      --  of TITLE
    @NewCountry nvarchar(15) OUTPUT)  -- returns current value 
                                      --  of COUNTRY

UPDATE Employees SET 
WHERE employeeid=@nEmpID

    @NewPosition = Title, 
    @NewCountry = Country 
FROM Employees WHERE employeeid=@nEmpID

You use this procedure from ASP.NET in the following manner:

SqlConnection conn = new SqlConnection(txtConn.Text);
SqlCommand cmd = new SqlCommand("MySampleUpdate", conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter p1, p2, p3, po1, po2;

// Set the EmployeeID param to identify the row to update
p1 = new SqlParameter("@nEmpID", SqlDbType.Int);
p1.Direction = ParameterDirection.Input;
p1.Value = grid.DataKeys[e.Item.ItemIndex];

// Set the Position param to pass the new value
p2 = new SqlParameter("@sPosition", SqlDbType.NVarChar, 30);
p2.Direction = ParameterDirection.Input;
p2.Value = txtPosition.Text;

// Set the Country param to pass the new value
p3 = new SqlParameter("@sCountry", SqlDbType.NVarChar, 15);
p3.Direction = ParameterDirection.Input;
p3.Value = txtFrom.Text;

// Output param to get the new value for POSITION
po1 = new SqlParameter("@NewPosition", SqlDbType.NVarChar, 30);
po1.Direction = ParameterDirection.Output;

// Output param to get the new value for COUNTRY
po2 = new SqlParameter("@NewCountry", SqlDbType.NVarChar, 15);
po2.Direction = ParameterDirection.Output;

// Executes the procedure

The contents of the stored procedure’s two output parameters can be accessed by using their Value properties. Once you know the values stored in the table, you can avoid a full reloading of the grid’s data source to show current data. To summarize, when you are going to reload the whole data set after the update, use a simple SQL statement and don’t worry about possible triggers or anything else that could modify the data you submitted. Requerying for data will fix it all. This approach is shown in the EditRowsWithUpdate.aspx application, the full source code for which is available on the companion CD.

When you want to optimize the code and avoid reloading the data every time a row is updated, use the MySampleUpdate stored procedure we examined as your starting point. You perform the update and obtain the current value for the fields the user attempted to change. You then modify the cached grid’s data source and rebind. As a result, you have perfectly synchronized data without reloading anything from the database. The following code shows a possible way to update cached data. The full source code for the EditRowsWith­UpdateSP.aspx application is available on the companion CD.

void UpdateCachedData(int nEmpID, String sNewPosition, 
                        String sNewCountry)
    // Retrieves the grid's data source from Session or from 
    // someplace else you cached it
    DataSet ds = (DataSet) Session["MyData"];
    DataTable dt = ds.Tables["MyTable"];

    // Retrieves the row to update based on the primary key 
    // Only one row is expected
    DataRow[] adr = dt.Select("employeeid=" + nEmpID);

    // Updates the row and persists changes with the DataTable
    adr[0]["title"] = sNewPosition;
    adr[0]["country"] = sNewCountry;

    // Caches the updated DataSet for further use
    Session["MyData"] = ds;
Using Triggers

A typical circumstance in which the data you send can be changed on-the-fly in the database involves update triggers. Let’s see how the two approaches I described in the preceding section—using output parameters with a stored procedure and then updating the local cache of data, and reloading data directly from disk—work with triggers. Consider the following code:

IF UPDATE(Country) 
    UPDATE Employees SET Country='United Kingdom'
        WHERE Country = 'UK'

The trigger simply replaces 'UK' with 'United Kingdom' during each update. The final result does not change for either approach. The trigger does its job and the DataGrid control is correctly updated, as Figure 4-6 shows.

Figure 4-6
The database modified the original text the user entered. The DataGrid control shows the correct data set.
A Long List of Drawbacks

In-place editing doesn’t require much from you. The only aspect you have to worry about is updating. Despite what I say, I’m very hesitant about promoting this type of in-place editing for a wide range of applications. It definitely has a number of issues that I don’t consider minor, such as the following:

  • Only visible fields are updatable.

  • The default text displayed by the text boxes is the plain contents of the cell. With precalculated columns, you end up modifying a column that does not map exactly to a field in the target database.

  • If you used special column formatting via the DataFormatString property, the extra text will appear in the text box.

  • No validation is possible on the client.

  • You cannot control the way in which the data entry takes place. What if you want to let users pick a value from a list?

  • You can update only BoundColumn columns. What if you want to change the data-bound URL of a link column? Or the path of an image?

  • Retrieving the new text is a bit impractical because you have to walk through a forest of array indexes.

You can work around all these issues by using edit templates. In other words, any editable column features a special template that provides the custom cell layout when in editing mode. This approach isn’t free from problems either, but the problems do not in any way limit your programming power. Only templated columns can have a template for editing. This means that if you want a special editing template for a column originally designed as a bound or link column, you need to turn that column into a templated column.