Cannot find selected value of dynamically added controls in repeater - asp.net

I am creating a survey page that has a list of questions and answers that can be radiobuttonlists, checkboxlists or textboxes. These controls are added dynamically to a Repeater in its ItemDataBound event using Controls.Add.
I've managed to render the page ok but when I submit the form and iterate over the controls in the repeater to get the selectedvalues of the radiobuttons and textbox values, FindControl returns null. What do I need to do to get get the selected values? I've tried iterating over the RepeaterItems but that returned null too. I've tried different types of FindControl but it never resolves the control types.
It works if I add a declarative DataBinder in the Repeater like this
<asp:Repeater ID="rptSurvey" runat="server" Visible="true" EnableViewState="true" >
<ItemTemplate>
<%# DataBinder.Eval(Container.DataItem, "Question") %>
</ItemTemplate>
</asp:Repeater>
However, I want want to dynamically add the controls but in doing this i cant get the selectedvalues when submitting. This is tha main structure of my code...
<html>
<asp:Repeater ID="rptSurvey" runat="server" Visible="true">
</asp:Repeater>
<asp:Button ID="btnSubmit" runat="server" Text="Submit" OnClick="btnSubmit_Click" />
</html>
protected void Page_Load(object sender, EventArgs e)
{
...
if (!IsPostBack)
{
rptSurvey.DataSource = GetQuestions();
rptSurvey.DataBind();
}
...
}
protected void rptSurvey_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
string question = (DataBinder.Eval(e.Item.DataItem, "Question")).ToString();
litQuestion = new Literal();
litQuestion.Text = question;
RadioButtonList rblAnswer = (RadioButtonList)item;
rptSurvey.Controls.Add(rblAnswer);
}
}
protected void btnSubmit_Click(object sender, EventArgs e)
{
...
Dictionary<int, string> answers = new Dictionary<int, string>();
try
{
var list = FindControls(rptSurvey, c => c is RadioButtonList || c is CheckBoxList || c is TextBox);
foreach (Control item in list)
{
QuestionId = int.Parse(Questions.Rows[list.IndexOf(item)][0].ToString());
if (item is TextBox)
{
TextBox txtAnswer = (TextBox)item;
answers.Add(QuestionId, txtAnswer.Text);
}
else if (item is RadioButtonList)
{
RadioButtonList rblAnswer = (RadioButtonList)item;
answers.Add(QuestionId, rblAnswer.SelectedItem.Text);
}
else if (item is CheckBoxList)
{
// Iterate through the Items collection of the CheckBoxList
string cblMultiAnswer = "";
for (int i = 0; i < cblAnswer.Items.Count; i++)
{
if (cblAnswer.Items[i].Selected)
{
cblMultiAnswer += cblAnswer.Items[i].Value + ",";
}
}
answers.Add(QuestionId, cblMultiAnswer);
}
}
bSurvey.BLInsertSurveyAnswers(answers, dateCreated, _userEmail);
}
}
public static List<Control> FindControls(Control parent, Predicate<Control> match)
{
var list = new List<Control>();
foreach (Control ctl in parent.Controls)
{
if (match(ctl))
list.Add(ctl);
list.AddRange(FindControls(ctl, match));
}
return list;
}

you have to create the control tree first (always - not only on non-postbacks). do it in the oninit or onpreload event.
look here: https://web.archive.org/web/20211020131055/https://www.4guysfromrolla.com/articles/081402-1.aspx

Related

How to detect repeater on OnCheckedChanged event of radio button

