Microsoft & .NET ASP Editing Nested DataGrids in ASP.NET

Editing Nested DataGrids in ASP.NET

Recently, while working on a large ASP.NET application, I decided I wanted to repeat

data that could be displayed conveniently in a DataGrid. I thought naturally that the

DataList was good for repeating things. Why not use the DataList to repeat a DataGrid? I

quickly discovered why not. This nesting relationship seems to be broken.

I searched many newsgroups on the Web and discovered that many of you thought that

nesting a DataGrid inside of a DataList was a good idea, and there also seems to be some

consensus that editing doesn’t work correctly. If your experiences are similar to mine,

you will have discovered that you can nest a DataGrid in the ItemTemplate of a DataList

and switch to edit mode, but the update events don’t seem to find their way back to the

DataGrid. Well, I have a solution. (Actually, I found two solutions, one which I briefly

describe and the other that I will demonstrate.)

If you have been following along in Part 1 and Part 2, you know that you

can express some reasonably complex relationships very quickly by using the template

sections of a DataList, controlling layout with an HTML table, and using UserControls to

implement the template sections of the DataList. In this final installment, I will

demonstrate one example and describe a second that will permit you to successfully repeat

or nest a DataGrid and get the edit and update behavior to respond correctly. If you are

just joining us, I encourage you to read the first two installments of this column in the

VB Today section of www.codeguru.com, as I won’t be repeating

that material here.

Editing Nested Relationships

When you place a DataGrid in the ItemTemplate of a DataList, think of the DataGrid as

just another template. The DataGrid is the template that the DataList will repeat for

every item in the DataList’s data source. Unfortunately, you cannot directly interact with

the control used to create the template in the code-behind because it doesn’t really exist

at runtime; remember, it is a template used to help paint the picture of all rows in the

DataList. To alleviate this problem, we can rely on our friend the UserControl.

Place the DataGrid in a UserControl. Add a template column to the DataGrid and add a

LinkButton in the DataGrid’s ItemTemplate and a LinkButton in the DataGrid’s

EditItemTemplate. The ItemTemplate button is your edit button, and the EditItemTemplate

button is your update button (see Figure 1). To designate the first button as the edit

button, set both the CommandName and Text properties to Edit. To designate the second

button as the update button, set the CommandName and Text properties to Update. Define an

EditCommand and UpdateCommand event handler for the DataGrid. Because the grid is on a

UserControl, we can refer to the DataGrid (but not the UserControl) in the code-behind,

even when we place the UserControl in the DataList. The two event handlers will look

something like those shown in Listing 1.

Figure 1: The template column for a DataGrid, showing how to add an Edit and

Update LinkButton.

Listing 1: Examples of the EditCommand and UpdateCommand event

handlers.

