Tuesday, September 06, 2005

DropDownList Controls In an ASP.Net DataGrid

This article will demonstrate how to add a DropDownList control to each row of an ASP.Net DataGrid, including the header row. The article demonstrates how to perform data binding on each DropDownList control, and how to handle events when the DropDownList selection changes.
In this article we will demonstrate how to add a data bound, event raising control into a column of an ASP.Net DataGrid. We will also see how to place a control into the header row of a DataGrid. We will demonstrate this using the DropDownList control.
The following screen shot shows a web form in design mode. We are going to query the authors table in SQL Server’s pubs table. You can see we have one column (au_fname) to display the au_fname column from the table. In the second column we have added DropDownList controls to both the header line, and in each row with a data record.

Let’s first take a look at the ASPX markup to create the grid.
id="DataGrid1" runat="server"
AutoGenerateColumns="False"
OnItemDataBound="DataGrid1_ItemDataBound">




ID="HeaderDropDown" Runat="server"
AutoPostBack="True"
OnSelectedIndexChanged="DropDown_SelectedIndexChanged" />


ID="ItemDropDown" Runat="server"
AutoPostBack="True"
OnSelectedIndexChanged="DropDown_SelectedIndexChanged" />




We have a DropDownList in the header declared as HeaderDropDown, and a DropDownList in the Item template declared as ItemDropDown. Notice we set the AutoPostBack property to true. Setting AutoPostBack to true allows the form to post back to the server and raise an event each time the user changes a selection in the DropDownList control. We also assign an event handler for the SelectedIndexChanged event. Notice we are sharing the same event handler (DropDown_SelectedIndexChanged) for all DropDownList controls. We will see later in the code how we can still identity the exact control firing the event.
Before we look at the SelectedIndexChanged event handlers, let’s think about populating the drop down controls with data for the user to select. When the DataGrid binds to a data source it will create a DropDownList control to place in the header row, and also a DropDownList for each row of data rendered. We need to catch when ASP.NET creates these controls so we can populate them with data for the user to select from. The best place to do this is during the ItemDataBound event. You can see in the ASPX above we are handling this event using the DataGrid1_ItemDataBound method, shown below.
protected void DataGrid1_ItemDataBound(object sender, DataGridItemEventArgs e)
{
if(e.Item.ItemType == ListItemType.AlternatingItem
e.Item.ItemType == ListItemType.Item)
{
string[] options = { "Option1", "Option2", "Option3" };
DropDownList list = (DropDownList)e.Item.FindControl("ItemDropDown");
list.DataSource = options;
list.DataBind();
}
else if(e.Item.ItemType == ListItemType.Header)
{
string[] options = { "OptionA", "OptionB", "OptionC" };
DropDownList list = (DropDownList)e.Item.FindControl("HeaderDropDown");
list.DataSource = options;
list.DataBind();
}
}
ItemDataBound occurs after a DataGridItem is bound to the grid. The event fires for each row, including the header and the footer rows. We know if the item belongs to the header, the footer, or one of the items by checking the Item.ItemType property of the event. Rows of data will always have a ListItemType of Item or AlternatingItem.
We use the FindControl method to obtain a reference to the DropDownList control for each row. To learn more about using FindControl in these scenarios, see ‘In Search Of ASP.NET Controls’. We specify the control to find by name. For the header remember we specified a name of “HeaderDropDown”, while rows with data will have a name of “ItemDropDown”.
We have an array of strings to represent data for the DropDownList control to bind against. Note that this event will fire for each row of the grid, so you’ll want to make sure this method performs well. You would not want to perform a database query each time the event fires. In this example, the DropDownList control in the header binds against a different set of strings than the DropDownList controls in the item rows.
Whenever the user modifies any of the DropDownList selections in our grid, the DropDown_SelectedIndexChanged event will fire. Remember, we assigned the same event handler for all of our lists, and we set the AutoPostBack property to true so a new selection should post back to the server and raise the event immediately. The event handler is shown below.
protected void DropDown_SelectedIndexChanged(object sender, EventArgs e)
{
DropDownList list = (DropDownList)sender;
TableCell cell = list.Parent as TableCell;
DataGridItem item = cell.Parent as DataGridItem;
int index = item.ItemIndex;
string content = item.Cells[0].Text;
Response.Write(
String.Format("Row {0} contains {1}", index, content)
);

}
First, notice the sender parameter will be the DropDownList control modified by the user. By casting the object reference to a DropDownList we get a reference to the modified control and can see what the user has selected.
Secondly, notice the DropDownList control’s parent will be a TableCell of the grid. The parent of the TableCell will be a DataGridItem. Once we have a reference to the DataGridItem, we can see which row was selected by using the ItemIndex property. (Note: ItemIndex will be -1 for the header row). With the DataGridItem we can also inspect the values of other cells in the row.