I have a nested repeater and there is a radiobutton in the inner one ( rptrSubscriptions ). What I want to do is to highlight the radio button that is selected and unhighlight the previous one .
There must be only one radio button selected between all of the radio buttons in the outer repeater .I also need to save the itemID of the selected one in a hidden field.
I have developed a method with the name of HighlightSubscription that will be called when user clicks on the radio button (OnCheckedChanged="HighlightSubscription") .
The problem is this, the system doesn't recognize rptrSubscriptions in HighlightSubscription method, I tried to use findcontrol but unfortunately there is no access to e.items in this method.
I also defined rptrSubscriptions as a public variable but it will be null too in this method.
I have copied the source of HighlightSubscription from HighlightBox that is a radio button inside a single repeater and it works fine as its definition is inside the designer,
but for the nested repeater the definition cant be in the designer as the system automatically removes it in compile time.
Any suggestion is highly appreciated.
protected void rptrSubscriptionGroups_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
Repeater rptrSubscriptions = (Repeater)e.Item.FindControl("rptrSubscriptions");
if (rptrSubscriptions != null)
{
RepeaterItem item = e.Item;
if (e.Item.ItemType == (ListItemType.Item) || e.Item.ItemType == (ListItemType.AlternatingItem))
{
DataTable SubscriptionTbl = SubscriptionsBind(SelectedLanguageID, SelectedPlatformID, SelectedCurrencyID, SelectedGroupID, 1);
rptrSubscriptions.DataSource = SubscriptionTbl;
rptrSubscriptions.DataBind();
}
}
}
protected void HighlightSubscription(object source, EventArgs e)
{
//find previous selected row
if (hiddenSelectedSubscriptionRowIndex.Value.Length > 0)
{
int rowIndex = int.Parse(hiddenSelectedSubscriptionRowIndex.Value);
//Repeater rptrSubscriptions = (Repeater)e.Item.FindControl("rptrSubscriptions");
RepeaterItem item = rptrSubscriptions.Items[rowIndex];
if (rptrSubscriptions != null)
{
// Uncheck Old Radio Button
RadioButton rbOld = (RadioButton)item.FindControl("rbBox");
rbOld.Attributes.CssStyle.Add("border-color", "lightgray");
rbOld.Attributes.CssStyle.Add("border-width", "1px");
rbOld.Checked = false;
}
}
RadioButton rb = source as RadioButton;
int CurrentrepeaterItemIndex = ((RepeaterItem)rb.NamingContainer).ItemIndex;
// save current row index in a hiddden field
hiddenSelectedSubscriptionRowIndex.Value = CurrentrepeaterItemIndex.ToString();
}
<asp:Repeater ID="rptrSubscriptionGroups" runat="server" OnItemDataBound="rptrSubscriptionGroups_ItemDataBound" OnItemCommand="rptrSubscriptionGroups_ItemCommand">
<ItemTemplate>
<asp:Repeater ID="rptrSubscriptions" runat="server" OnItemDataBound="rptrSubscriptions_ItemDataBound" OnItemCommand="rptrSubscriptions_ItemCommand">
<ItemTemplate>
<asp:RadioButton ID="rbSubscription" runat="server" OnCheckedChanged="HighlightSubscription" AutoPostBack="true"/>
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
A little edit from me you can try this one :
protected void HighlightSubscription(object source, EventArgs e)
{
Repeater rptrSubscriptions = new Repeater();
foreach (RepeaterItem a in rptrSubscriptionGroups.Items)
{
rptrSubscriptions = (Repeater)a.FindControl("rptrSubscriptions");
}
//find previous selected row
if (hiddenSelectedSubscriptionRowIndex.Value.Length > 0)
{
int rowIndex = int.Parse(hiddenSelectedSubscriptionRowIndex.Value);
//Repeater rptrSubscriptions = (Repeater)e.Item.FindControl("rptrSubscriptions");
RepeaterItem item = rptrSubscriptions.Items[rowIndex];
if (rptrSubscriptions != null)
{
// Uncheck Old Radio Button
RadioButton rbOld = (RadioButton)item.FindControl("rbBox");
rbOld.Attributes.CssStyle.Add("border-color", "lightgray");
rbOld.Attributes.CssStyle.Add("border-width", "1px");
rbOld.Checked = false;
}
}
RadioButton rb = source as RadioButton;
int CurrentrepeaterItemIndex = ((RepeaterItem)rb.NamingContainer).ItemIndex;
// save current row index in a hiddden field
hiddenSelectedSubscriptionRowIndex.Value = CurrentrepeaterItemIndex.ToString();
}
Something like this:
protected void rptrSubscriptionGroups_ItemDataCreated(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
RepeaterItem item = e.Item;
RadioButton rb = (RadioButton)item.FindControl("rbSubscription");
rb.AutoPostBack = true;
rb.CheckedChanged += new EventHandler(rb_CheckChanged);
}
}
private void rb_CheckChanged(object sender, EventArgs e)
{
RadioButton rb = (RadioButton)sender;
if(rb.Checked == true){
HighlightSubscription();
}
}

dropdownchanged event not firing in usercontrol in asp.net

