Edit the Header

Let Your End Users Customize DataGrid Headers

CoreCoder

LANGUAGES: C#

ASP.NET VERSIONS: 1.0 | 1.1 | 2.0

 

Edit the Header

Let Your End Users Customize DataGrid Headers

 

By Dino Esposito

 

I agree that the title of this article may sound a bit weird at first. Edit the headers of a DataGrid control? Why on earth should I need to let my users do this? That was exactly my knee-jerk reaction when a reader sent me such an inquiry. That reader got a peremptory order from her boss and was fully engaged in the task of building an Excel worksheet Web emulator. She wanted to enable her users to rename the worksheet's columns at will; and the worksheet was rendered using a DataGrid control. Hence the question: How can I let my users edit the header of a DataGrid column?

 

Although the question clearly touches on a very specific scenario, I believe there are at least a couple of reasons to publicly address it in this column. First is that the solution demonstrates that you can change virtually everything in a DataGrid. From a wider perspective, this little example introduces you to a new generation of very rich Web controls that supply an embedded user interface for in-place editing and configuration. The Web Parts Framework of ASP.NET 2.0 provides the first example of this approach. The Web Parts in your page can be removed, added, and their user interface configured through built-in controls. These configuration controls are referenced in the page as regular controls and are hidden until the Web Parts manager is turned into edit mode. For a preview of ASP.NET 2.0 Web Parts check out Stephen Walther's article Meet the Web Part Framework.

 

In this article, you'll see how to apply the same mechanics to a DataGrid control to edit the header's text of displayed columns. The results won't be as complex and sophisticated as that of ASP.NET 2.0 Web Parts, but will give you a good head start on such a new feature.

 

Prepare for Customization

The first thing you need to figure out is how can users trigger the customization process. If you're designing a new control - for example, a sort of grid control - you could embed all the logic and the user interface in the control itself. If the extra layer of code gets too complex and memory consumptive, you should consider the creation of a distinct control to optimize the amount of markup and script code generated for the browser.

 

In this article, I'll consider a plain ASP.NET page with a child DataGrid control. The page includes some buttons to start and stop edit mode. The working model entails that users click to edit the headers. When this happens, the page posts back and the grid redraws column headers using text boxes instead of literals. At the same time, the button used to start edit mode disappears and makes room for OK and Cancel buttons. Users will click these buttons to confirm or cancel changes. The DataGrid tracks the working mode in the ItemCreated event and replaces default literals with text boxes. Figure 1 lists the ASPX source code of the page.

 

<%@ Page Language="C#" Src="EditHeader.aspx.cs"

  Inherits="MyPage" %>

Edit Grid's Header

  autogeneratecolumns="false" onitemcreated="ItemCreated">

  

  

    

      datafield="companyname"/>

    

      datafield="country"/>

      

  onclick="EditHeaders"/>

  visible="false" onclick="EditHeaders_OK"/>

  visible="false" onclick="EditHeaders_Cancel" />

Figure 1: The source code of the sample ASP.NET page.

 

