Introduction
The GridView
is one of the significant controls in ASP.NET 2.0 and later versions. No doubt it reduces a lot of work time. However, customizing the GridView
control is not an easy task. For example, it's hard to add a radio button column to the GridView
. This article demonstrates the reason why a radio button column cannot act as normal and how to create a simple and extensible radio button GridView
.
What's the Problem
Typically, we are able to add a custom column by implementing an ITemplate
interface if the GridView
's columns are not auto generated or we can add the custom column in the OnRowDataBound
method if the columns are auto generated. For example, we could add a radio column using the following code:
protected override void OnRowDataBound(GridViewRowEventArgs e)
{
TableCell tc = new TableCell();
if (e.Row.RowType == DataControlRowType.DataRow)
{
RadioButton radioButton = new RadioButton()
{
ID = "NormalRadioButton",
GroupName = "NormalRadioButtonGroupName",
AutoPostBack = true
};
tc.Controls.Add(radioButton);
}
e.Row.Cells.AddAt(0, tc);
base.OnRowDataBound(e);
}
This code added a radio button cell to each row of the GridView
. Also we specified the same GroupName
for each radio button. Note that the same ID is allowed here. I'll explain it later.
By now, everything is OK. However, if we compile this code and open the *.aspx file in the browser, we'll find the behavior of the radio column is not correct:
Why can we select two radio buttons at the same time? Haven't we added all the radio buttons into a group named "NormalRadioButtonGroupName
"? The culprit is a particular control called NamingContainer
.
About "NamingContainer"
The NamingContainer
is a particular parent control which implements the INamingContainer
interface. It is used to "contain" the child control. It is also used to generate the UniqueID
of the control. For example, a TextBox
is added to a Panel
, assume the Panel
is the NamingContainer
of the TextBox
, the Panel's ID is "Panel1
" and the TextBox's ID is "textBox1
", then the TextBox's UniqueID
should be "Panel1$textBox1
". By the way, the ClientID
of a control is the same as the UniqueID
except the split character - use '_' to replace '$'. Let's go back to section "What's the problem". When I created the radio button for each row of the GridView
, I specified the same ID of the radio button. The NamingContainer
guarantees the IDs of the radio buttons won't be duplicated. In particularly, the IDs of the example are as the following code:
NormalRadioButtonGridView1$ctl02$NormalRadioButton
NormalRadioButtonGridView1&ctl03$NormalRadioButton
NormalRadioButtonGridView1$ctl04$NormalRadioButton
"NormalRadioButtonGridView1
" is the ID of the GridView
, "ctlXX
" is the ID of the row (Because we didn't assign a value to each row, ASP.NET automatically generated the control ID as "ctlXX
"), "NormalRadioButton
" is the radio button ID we assigned. More details about NamingContainer
can be found here.
Find and Resolve the Problem
Now we view the source code of the page in the browser. It is as below:
//...
input id="NormalRadioButtonGridView1_ctl02_NormalRadioButton"
type="radio"
name="NormalRadioButtonGridView1$ctl02$NormalRadioButtonGroupName"
value="NormalRadioButton"
onclick="javascript:setTimeout('__doPostBack
(\'NormalRadioButtonGridView1$ctl02$NormalRadioButton\',\'\')', 0)"
//...
input id="NormalRadioButtonGridView1_ctl03_NormalRadioButton"
type="radio"
name="NormalRadioButtonGridView1$ctl03$NormalRadioButtonGroupName"
value="NormalRadioButton"
onclick="javascript:setTimeout('__doPostBack
(\'NormalRadioButtonGridView1$ctl03$NormalRadioButton\',\'\')', 0)"
//...
input id="NormalRadioButtonGridView1_ctl04_NormalRadioButton"
type="radio"
name="NormalRadioButtonGridView1$ctl04$NormalRadioButtonGroupName"
value="NormalRadioButton"
onclick="javascript:setTimeout('__doPostBack
(\'NormalRadioButtonGridView1$ctl04$NormalRadioButton\',\'\')', 0)"
Note the "name
" attribute of each radio button. Although we assigned the same group name for each radio button, due to the NamingContainer
, the truely "name
" value is combined by the ID of NamingContainer
of each level. This is why the system does not think the radios are in one group so that we can select more than one radio buttons at the same time.
To solve this problem, a first idea is to modify the "name
" value. Unfortunately, the "name
" value contains the hierarchy of the radio button, if we modify it, the radio button will lose its infomation after post back. But if you don't want to process the logic in the server side, in other words, you turn off the AutoPostBack
property, then modify the "name
" attribute of each radio button and assign the same value to it is a good solution. In most situations, we should let the GridView
post back to invoke our own event functions such as "selected index changed". So we should find a way to correct the behavior even if it will post back to the server.
First and most important, we should simulate a "Group" mechanism. Look at the "onclick
" attribute. It calls a JavaScript function named __doPostBack
. This function is auto generated by ASP.NET if there is a control who will post back data to the server. It takes two arguments, the first argument specifies which control posts back data; the second argument is a value to pass to the server. We can use this function to pass a value such as fromGridViewRadioButton
to the server. In the server side, if we capture a posted data whose sender's ID is the current radio button control and the passed value is fromGridViewRadioButton
, we set the radio button's status Checked
; if the sender's ID is not the current radio button control and the passed value is fromGridViewRadioButton
, we set the radio button's status UnChecked
. We should write code as below:
protected override bool LoadPostData(string postDataKey,
System.Collections.Specialized.NameValueCollection postCollection)
{
Boolean flag = base.LoadPostData(postDataKey, postCollection);
if (postCollection[EVENT_TARGET] != this.UniqueID)
{
if (postCollection[EVENT_ARGUMENT] == RADIO_BUTTON_FLAG)
{
if (this.Checked != false)
{
this.Checked = false;
flag = true;
}
}
}
return flag;
}
Second, we should capture the output HTML text and add fromGridViewRadioButton
argument to the __doPostBack
function:
protected override void Render(HtmlTextWriter writer)
{
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
HtmlTextWriter customHtmlWriter = new HtmlTextWriter(sw);
base.Render(customHtmlWriter);
String pageResult = ProcessPageOutput(sb.ToString());
writer.Write(pageResult);
}
virtual protected String ProcessPageOutput(String outputString)
{
}
Finally, when the selected radio button changes, we should notify the GridView
to change the selected index:
protected override void OnCheckedChanged(EventArgs e)
{
base.OnCheckedChanged(e);
if (this.Checked)
{
_parentGridView.setNewSelectedIndex(_parentGridViewRow.RowIndex);
}
}
Using the Code
Build the source code and drag the DLL to the toolbox of Visual Studio. Use the RadioButtonGridView
as you use the normal GridView
.
Note that here is another control named RadioButtonEx
in the DLL. This radio button can be used not only in the GridView
. Whenever you want to add a group of radio buttons in a container, but find the unexpected behavior as the GridView
, try to use RadioButtonEx
.
You can override ProcessPageOutput
to add your own logic to make this control meet your request.
History
- 2009-04-23 Original version