Private Sub Datagrid2_EditCommand(ByVal source As Object, _
  ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
  Handles Datagrid2.EditCommand

  Datagrid2.EditItemIndex = e.Item.ItemIndex
  Datagrid2.DataSource = ACustomer.ContactInformationList
  Datagrid2.DataBind()

End Sub

Private Sub Datagrid2_UpdateCommand(ByVal source As Object, _
  ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
  Handles Datagrid2.UpdateCommand

  ACustomer.ContactInformationList(e.Item.ItemIndex).Number = _
    CType(e.Item.Cells(2).Controls(0), TextBox).Text

  ACustomer.ContactInformationList(e.Item.ItemIndex).Description = _
    CType(e.Item.Cells(3).Controls(0), TextBox).Text

  Datagrid2.EditItemIndex = -1
  Datagrid2.DataSource = ACustomer.ContactInformationList
  Datagrid2.DataBind()
End Sub

The basic behavior of the EditCommand event handler is to set the DataGrid’s

EditItemIndex to the selected row in the DataGrid and rebind the source data. The basic

behavior of the UpdateCommand is to read the new data out of the DataGrid’s generated

template controls, set the EditItemIndex back to -1 (not selected), and rebind the data

source. The hardest part about extracting data from the DataGrid is that one must know

that the EditItemTemplate creates a textbox for each column in the DataGrid by default. (A

TextBox is used for edit mode unless you define a specific template yourself.)

Consequently, we need to find the TextBox control relative to each data cell, read the

value out that control, and perform any type conversions if necessary. An example of this

is indicated in bold font.

After the UserControl is defined with the DataGrid on it, we need to place the

UserControl in the ItemTemplate (at a minimum) for our DataList. Finally, we need to bind

the data source of the DataList and define the data binding statement for each nested

DataGrid.

Recall from Part 2

that the data source we are working with (while it can be any object that implements

IEnumerable) is a strongly typed collection of Customer objects, each containing a

strongly typed collection of ContactInformation objects. Alternatively, represented as a

DataSet, we have many Customers and a one-to-many relationship between Customer objects

and ContactInformation objects.

In my implementation, the DataList is bound to the CustomerList (see Listing 2) and

each UserControl with a DataGrid is bound to a Customer.ContactInformationList. The

binding statement for the UserControl was implemented as HTML block script (see Listing

3), and the code-behind for the UserControl with DataGrid is provided in Listing 4.

Listing 2: Databinding for the DataList occurs in the Page_Load

event.

Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load

  DataList1.DataSource = CustomerList.GetCustomerList
  DataList1.DataBind()

End Sub

Listing 3: Databinding for the nested UserControl with DataGrid was implemented

in the HTML as block script.

<uc1:DataGridUserControl id=DataGridUserControl1 runat="server"
     Data='<%# Container.DataItem %>'></uc1:DataGridUserControl>

Listing 4: The code-behind for the UserControl with DataGrid.

Public Class DataGridUserControl
  Inherits System.Web.UI.UserControl

  [ Web Form Designer Generated Code ]

  Private Sub Page_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
    Datagrid2.DataSource = ACustomer.ContactInformationList
    Datagrid2.DataBind()
  End Sub

  Public Property Data() As Object
  Get
    Return ACustomer
  End Get
  Set(ByVal Value As Object)
    SetData(Value)
  End Set
  End Property


  Private ACustomer As Customer = Nothing
  Public Sub SetData(ByVal data As Object)
    ACustomer = CType(data, Customer)
    Datagrid2.DataSource = ACustomer.ContactInformationList
    Datagrid2.DataBind()
  End Sub

  Private Sub Datagrid2_EditCommand(ByVal source As Object, _
    ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
    Handles Datagrid2.EditCommand

    Datagrid2.EditItemIndex = e.Item.ItemIndex
    Datagrid2.DataSource = ACustomer.ContactInformationList
    Datagrid2.DataBind()

  End Sub

  Private Sub Datagrid2_UpdateCommand(ByVal source As Object, _
    ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
    Handles Datagrid2.UpdateCommand

    ACustomer.ContactInformationList(e.Item.ItemIndex).Number = _
    CType(e.Item.Cells(2).Controls(0), TextBox).Text

    ACustomer.ContactInformationList(e.Item.ItemIndex). _
              Description = CType(e.Item.Cells(3).Controls(0), _
                                  TextBox).Text

    Datagrid2.EditItemIndex = -1
    Datagrid2.DataSource = ACustomer.ContactInformationList
    Datagrid2.DataBind()
  End Sub

End Class

Listing 2 shows that we need to bind the DataList each time the page is loaded. In Part 2, we used IsPostBack

to reduce the number of times we bound the DataList, but in this example we need to get

data all of the way to the each DataGrid and rebinding seems to work best.

Listing 3 demonstrates the HTML script block that binds each Customer object to each

dynamically generated UserControl. It is not obvious from the code that Container.DataItem

represents a Customer object, but we know that the DataList is bound to a CustomerList.

Place the DataGridUserControl in each template where you want it to appear; specifically,

you only need the DataGridUserControl in the DataList’s ItemTemplate to edit the DataGrid

because it is the DataGrid that will be placed in edit mode, not the DataList.

Listing 4 demonstrates the code-behind for the DataGridUserControl. It is the public

Data property that the DataList’s script block calls in Listing 2. And, while you may be

able to reduce the number of times that DataGrid binding occurs, it seems to work best if

data binding occurs on Page_Load—which allows the events to fire—and in the

actual event—for example, in the UpdateCommand event.

To recap, here are the macro steps summarized:

  1. Place the DataGrid on a UserControl.
  2. Define the Edit and Update commands as LinkButtons in a template column in the

    DataGrid.

  3. Implement he DataGrid’s editing events, such as EditCommand and UpdateCommand.
  4. Expose a public property that makes it convenient to send data to the dynamically

    created UserControl.

  5. Add a DataList to a different Web Form.
  6. Place the UserControl containing the DataGrid in the DataList’s ItemTemplate.
  7. Define a script block that assigns the Container.Data item to the UserControl’s public

    property that you added for this purpose.

  8. Each time the DataList’s page loads, bind the DataList to your master data source,

    which will in turn implicitly pump data to each dynamically created UserControl. The

    dynamically created UserControl’s code-behind will take care of binding the detail source

    to the DataGrid.

(Don’t worry; we’ll make all of this code available at http://www.softconcepts.com/source. If you are doing this for the first

time, it can be confusing.)

Why Can’t I Edit a DataGrid Nested in a DataList?

Great. You have successfully displayed the nested DataList and DataGrid controls (see

Figure 2), and when you click Edit, the DataGrid switches to edit mode. However, when you

click Update, nothing happens. What could have gone wrong? Unfortunately, I am not sure

about the answer to this question. It seems that the DataGrid either is not rewiring the

events to each dynamically created UserControl (because the events are part of the

UserControl class), or the DataGrid events aren’t getting fired, or both. Whatever is

happening, or perhaps is not happening, seems to be a bug. In all likelihood, you have an

application to get working and don’t have time to fix bugs in core Web controls. So, let’s

talk about a reasonable workaround.

Figure 2: The DataList showing Customer information and the nested DataGrid

showing Contact information for each Customer.

Removing the DataList from the Equation

What does the DataList really do? From where I am sitting, it looks like, for the most

part, the DataList repeats controls defined in the template. If the only control we define

in the template is a UserControl, it’s easy enough for you and me to load all of the

UserControls we want and bind them individually. In fact, this seems to be a workable

solution to our problem.

If we remove the DataList from the equation and move all of the unique controls to a

UserControl, we can load an instance of our UserControl, give it some data, and add it to

the page’s Controls collection. This doesn’t require very much code and seems to create an

almost identical visual result, and, most importantly, all events seem to fire.

In Listing 5, I added a TextBox for each of the ID, First Name, Last Name, and Email

address to the UserControl using an HTML table to constrain the layout. I also added the

DataGrid in cell 4 followed by an image in cell 5. The new UserControl contains one

templatized row. The key is to load the UserControl for each Customer object, bind the

UserControl to data, and add the dynamically loaded UserControl to the Controls collection

of a control on the Web Form, shown in listing 6.

Listing 5: The code-behind for the new UserControl.

Public Class NewControl
  Inherits System.Web.UI.UserControl

  [Web Form Designer Generated Code ]
  Private Sub Page_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load

    BindData()
      
  End Sub

  Private Sub BindData()
    TextBoxID.Text = ACustomer.ID
    TextBoxFirstName.Text = ACustomer.FirstName
    TextBoxLastName.Text = ACustomer.LastName
    ImagePicture.ImageUrl = ACustomer.Picture
    Datagrid2.DataSource = ACustomer.ContactInformationList
    Datagrid2.DataBind()
  End Sub

  Private ACustomer As Customer

  Public Property Data() As Object
  Get
    Return Data
  End Get
  Set(ByVal Value As Object)
    SetData(Value)
  End Set
  End Property

  Private Sub SetData(ByVal Value As Object)
    ACustomer = CType(Value, Customer)
    Me.BindData()
  End Sub

  Private Sub Datagrid2_EditCommand(ByVal source As Object, _
    ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
    Handles Datagrid2.EditCommand

    Datagrid2.EditItemIndex = e.Item.ItemIndex
    BindData()

  End Sub

  Private Sub Datagrid2_UpdateCommand(ByVal source As Object, _
    ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) _
    Handles Datagrid2.UpdateCommand

    ACustomer.ID = TextBoxID.Text
    ACustomer.FirstName = TextBoxFirstName.Text
    ACustomer.LastName = TextBoxLastName.Text
    ACustomer.Email = TextBoxEmail.Text

    ACustomer.ContactInformationList(e.Item.ItemIndex).Number = _
        CType(e.Item.Cells(2).Controls(0), TextBox).Text

    ACustomer.ContactInformationList(e.Item.ItemIndex). _
              Description = _
              CType(e.Item.Cells(3).Controls(0), TextBox).Text

    Datagrid2.EditItemIndex = -1
    BindData()

  End Sub
End Class

Listing 6: The code-behind that dynamically loads a UserControl for each object

in my master data source.

Private Sub Page_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load

  Dim ACustomer As Customer

  For Each ACustomer In CustomerList.GetCustomerList
    Dim MyControl As Control = LoadControl("NewControl.ascx")
    CType(MyControl, NewControl).Data = ACustomer
    Target.Controls.Add(MyControl)
    Target.Controls.Add(New LiteralControl("<BR>"))
  Next

End Sub

Listing 5 shows that each time the UserControl gets data or its edit state changes, we

rebind to all of the controls in the UserControl. The one piece left out is that we may

have to devise a scheme for making the TextBox controls, such as that used to store the

ID, FirstName, LastName, and Email address read only when we aren’t in edit mode and

writable when we are in edit mode; as an alternative, you could permit editing of the main

data—a separate step from editing the grid. I will leave it for you to decide what

works best in your implementation.

Listing 6 is the code that replaces the DataList. In Listing 6, Target is the name of

an HtmlTableCell whose runat property is equal to server. A table cell makes a convenient

place to load dynamic controls. (You also might use a control such as the PlaceHolder for

this purpose.) The trick is to iterate over each object in the data source, load the

UserControl, hand off the data to the UserControl, and add the UserControl to the

Container’s Controls collection. After finishing these few simple steps, the nested

DataGrid will move easily into and out of edit mode and all events will fire

correctly.

The sample in Listings 5 and 6 addresses and resolves the same functional behavior as

the earlier nested DataList and DataGrid example. What isn’t demonstrated is how to

replicate the same visual appearance. Massaging the appearance is simply a matter of

adding a style sheet and changing control properties until you get the result you

desire.

The biggest drawback to not using the DataList is that you do not automatically get the

different styles for item, selected item, and edit item templates. You will have to apply

different styles to each of the dynamically loaded UserControls to simulate the styles

associated with the various edit regions in the DataList, but this is comparatively easy

to do.

Alternate Scenario

If you have your heart set on using a DataList with a nested DataGrid, my good friend

Joe Shook demonstrated a functional way to do this as well.

Define a Web Form with a DataList. In that form, implement your editing events. Each

time the DataList.ItemCreated event fires, the ItemCreated event will send an argument

containing a DataListItem. The DataListItem contains the dynamically created UserControl.

Using the event argument, call e.Item.Find(“uc1”), where uc1 is your UserControl’s ID, to

obtain a reference to the template-created UserControl. If the Find returns the

UserControl, request the DataGrid on that UserControl and manually re-associate your edit

event handlers with the DataGrid by calling AddHandler.

In this alternative example, your UserControl and DataGrid’s events actually reside in

the page containing the DataList. I am not a big fan of that part of the solution, but it

does seem to work. A modest revision would entail moving the edit event handlers to the

UserControl and binding the DataGrid’s event properties to the UserControl’s event

handlers using AddHandler. This revision should work and now your behavior travels with

your component, the UserControl.

Summary

There are so many things I like about programming in .NET that the few things that seem

out of whack don’t bother me too much. From my own experience, conversations, and

newsgroups, it appears that nesting a DataGrid in a DataList doesn’t work quite the way

one would expect. Perhaps this is by design or is a bug; we may never know.

Through experimentation, it appears that a nested DataGrid’s edit events aren’t

restored when the DataGrid is nested in a DataList. In this article, Part 3 of 3, I

demonstrated a workaround for the nonexistent edit behavior that excludes the DataList

from the equation, requiring you to load a UserControl containing a DataGrid for each

object in the master data source—this is the data used as the DataList’s data

source. As an alternative, you can use the DataList with a nested DataGrid, but you will

have to manually find the dynamically created DataGrids defined in the DataList’s

templates and re-associate the DataGrid event handlers in the DataList’s ItemCreated

event. The later approach, while not shown here, has been demonstrated to work.

I suspect from conversations I have had that Microsoft is aware of this relatively

minor problem and a more permanent solution will be forthcoming.

About the Author

Paul Kimmel is a software architect, writer, and columnist for codeguru.com. Look for

his recent book Visual Basic .NET Power Coding from Addison-Wesley. Paul Kimmel

is available to help design and build your .NET solutions and can be contacted at [email protected].

Latest Posts

Related Stories