Looking at the code in Figure 1, there are two points worth noting (one of which is not strictly related to the customization of the DataGrid). The combined use of the Src and Inherits attributes of the @Page directive lets you create any page in code-behind mode outside of Visual Studio.NET. This is a programming aspect of ASP.NET that often goes unnoticed, let alone used. (It's a victim of the analogous CodeBehind but Visual Studio.NET-specific attribute. It's worth recalling that the ASP.NET runtime tolerates the CodeBehind attribute in the @Page directive, but ignores it when processing the page.) The file referenced by the Src attribute can have any name (that is, it isn't forced to the page.aspx.cs convention) and contains the source code attached to the layout. The Inherits attribute indicates which classes in the file should be used as the code-behind class for the page.

 

The second aspect to note relates strictly to the example. Bear in mind that when the columns of a DataGrid are automatically generated, the Columns collection of the class is empty. In other words, if you plan to programmatically use the members of the Columns collection, make sure that AutoGenerateColumns is set to false. The contents of the Columns collection has a one-to-one dependency on the contents of the tag. Figure 2 shows how the sample page looks in the beginning.

 


Figure 2: The sample page in action before the headers enter in edit mode.

 

Enter Edit Mode

When the user clicks the Edit button, the following code runs to turn the grid's headers into edit mode:

 

void EditHeaders(object sender, EventArgs e)

{

  SetEditMode(true);

}

 

The SetEditMode method accepts a Boolean argument denoting whether the edit mode should be turned on (true) or off (false). The SetEditMode method does a few more things, as well. In particular, it manages the page controls that allow users to enter and exit the edit mode. It also instructs the DataGrid to render headers in edit mode by setting the RenameColumnHeaders persistent page property (see Figure 3).

 

void SetEditMode(bool mode)

{

  // Turn on/off the grid mode.

  RenameColumnHeaders = mode;

  // Configure the page UI.

  Edit_OK.Visible = mode;

  Edit_Cancel.Visible = mode;

  Edit.Visible = !mode;

  // Clear the array of text boxes used to edit headers.

  if (!mode)

    __headerEditFields.Clear();

  // Perform binding.

  BindData();

}

Figure 3: Render headers in edit mode by setting the RenameColumnHeaders property.

 

In this example, RenameColumnHeaders is a Boolean property implemented through the page's viewstate. If you wrap this solution in a new custom control, it should be coded as a DataGrid's private persistent property. Basically, the ItemCreated event of the DataGrid checks the RenameColumnHeaders property to determine how headers should be rendered. When the headers are going to be rendered in edit mode, all the dynamic text boxes used are gathered in the __headerEditFields array. Finally, the call to BindData triggers the data binding process, at the end of which, the DataGrid generates the markup for the browser.

 

Render the DataGrid

Immediately after creating the header, the DataGrid control fires the ItemCreated event. By hooking the event, developers can still interact with the control's machinery and modify what's just been created. This is exactly what happens here. Figure 4 shows the code for the ItemCreated event.

 

void ItemCreated(object sender, DataGridItemEventArgs e)

{

  if (e.Item.ItemType == ListItemType.Header)

    SetupHeaderItem(e);

}

 

void SetupHeaderItem(DataGridItemEventArgs e)

{

  foreach(TableCell cell in e.Item.Cells)

  {

    if (RenameColumnHeaders)

    {

      TextBox t = new TextBox();

      SetupEditTextBox(t);

      t.Text = cell.Text;

      cell.Controls.Add(t);

      if (__headerEditFields == null)

        __headerEditFields = new ArrayList();

      __headerEditFields.Add(t);

    }

  }

}

 

void SetupEditTextBox(TextBox t)

{

  t.BackColor = Color.LightCyan;

  t.BorderStyle = BorderStyle.Outset;

  t.BorderWidth = Unit.Point(0);

  t.Width = Unit.Percentage(100);

  t.Font.Name = grid.Font.Name;

  t.Font.Size = grid.Font.Size;

}

Figure 4: The DataGrid code that renders the header text through TextBox controls.

 

The ItemCreated event fires whenever a DataGrid item is created; not just the header, but also the footer, data items, and pager. The type of item being created is indicated in the event argument data structure - an instance of the DataGridItemEventArgs class:

 

if (e.Item.ItemType == ListItemType.Header)

{...}

 

The header is simply a table row and is implemented through the DataGridItem class. The DataGridItem class extends TableRow with a few DataGrid-specific properties. A DataGridItem object can access the list of cells (column headers, actually) through the Cells collection inherited from the base class:

 

foreach(TableCell cell in e.Item.Cells)

{...}

 

Each iteration of the loop processes a distinct column's header. As the code in Figure 4 fully demonstrates, you create a new TextBox object, adjust its visual settings, and add it to the Controls collection of the table's cell. A reference to each created TextBox is also added to an internal array to facilitate updates to the grid columns.

 

The Text property of the cell reflects the HeaderText property of the DataGridColumn object as defined in the tag. This value becomes the default value of the dynamically created TextBox. The value of the RenameColumnHeader Boolean property determines whether the header rendering should go through the normal procedure and display literals, or switch to the custom code that creates and handles text boxes.

 


Figure 5: When the user clicks the Edit button, the page posts back and renders the DataGrid in edit mode. The Edit button is temporarily hidden, replaced by a pair of OK/Cancel buttons.

 

Figure 5 shows the page in action after the user has clicked to edit the headers. Users are shown a grid with text boxes in the header and can freely edit the text. The Edit button, which caused the postback, is temporarily hidden and replaced with OK and Cancel buttons. To persist changes, users simply need to click the OK button. The Cancel button will cancel all changes and restore the previous values.

 

Let's take a look at the code attached to the OK and Cancel buttons (see Figure 6). The code to cancel the editing is straightforward. It simply calls SetEditMode to update the user interface and rebind the data. To confirm and then reflect changes, however, one more step is required. The code loops through all the TextBox controls referenced in the internal array of TextBox, retrieves the current text and assigns it to the HeaderText property of each DataGrid column. Again, note that this code won't work if you have the grid generate the columns automatically. If AutoGenerateColumns is true, the Columns collection is empty, and this code will simply throw a null exception.

 

void EditHeaders_Cancel(object sender, EventArgs e)

{

  SetEditMode(false);

  BindData();

}

 

void EditHeaders_OK(object sender, EventArgs e)

{

  int i = 0;

  foreach(TextBox t in __headerEditFields)

  {

    grid.Columns[i].HeaderText = t.Text;

    i++;

  }

  SetEditMode(false);

  BindData();

}

Figure 6: Event handlers for the OK and Cancel buttons.

 

Persist Changes across Postbacks

The dynamically created TextBox controls are stored in an internal array, so to retrieve all of them you need to loop through the array. The array is initialized and filled during ItemCreated when RenameColumnHeaders is true. Next, the page is displayed. Then, when the user clicks to persist changes, a new request is processed on the server in a new, clean runtime environment. Nevertheless, the array is still there, correctly instantiated and filled with the correct values.

 

But how? What guarantees that the array will retain its state across postbacks? Is it viewstate? No; the __headerEditFields array isn't stored to viewstate. In fact, it isn't persisted to any medium, explicitly or implicitly. In addition, it's easy to verify that the array survives only those postbacks that occur when the user clicks the OK button.

 

When an ASP.NET page posts back, all controls are reinitialized; the DataGrid is no exception. When this happens, the DataGrid fires the ItemCreated event. At this time however, viewstate hasn't been restored, so RenameColumnHeaders is still set to true. Subsequently, the TextBox controls are recreated and the array is refilled. Next, viewstate is restored and the Load event fires. The postback event is processed and RenameColumnHeaders is set to false. When the page generates the output, the array has been emptied, and remains so for the next postback.

 

As a matter of fact, the array survives all the postbacks it needs with no extra data being stored to viewstate. This trick is behind the implementation of many built-in data-bound controls, including DataGrid and such list controls as CheckBoxList and RadioButtonList. Keep this in mind if you ever need to write custom list-bound controls.

 

Dino Esposito is a Wintellect trainer and consultant who specializes in ASP.NET and ADO.NET. Author of Programming Microsoft ASP.NET and Introducing ASP.NET 2.0, both from Microsoft Press, Dino helps several companies to architect and build effective products and components for ASP.NET developers. Dino is the cofounder of http://www.VB2TheMax.com, a popular portal for VB and .NET programmers. Write to him at mailto:[email protected] or join the blog at http://weblogs.asp.net/despos.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish