0

I have the following nested listview...

<asp:ListView ID="lvwRiskQuestions" runat="server" ItemPlaceholderID="QuestionItemPlaceholder">
    <LayoutTemplate>
        <asp:PlaceHolder ID="QuestionItemPlaceholder" runat="server" />
    </LayoutTemplate>
    <ItemTemplate>
        <%# Eval("DESCRIPTION")%>
            <asp:ListView ID="lvwAnswers" runat="server" ItemPlaceholderID="AnswerItemPlaceholder" DataSource='<%# Eval("Answers")%>'>
                <LayoutTemplate>
                    <asp:PlaceHolder ID="AnswerItemPlaceholder" runat="server" />
                </LayoutTemplate>
                <ItemTemplate>
                    <asp:RadioButton ID="rdbSelect" runat="server" AutoPostBack="true" OnCheckedChanged="rdbSelectChanged"/>                                        
                        <%# Eval("Description")%>
                </ItemTemplate>
        </asp:ListView>
    </ItemTemplate>
</asp:ListView>

I get hold of the radio buttons OnCheckedChanged like so...

Protected Sub rdbSelectChanged(ByVal sender As Object, ByVal e As System.EventArgs)

    Dim rb1 As RadioButton = CType(sender, RadioButton)

    Dim lvwAnswers = DirectCast(lvwRiskQuestions.FindControl("lvwAnswers"), ListView)

    For Each row As ListViewItem In lvwAnswers.Items
        Dim rb As RadioButton = row.FindControl("rdbSelect")
        If rb IsNot Nothing AndAlso rb.Checked Then
            rb.Checked = False
        End If
    Next
    rb1.Checked = True
End Sub

The problem i have is 'lvwAnswers' is Nothing. I'm guessing im not doing my findcontrol correctly.

Any help greatly appreciated.

Dooie
  • 1,649
  • 7
  • 30
  • 47
  • yes, you are not doing your FindControl correctly. Your parent repeater contains many lvwAnswers. Maybe you can try something like casting rb1.Parent.Parent (or rb1.BindingContainer.Parent) to ListView – jbl Nov 07 '12 at 12:47

2 Answers2

3

If you're just generating a list of radio-buttons for the answers, you could use the RadioButtonList control. This would generate the correct HTML so that only one answer could be selected per question without having to post-back to de-select the other options.

If your answer template contains more than a single RadioButton, things get more complicated. When it's not hosted in a RadioButtonList, the RadioButton uses the UniqueID of the parent NamingContainer to build its unique group name. Unfortunately, in your example, the NamingContainer will be the ListViewDataItem from the lvwAnswers list, and each answer will have a different ID.

What you need is a RadioButton which will look at the NamingContainer's NamingContainer to generate its group name. You could either re-implement the RadioButton control, or use a little bit of reflection to update the private _uniqueGroupName field:

[ToolboxData("<{0}:ListRadioButton runat=\"server\" />")]
public class ListRadioButton : RadioButton
{
   private static readonly FieldInfo UniqueGroupNameField = FindUniqueGroupNameField();
   private string _uniqueGroupName;

   private static FieldInfo FindUniqueGroupNameField()
   {
      return typeof(RadioButton).GetField("_uniqueGroupName", 
         BindingFlags.NonPublic | BindingFlags.Instance);
   }

   protected virtual string CreateUniqueGroupName()
   {
      string result = GroupName;
      if (string.IsNullOrEmpty(result))
      {
         result = ID;
      }
      if (string.IsNullOrEmpty(result))
      {
         result = UniqueID;
      }
      else
      {
         Control container = NamingContainer;
         if (container != null)
         {
            if (container is IDataItemContainer)
            {
               container = container.NamingContainer ?? container;
            }

            result = container.UniqueID + base.IdSeparator + result;
         }
         else
         {
            string uniqueID = UniqueID;
            if (!string.IsNullOrEmpty(uniqueID))
            {
               int index = uniqueID.LastIndexOf(base.IdSeparator);
               if (index != -1)
               {
                  result = uniqueID.Substring(0, 1 + index) + result;
               }
            }
         }
      }

      return result;
   }

   private void EnsureUniqueGroupName()
   {
      if (_uniqueGroupName == null)
      {
         string value = CreateUniqueGroupName();
         if (UniqueGroupNameField != null) UniqueGroupNameField.SetValue(this, value);
         _uniqueGroupName = value;

         value = base.Attributes["value"];
         if (string.IsNullOrEmpty(value))
         {
            base.Attributes["value"] = UniqueID;
         }
      }
   }

   protected override bool LoadPostData(string postDataKey, NameValueCollection postCollection)
   {
      EnsureUniqueGroupName();
      return base.LoadPostData(postDataKey, postCollection);
   }