Once you know the control invoking the event you can find almost any other piece of information you need about the grid or the underlying data source by getting to the DataGridItem object and inspecting the ItemIndex property or the collection of TableCells. With this information in hand, adding controls to the rows of your DataGrid should become a straightforward process.

Event Bubbling From Web User Controls in ASP.NET (C#)

This C# code example demonstrates event bubbling from a web user control (ASCX) to a parent page (ASPX) and the shows the flow of events through execution.
Some user controls are entirely self contained, for example, a user control displaying current stock quotes does not need to interact with any other content on the page. Other user controls will contain buttons to post back. Although it is possible to subscribe to the button click event from the containing page, doing so would break some of the object oriented rules of encapsulation. A better idea is to publish an event in the user control to allow any interested parties to handle the event.

This technique is commonly referred to as “event bubbling” since the event can continue to pass through layers, starting at the bottom (the user control) and perhaps reaching the top level (the page) like a bubble moving up a champagne glass.

For starters, let’s create a user control with a button attached.

WebUserControl1

The code behind for the user control looks like the following.

public class WebUserControl1 : System.Web.UI.UserControl
{
protected System.Web.UI.WebControls.Button Button1;
protected System.Web.UI.WebControls.Panel Panel1;

private void Page_Load(object sender, System.EventArgs e)
{
Response.Write("WebUserControl1 :: Page_Load
");
}

private void Button1_Click(object sender, System.EventArgs e)
{
Response.Write("WebUserControl1 :: Begin Button1_Click
");
OnBubbleClick(e);
Response.Write("WebUserControl1 :: End Button1_Click
");
}

public event EventHandler BubbleClick;

protected void OnBubbleClick(EventArgs e)
{
if(BubbleClick != null)
{
BubbleClick(this, e);
}
}

#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
InitializeComponent();
base.OnInit(e);
}

private void InitializeComponent()
{
this.Button1.Click += new System.EventHandler(this.Button1_Click);
this.Load += new System.EventHandler(this.Page_Load);

}
#endregion

}


The user control specifies a public event (BubbleClick) which declares a delegate. Anyone interested in the BubbleClick event can add an EventHandler method to execute when the event fires – just like the user control adds an EventHandler for when the Button fires the Click event.

In the OnBubbleClick event, we first check to see if anyone has attached to the event (BubbleClick != null), we can then invoke all the event handling methods by calling BubbleClick, passing through the EventArgs parameter and setting the user control (this) as the event sender. Notice we are also using Response.Write to follow the flow of execution.

An ASPX page can now put the user control to work.
In the code behind for the page.


public class WebForm1 : System.Web.UI.Page
{
protected WebUserControl1 BubbleControl;

private void Page_Load(object sender, System.EventArgs e)
{
Response.Write("WebForm1 :: Page_Load
");
}

#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
InitializeComponent();
base.OnInit(e);
}

private void InitializeComponent()
{
this.Load += new System.EventHandler(this.Page_Load);
BubbleControl.BubbleClick += new EventHandler(WebForm1_BubbleClick);
}
#endregion

private void WebForm1_BubbleClick(object sender, EventArgs e)
{
Response.Write("WebForm1 :: WebForm1_BubbleClick from " +
sender.GetType().ToString() + "
");
}
}


Notice the parent page simply needs to add an event handler during InitializeComponent method. When we receive the event we will again use Reponse.Write to follow the flow of execution. When the page executes after clicking on the user control button, you should see the following.




One word of warning: if at anytime events mysteriously stop work, check the InitializeComponent method to make sure the designer has not removed any of the code adding event handlers.


Additional Resources
MSDN: Events and Delegates
MSDN: Raising An Event