Editing Nested DataGrids in ASP.NET, Page 2
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.