I have a dropdownlist in a usercontrol as shown below
<asp:dropdownlist id="ddlLanguage" runat="server" AutoPostBack="true" EnableViewState="true" onselectedindexchanged="ddlLanguage_SelectedIndexChanged">
</asp:dropdownlist>
my selectedchanged event is not getting fired even once
in code behind
if (!IsPostBack)
{
//dt - is list of languages availbale in DB
//value[0]-contains lang currently to be binded to dropdownlist based
//remaining values (values [1]) to are to be populated to textbox
LoadModuleInfo(dt,values)
}
private void LoadModuleInfo(System.Data.DataTable dtLanguages, string[] values)
{
this.txbxModuleName.Text = values[1];
this.ddlLanguage.DataSource = dtLanguages;
this.ddlLanguage.DataTextField = "language_description";
this.ddlLanguage.DataValueField = "language";
this.ddlLanguage.DataBind();
// set up selections on the screen
this.ddlLanguage.SelectedIndex = this.getIndex(dtLanguages, values[0]);
}
protected void ddlLanguage_SelectedIndexChanged(object sender, System.EventArgs e)
{
//get new values ( values[0] and values[1])
LoadModuleInfo(dtLanguages, values);
}
protected int getIndex(DataTable dt, string recordId)
{
int intCt = 0;
foreach (System.Data.DataRow dr in dt.Rows)
{
if (dr[0].ToString() == recordId)
{
break;
}
else
{
intCt++;
}
}
return intCt;
}
i have wriiten the above code, but selectedchanged event is not fired for dropdownlist control available in USERCONTROL.
Please help.
If you page not refreshed at all .. most probably you have a javascript error in the page
Kindly remove below line from your code and try
this.ddlLanguage.DataValueField = "language";
or
change this too language to language_description

Getting values from textbox in the repeater in button click event

I have a TextBox in a repeater that is populated from the database in the ItemDataBound event. When I tried to get the text inside the TextBox in a Button_Click event I found the TextBox.Text empty. How can I get the Text?
foreach (RepeaterItem repeated in repEdit.Items)
{
DropDownList drp = (DropDownList)FindControlRecursive(repeated, "drpdown");
TextBox txt = (TextBox)FindControlRecursive(repeated, "txt");
CheckBox chk = (CheckBox)FindControlRecursive(repeated, "chk");
if (drp != null && !string.IsNullOrEmpty(drp.Attributes["ID"]))
{
loc.GetType().GetProperty(drp.Attributes["ID"].Split('#')[0] + "ID").SetValue(loc, int.Parse(drp.SelectedValue), null);
}
if (txt != null && !string.IsNullOrEmpty(txt.Attributes["ID"]))
{
if (txt.Attributes["ID"].Contains("#int"))
{
loc.GetType().GetProperty(txt.Attributes["ID"].Split('#')[0]).SetValue(loc, int.Parse(txt.Text), null);
}
else if (txt.Attributes["ID"].Contains("#decimal"))
{
loc.GetType().GetProperty(txt.Attributes["ID"].Split('#')[0]).SetValue(loc, decimal.Parse(txt.Text), null);
}
else
{
loc.GetType().GetProperty(txt.Attributes["ID"].Split('#')[0]).SetValue(loc, txt.Text, null);
}
}
if (chk!=null && !string.IsNullOrEmpty(chk.Attributes["ID"]))
{
loc.GetType().GetProperty(chk.Attributes["ID"].Split('#')[0]).SetValue(loc, chk.Checked, null);
}
}
HTML
<asp:Repeater ID="repEdit" runat="server" OnItemDataBound="repEdit_ItemDataBound">
<ItemTemplate>
<asp:Label ID="lblName" runat="server" Visible="false"></asp:Label>
<asp:TextBox ID="txt" runat="server" Visible="false"></asp:TextBox>
<asp:CheckBox ID="chk" runat="server" Visible="false" />
<asp:DropDownList ID="drpdown" runat="server" Visible="false">
</asp:DropDownList>
<br />
</ItemTemplate>
</asp:Repeater>
Here is the itemdatabound event
protected void repEdit_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
Label name = e.Item.FindControl("lblName") as Label;
TextBox text = e.Item.FindControl("txt") as TextBox;
CheckBox chk = e.Item.FindControl("chk") as CheckBox;
DropDownList drp = e.Item.FindControl("drpdown") as DropDownList;
Button but = e.Item.FindControl("butEdit") as Button;
//butEdit.Visible = true;
if (but != null)
{
but.Visible = true;
}
if (e.Item.ItemType == ListItemType.Item)
{
KeyValuePair<string, Dictionary<int, string>> kvp = (KeyValuePair<string, Dictionary<int, string>>)e.Item.DataItem;
if (name != null)
{
if (kvp.Key.Contains("#datetime"))
{
return;
}
name.Visible = true;
name.Text = kvp.Key.Split('#')[0].ToString();
}
if (kvp.Key.Contains("#int") || kvp.Key.Contains("#decimal"))
{
text.Visible = true;
text.ID = kvp.Key;
text.EnableViewState = true;
text.Attributes["ID"] = kvp.Key;
text.Text = kvp.Value[0].ToString();
if (kvp.Key.Split('#')[0] == "ID")
{
text.Enabled = false;
}
}
Here is your Repeater. It has a PlaceHolder and Button.
<asp:Repeater ID="rpNotifications" runat="server"
OnItemDataBound="rpNotifications_ItemDataBound">
<ItemTemplate>
<asp:PlaceHolder ID="commentHolder" runat="server"></asp:PlaceHolder>
<asp:Button runat="server" ID="btnComment"
Text="Comment" CssClass="btn blue"
OnClick="btnComment_Click" CommandArgument='<%# Eval("id") %>' />
</ItemTemplate>
</asp:Repeater>
The button has the command argument set as the Id of the data I am binding to it.
Next here is where you need to Bind your Repeater. If you don't Bind it in the Page_Init the resulting values in the dynamically created controls will be lost.
protected void Page_Init(object sender, EventArgs e)
{
rpNotifications.DataSource = {datasource-here}
rpNotifications.DataBind();
}
And here is why (Image from: http://msdn.microsoft.com/en-us/library/ms972976.aspx)
Dynamically Create Control on ItemDataBound
You will need to add the textbox to the placeholder as this is the only way to dynamically assign an ID to the control.
protected void rpNotifications_ItemDataBound(object sender,
RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item
|| e.Item.ItemType == ListItemType.AlternatingItem)
{
Common.Models.Item item = (Common.Models.Item)e.Item.DataItem;
// Create new Control
TextBox txtComment = new TextBox();
txtComment.ID = String.Format("txtComment{0}", item.id.ToString());
// Ensure static so you can reference it by Id
txtComment.ClientIDMode = System.Web.UI.ClientIDMode.Static;
// Add control to placeholder
e.Item.FindControl("commentHolder").Controls.Add(txtComment);
}
}
Get Value On Button Click
Now you need to create the event handler for the button and get the resulting value
protected void btnComment_Click(object sender, EventArgs e)
{
// This is the ID I put in the CommandArgument
Guid id = new Guid(((Button)sender).CommandArgument);
String comment = String.Empty;
// Loop through the repeaterItems to find your control
foreach (RepeaterItem item in rpNotifications.Controls)
{
Control ctl = item.FindControl(
String.Format("txtComment{0}", id.ToString()));
if (ctl != null)
{
comment = ((TextBox)ctl).Text;
break;
}
}
}
Try to get ASP.Net controls using below code
DropDownList drp = (DropDownList)repeated.FindControl("drpdown");
TextBox txt = (TextBox)repeated.FindControl("txt");
CheckBox chk = (CheckBox)repeated.FindControl("chk");
Then you can get values from there respective properties.

Maintain selected item in DropDownList inside GridView after removing some ListItems

I have a GridView with a DropDownList in each row. (The items in the DropDownList are the same for each.) I have a DropDownList "ddlView" outside of the GridView that is used for filtering the available options in the other DropDownLists. The default selection for ddlView is no filter.
When a user selects a new value for ddlView any selected values in the other DropDownLists disappear if they are not one of the values present after the filter is applied. What I would like to happen in that case is the previously selected value still be present and selected.
What is the best way to accomplish this?
The previously selected values are available during postback but appear to be cleared once DataBind() is called on the GridView, so I am unable to determine their previous value in the method where they are populated (the RowDataBound event).
My best idea so far is to manually store that information into an object or collection during postback and reference it later during the databinding events.
Is there a better way?
I don't think there is a better way to accomplish this as when the GridView is bound all of the controls are recreated thus removing the selections.
The following works: (I store the selections on postback to retrieve again in the RowDataBound event)
Markup
<asp:Button ID="button1" runat="server" Text="Post Back" OnClick="button1_Click" />
<br />
<asp:GridView ID="gridView1" runat="server">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:DropDownList ID="gridViewDropDownList" runat="server">
<asp:ListItem Value="1">Item 1</asp:ListItem>
<asp:ListItem Value="2">Item 2</asp:ListItem>
<asp:ListItem Value="3">Item 3</asp:ListItem>
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
Code
public class GridViewDropDownSelections
{
public int RowIndex { get; set; }
public int SelectedIndex { get; set; }
}
...
private List<GridViewDropDownSelections> selectedDropDownListItems = new List<GridViewDropDownSelections>();
protected override void OnLoad(EventArgs e)
{
var selections = gridView1.Rows.Cast<GridViewRow>().Where(r => r.RowType == DataControlRowType.DataRow)
.Select(r => new GridViewDropDownSelections() { RowIndex = r.RowIndex, SelectedIndex = ((DropDownList)r.FindControl("gridViewDropDownList")).SelectedIndex }).ToList();
selectedDropDownListItems.AddRange(selections);
gridView1.RowDataBound += new GridViewRowEventHandler(gridView1_RowDataBound);
if (!IsPostBack)
{
BindDataGrid();
}
base.OnLoad(e);
}
protected void button1_Click(object sender, EventArgs e)
{
BindDataGrid();
}
private void BindDataGrid()
{
//Dummy data
string[] data = new string[] { "Item 1", "Item 2", "Item 3" };
gridView1.DataSource = data;
gridView1.DataBind();
}
void gridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
var selection = selectedDropDownListItems.FirstOrDefault(i => i.RowIndex == e.Row.RowIndex);
if (selection != null)
{
try
{
DropDownList gridViewDropDownList = (DropDownList)e.Row.FindControl("gridViewDropDownList");
gridViewDropDownList.SelectedIndex = selection.SelectedIndex;
}
catch (Exception)
{
}
}
}
}
Hope it helps.
Instead of using DataBinding to apply the filter, you could add/remove the DropdownList items through the Items property, when another filter is selected. This way the selected value in the dropdowns should not be resetted.
You might be able to accomplish this with a simple condition. I don't know how you're filtering the items, but it would go something like this:
for (int itemIndex = 0; itemIndex < DropDownList1.Items.Count; itemIndex++)
{
ListItem item = DropDownList1.Items[itemIndex];
if (DropDownList1.Items.IndexOf(item) > ddlView.SelectedIndex)
{
if (!item.Selected)
{
DropDownList1.Items.Remove(item);
}
}
}
Not sure if this is what you're looking for, but hope it helps.
Use client side javascript
Search for all the appropriate select inputs and for each one found, go through the options and remove the one selected in the master ddl
function UpdateDropDowns (tbl) {
var controls = tbl.getElementsByTagName('select');
for (var i = 0; i < controls.length; i++) {
RemoveOption(controls[i], 'the value to remove');
}
}
function RemoveOption(ddl, val)
{
for (var i = ddl.options.length; i>=0; i--) {
if (ddl.options[i].value == val) {
ddl.remove(i);
}
}
}

