Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

A Simple and Extensible Radio Button Style GridView

2.00/5 (1 vote)
25 Apr 2009CPOL5 min read 41.3K   583  
An article on describing how to create a radio button style GridView

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:

C#
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:

Normal_RadioGridView

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:

C#
/// 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;
}

Second, we should capture the output HTML text and add fromGridViewRadioButton argument to the __doPostBack function:

C#
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
   // ...
}

Finally, when the selected radio button changes, we should notify the GridView to change the selected index:

C#
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

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)