简单且可扩展的单选按钮式 GridView





2.00/5 (1投票)
一篇描述如何创建单选按钮式 GridView 的文章。

引言
GridView
是 ASP.NET 2.0 及更高版本中重要的控件之一,无疑可以节省大量工作时间。然而,自定义 GridView
控件并非易事。例如,很难在 GridView
中添加一个单选按钮列。本文演示了为什么单选按钮列不能正常工作的原因,以及如何创建一个简单且可扩展的单选按钮 GridView
。
问题所在
通常,如果 GridView
的列不是自动生成的,我们可以通过实现 ITemplate
接口来添加自定义列;如果列是自动生成的,我们可以在 OnRowDataBound
方法中添加自定义列。例如,我们可以使用以下代码添加一个单选按钮列:
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);
}
此代码为 GridView
的每一行添加了一个单选按钮单元格。我们还为每个单选按钮指定了相同的 GroupName
。请注意,此处允许使用相同的 ID。我稍后会解释原因。
到目前为止,一切正常。但是,如果我们编译此代码并在浏览器中打开 *.aspx 文件,我们会发现单选按钮列的行为不正确。

为什么我们可以同时选择两个单选按钮?我们不是已经将所有单选按钮添加到名为“NormalRadioButtonGroupName
”的组中吗?罪魁祸首是一个称为 NamingContainer
的特殊控件。
关于“NamingContainer”
NamingContainer
是一个实现 INamingContainer
接口的特殊父控件。它用于“包含”子控件。它还用于生成控件的 UniqueID
。例如,如果一个 TextBox
被添加到 Panel
中,假设 Panel
是 TextBox
的 NamingContainer
,Panel
的 ID 是“Panel1
”,TextBox
的 ID 是“textBox1
”,那么 TextBox
的 UniqueID
应该是“Panel1$textBox1
”。顺便说一句,控件的 ClientID
与 UniqueID
相同,只是分隔符不同——使用 '_' 替换 '$'。让我们回到“问题所在”部分。当我在 GridView
的每一行创建一个单选按钮时,我指定了相同的单选按钮 ID。NamingContainer
确保单选按钮的 ID 不会重复。特别地,示例如下代码中的 ID:
NormalRadioButtonGridView1$ctl02$NormalRadioButton
NormalRadioButtonGridView1&ctl03$NormalRadioButton
NormalRadioButtonGridView1$ctl04$NormalRadioButton
“NormalRadioButtonGridView1
”是 GridView
的 ID,“ctlXX
”是行的 ID(因为我们没有为每行分配值,ASP.NET 自动生成了控件 ID 为“ctlXX
”),“NormalRadioButton
”是我们指定的单选按钮 ID。有关 NamingContainer
的更多详细信息,请参见 此处。
查找并解决问题
现在我们在浏览器中查看页面的源代码。如下所示:
//...
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)"
请注意每个单选按钮的“name
”属性。尽管我们为每个单选按钮分配了相同的组名,但由于 NamingContainer
,实际的“name
”值是由每个级别的 NamingContainer
的 ID 组合而成的。这就是为什么系统不认为这些单选按钮属于一个组,因此我们可以选择多个单选按钮。
为了解决这个问题,首先想到的是修改“name
”值。不幸的是,“name
”值包含了单选按钮的层次结构,如果我们修改它,在回发后单选按钮将丢失其信息。但是,如果您不想在服务器端处理逻辑,换句话说,您关闭了 AutoPostBack
属性,那么修改每个单选按钮的“name
”属性并为其分配相同的值是一个不错的解决方案。在大多数情况下,我们应该让 GridView
回发以调用我们自己的事件函数,例如“selected index changed”。因此,即使会回发到服务器,我们也应该找到一种方法来纠正行为。
首先,也是最重要的,我们应该模拟一个“组”机制。查看“onclick
”属性。它调用一个名为 __doPostBack
的 JavaScript 函数。如果存在会回发数据的控件,ASP.NET 会自动生成此函数。它接受两个参数,第一个参数指定哪个控件回发数据;第二个参数是要传递到服务器的值。我们可以使用此函数传递一个值,例如 fromGridViewRadioButton
,到服务器。在服务器端,如果我们捕获到发送者 ID 是当前单选按钮控件并且传递的值是 fromGridViewRadioButton
的已回发数据,我们则将单选按钮的状态设置为 Checked
;如果发送者 ID 不是当前单选按钮控件并且传递的值是 fromGridViewRadioButton
,我们则将单选按钮的状态设置为 UnChecked
。我们应该编写如下代码:
/// Override this method to add custom code to process the check-logic.
/// The '__EVENTTARGET' and '__EVENTARGUMENT'
/// are hidden inputs auto-generated by ASP.NET if some controls are 'AutoPostBack'.
/// When I add a new radio button to the RadioButtonGridView,
/// I will check the 'RadioButtonAutoPostBack' property of RadioButtonGridView,
/// if this property is set to true, the RadioButtonEx.AutoPostBack
/// should set to true also, otherwise it should set to false.
/// In particularly, if you don't want to use the post back mechanism
/// to implement the RadioButon style
/// GridView, you can set the 'RadioButtonAutoPostBack' to false and
/// the following code makes no sense.
///
/// When a radio button in the grid view is clicked,
/// it will pass the UniqueID of the radio button
/// as the '__EVENTTARGET' argument and the 'fromGridViewRadioButton'
/// string as the '__EVENTARGUMENT' argument to the
/// '__doPostBack' JavaScript function. This function is also auto-generated
/// by ASP.NET if some controls are 'AutoPostBack'.
/// '__doPostBack' function then submit the current form and these values to the server,
/// that's why we can get them here.
protected override bool LoadPostData(string postDataKey,
System.Collections.Specialized.NameValueCollection postCollection)
{
Boolean flag = base.LoadPostData(postDataKey, postCollection);
// Because the check-status is processed in the base.LoadPostData(),
// so we just add the uncheck-status process here.
//
// If the __EVENTTARGET doesn't equal current control ID, there are two situations:
// 1. The posted back data is for the radio buttons in the grid view,
// but not this one.
// In this case, the Checked value should be set false.
// 2. The posted back data is not for the radio buttons.
// In this case, the Checked value should
// not be changed.
// We already added an argument whose value is "fromGridViewRadioButton"
// to the radio button's
// __doPostBack() function, so we just retrieve the
// "__EVENTARGUMENT" from the post collection, if
// the value equals "fromGridViewRadioButton", the posted back data
// is for the radio buttons, otherwise not.
if (postCollection[EVENT_TARGET] != this.UniqueID)
{
if (postCollection[EVENT_ARGUMENT] == RADIO_BUTTON_FLAG)
{
if (this.Checked != false)
{
this.Checked = false;
flag = true;
}
}
}
return flag;
}
其次,我们应该捕获输出的 HTML 文本,并将 fromGridViewRadioButton
参数添加到 __doPostBack
函数中。
protected override void Render(HtmlTextWriter writer)
{
// Create a custom html writer and parse it to the base render method to
// capture the default generated html code.
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
HtmlTextWriter customHtmlWriter = new HtmlTextWriter(sw);
base.Render(customHtmlWriter);
// We should customize the default output html text to correct the behavior.
String pageResult = ProcessPageOutput(sb.ToString());
// Write it back to the server
writer.Write(pageResult);
}
//...
virtual protected String ProcessPageOutput(String outputString)
{
// ...
// Add "fromGridViewRadioButton" as the second argument to the __doPostBack function
// ...
}
最后,当选中的单选按钮更改时,我们应该通知 GridView
更改选中的索引。
protected override void OnCheckedChanged(EventArgs e)
{
base.OnCheckedChanged(e);
if (this.Checked)
{
// Use the new method 'setNewSelectedIndex' to set the new selected index
// and raise OnIndexChanging and OnIndexChanged event.
_parentGridView.setNewSelectedIndex(_parentGridViewRow.RowIndex);
}
}
Using the Code
构建源代码并将 DLL 拖到 Visual Studio 的工具箱中。像使用普通 GridView
一样使用 RadioButtonGridView
。
请注意,DLL 中还有一个名为 RadioButtonEx
的控件。此单选按钮不仅可以在 GridView
中使用。每当您想在一个容器中添加一组单选按钮,却发现出现像 GridView
那样的意外行为时,请尝试使用 RadioButtonEx
。
您可以覆盖 ProcessPageOutput
来添加您自己的逻辑,以使此控件满足您的需求。
历史
- 2009-04-23 原始版本