65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconemptyStarIconemptyStarIconemptyStarIcon

2.00/5 (1投票)

2009年4月25日

CPOL

5分钟阅读

viewsIcon

41771

downloadIcon

616

一篇描述如何创建单选按钮式 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 文件,我们会发现单选按钮列的行为不正确。

Normal_RadioGridView

为什么我们可以同时选择两个单选按钮?我们不是已经将所有单选按钮添加到名为“NormalRadioButtonGroupName”的组中吗?罪魁祸首是一个称为 NamingContainer 的特殊控件。

关于“NamingContainer”

NamingContainer 是一个实现 INamingContainer 接口的特殊父控件。它用于“包含”子控件。它还用于生成控件的 UniqueID。例如,如果一个 TextBox 被添加到 Panel 中,假设 PanelTextBoxNamingContainerPanel 的 ID 是“Panel1”,TextBox 的 ID 是“textBox1”,那么 TextBoxUniqueID 应该是“Panel1$textBox1”。顺便说一句,控件的 ClientIDUniqueID 相同,只是分隔符不同——使用 '_' 替换 '$'。让我们回到“问题所在”部分。当我在 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 原始版本
一个简单且可扩展的单选按钮式 GridView - CodeProject - 代码之家
© . All rights reserved.