Why is the footer-item not included in Repeater.Items?

I need to get a value from a textbox inside a FooterTemplate in the OnClick event of a button. My first thought was to loop through the items-property on my repeater, but as you can see in this sample, it only includes the actual databound items, not the footer-item.
ASPX:
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
Item<br />
</ItemTemplate>
<FooterTemplate>
Footer<br />
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
</FooterTemplate>
</asp:Repeater>
<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
Code-behind.cs:
protected void Page_Load(object sender, EventArgs e)
{
ListItemCollection items = new ListItemCollection();
items.Add("value1");
items.Add("value2");
Repeater1.DataSource = items;
Repeater1.DataBind();
}
protected void Button1_Click(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine(Repeater1.Items.Count);
}
This code will only output "2" as the count, so how do I get to reference my textbox inside the footertemplate?
From the MSDN documentation, the Items is simply a set of RepeaterItems based off the DataSource that you are binding to and does not include items in the Header or FooterTemplates.
If you want to reference the textbox, you can get a reference on ItemDataBound event from the repeater where you can test for the footer.
E.g.
private void Repeater_ItemDataBound(Object Sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Footer)
{
TextBox textBox = e.Item.FindControl("TextBox1") as TextBox;
}
}
You can find controls in the repeater. That will give you all the controls in the repeater (RepeaterItems collection). Now you can do something like this:
RepeaterItem footerItem=null;
foreach(Control cnt in Repeater1.Controls)
{
if(cnt.GetType() == typeof(RepeaterItem) && ((RepeaterItem)cnt).ItemType == ListItemType.Footer)
{
footerItem = cnt;
break;
}
}
The footer should be the last child control of the repeater so you can do something like..
RepeaterItem riFooter = Repeater1.Controls[Repeater1.Controls.Count - 1] as RepeaterItem;
if (riFooter != null && riFooter.ItemType == ListItemType.Footer) {
TextBox TextBox1 = riFooter.FindControl("TextBox1") as TextBox;
if (TextBox1 != null) {
TextBox1.Text = "Test";
}
}

Resources