   protected override void Render(HtmlTextWriter writer)
   {
      EnsureUniqueGroupName();
      base.Render(writer);
   }
}

With that control in place and registered using the site prefix, you can change your code to:

<asp:ListView ID="lvwRiskQuestions" runat="server" ItemPlaceholderID="QuestionItemPlaceholder">
<LayoutTemplate>
   <asp:PlaceHolder ID="QuestionItemPlaceholder" runat="server" />
</LayoutTemplate>
<ItemTemplate>
   <%# Eval("DESCRIPTION") %>
   <asp:ListView ID="lvwAnswers" runat="server" ItemPlaceholderID="AnswerItemPlaceholder" DataSource='<%# Eval("Answers")%>'>
   <LayoutTemplate>
      <asp:PlaceHolder ID="AnswerItemPlaceholder" runat="server" />
   </LayoutTemplate>
   <ItemTemplate>
      <site:ListRadioButton ID="rdbSelect" runat="server"
         Text='<%# Eval("Description") %>'
      />
   </ItemTemplate>
   </asp:ListView>
</ItemTemplate>
</asp:ListView>

In the rendered HTML, the radio-buttons for each question will then have the same name, and you will only be able to select a single answer per question, without having to post the entire page on each selection.

Richard Deeming
  • 29,830
  • 10
  • 79
  • 151
  • That's a pretty nice solution. Probably a stupid question : why don't you need to also override LoadViewState and SaveViewState ? – jbl Nov 08 '12 at 08:19
  • Because there's nothing else that needs to be saved in the ViewState. The `_uniqueGroupName` field is recreated on every request. – Richard Deeming Nov 08 '12 at 12:37
-1

I'd like to point out that this "copy/paste" code doesn't work and was taken from a comment on codeproject (Comment titled Another Option). The original code does work.

Here it is :

using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Reflection;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

[AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
public class SimpleRadioButton : RadioButton
{
    private static readonly FieldInfo UniqueGroupNameField = FindUniqueGroupNameField();
    private string _uniqueGroupName;

    private static FieldInfo FindUniqueGroupNameField()
    {
        return typeof(RadioButton).GetField("_uniqueGroupName",
            BindingFlags.NonPublic | BindingFlags.Instance);
    }

    protected virtual string CreateUniqueGroupName()
    {
        string result = this.GroupName;
        if (string.IsNullOrEmpty(result))
        {
            result = this.ID;
        }
        if (string.IsNullOrEmpty(result))
        {
            result = this.UniqueID;
        }
        else
        {
            Control container = this.NamingContainer;
            if (null != container)
            {
                if (container is IDataItemContainer)
                {
                    container = container.NamingContainer ?? container;
                }

                result = container.UniqueID + base.IdSeparator + result;
            }
            else
            {
                string uniqueID = this.UniqueID;
                if (!string.IsNullOrEmpty(uniqueID))
                {
                    int index = uniqueID.LastIndexOf(base.IdSeparator);
                    if (-1 != index)
                    {
                        result = uniqueID.Substring(0, 1 + index) + result;
                    }
                }
            }
        }

        return result;
    }

    private void EnsureUniqueGroupName()
    {
        if (null == _uniqueGroupName)
        {
            string value = this.CreateUniqueGroupName();
            if (null != UniqueGroupNameField) UniqueGroupNameField.SetValue(this, value);
            _uniqueGroupName = value;

            // Make sure we have a value attribute:
            value = base.Attributes["value"];
            if (string.IsNullOrEmpty(value))
            {
                base.Attributes["value"] = this.UniqueID;
            }
        }
    }

    protected override bool LoadPostData(string postDataKey, NameValueCollection postCollection)
    {
        this.EnsureUniqueGroupName();
        return base.LoadPostData(postDataKey, postCollection);
    }

    protected override void Render(HtmlTextWriter writer)
    {
        this.EnsureUniqueGroupName();
        base.Render(writer);
    }
}
Mathlec
  • 318
  • 1
  • 6
  • It wasn't "taken from a comment on CodeProject"; it was based on the same code which I used to post that comment in the first place! (Look at my "Real Name" on my SO profile, and compare it to my name on CodeProject.) – Richard Deeming Nov 16 '12 at 18:01
  • Also, the code from my answer here is identical to my comment on CodeProject (apart from the class name and some minor cosmetic changes). It works for me, and it works for the OP, so I don't know why you think it doesn't? – Richard Deeming Nov 16 '12 at 18:05
  • My apologies. I was trying to make sure this wasn't "stolen" from someone else. As for the code "not working" it was not for me. But I might have made an error or used it in a dynamic control. – Mathlec Nov 20 '12 at 17:16