Unable to populate RadioButtonList inside ASP.NET Repeater Template - c#

I am trying to populate a repeater containing a label and a RadioButtonList in an ASP.NET webform to make a small quiz.
These are the classes I am using:
public class Option
{
private string _body;
private bool? _isCorrect;
#region properties
public string Body
{
get { return _body; }
}
public bool? IsCorrect
{
get { return _isCorrect; }
}
#endregion
#region constructors
public Option(string body)
{
_body = body;
_isCorrect = null;
}
public Option(string body, bool isCorrect)
{
_body = body;
_isCorrect = isCorrect;
}
#endregion
#region methods
public override string ToString()
{
return _body;
}
#endregion
}
and:
public class Question
{
private string _body;
private Option[] _optionsArray;
private List<Option> _optionsList;
#region properties
public string Body
{
get { return _body; }
}
public Option[] Options
{
get { return _optionsArray; }
}
public List<Option> OptionsList
{
get { return _optionsList; }
}
#endregion
#region constructors
public Question(string body, Option[] options)
{
_body = body;
_optionsArray = options;
_optionsList = new List<Option>();
foreach (Option opt in options)
{
_optionsList.Add(opt);
}
}
#endregion
#region methods
public override string ToString()
{
return _body;
}
public List<Option> GetOptions()
{
return OptionsList;
}
#endregion
}
My webform looks like this:
<div runat="server" ID="quizDiv">
<br />
<br />
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<%--<asp:Label ID="questionBody" runat="server" Text=<%# DataBinder.Eval(Container.DataItem, "Body") %>>--%>
<asp:Label ID="questionBody" runat="server" Text=<%# ((Question)Container.DataItem).Body %>>
<asp:radiobuttonlist ID="blah" runat="server" DataTextField="Body" DataValueField="Body" DataSource=<%# ((Question)Container.DataItem).OptionsList %> >
</asp:radiobuttonlist>
</asp:Label>
</ItemTemplate>
</asp:Repeater>
<br />
</div>
and the code-behind like so:
protected void btnStart_Click(object sender, EventArgs e)
{
Dataset1 ds = new Dataset1();
question = ds.CreateQuestion();
List<Question> qts = new List<Question>();
qts.Add(question);
Repeater1.DataSource = qts;
Repeater1.DataBind();
}
For the time being I am just using one question. The label displaying my question shows up fine, but no radio buttons show up for displaying the answer options. I have gone through many samples, and this seems to work for people when they use data from a DB via a DataTable or DataSet. However no matter how much I to play around with the Datasource, DataValueField and DataTextField parameters the RadioButtonList remains utterly barren. As you can see, I initially used an array of Option and also tried a List but to no avail.
What am I missing here?
EDIT - Found my mistake
I had the <asp:label> and <asp:radiobuttonlist> tags nested wrong! The label was encapsulating the radiobuttonlist and causing the problem from what I can make out.
ALTERNATIVE - thanks to balexandre
balexandre did provide a workable alternative by using only code-behing. Thank you!
I am including the code listing of this alternative for anyone who needs it and happens across this post.
Change the markup to look like this:
<asp:Repeater ID="Repeater1" runat="server" OnItemDataBound="Repeater1_ItemDataBound">
<ItemTemplate>
<%--<asp:Label ID="questionBody" runat="server" Text=<%# DataBinder.Eval(Container.DataItem, "Body") %>>--%>
<asp:Label ID="questionBody" runat="server" Text=<%# ((Question)Container.DataItem).Body %>>
</asp:Label>
<asp:radiobuttonlist ID="blah" runat="server" >
</asp:radiobuttonlist>
</ItemTemplate>
</asp:Repeater>
and the code-behind must have the event handler:
protected void Repeater1_ItemDataBound(Object Sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
RadioButtonList rbl = (RadioButtonList)e.Item.FindControl("blah");
// do anything with your rbl
foreach (Option opt in question.Options)
{
rbl.Items.Add(opt.ToString());
}
}
}

you can only access the template using the Repeater method OnItemDataBound witch is invoked before it draws anything to the page.
void Repeater1_ItemDataBound(Object Sender, RepeaterItemEventArgs e) {
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem) {
RadioButtonList rbl = (RadioButtonList)e.Item.FindControl["blah"];
// do anything with your rbl
}
}
Or by looping through all the Controls in the Repeater Collection, witch for example you have submited the page.
BTW, and just for your information
this code:
private string _body;
private bool? _isCorrect;
public string Body
{
get { return _body; }
}
public bool? IsCorrect
{
get { return _isCorrect; }
}
is the same as
public string Body { private get; }
public bool? IsCorrect { private get; }

Related

Add custom properties to ASP.net checkboxlist

Checkbox list items have default "Text", "Value", "Enabled" and "Selected" properties.
I need to add an "ImageUrl" property to each item in my list.
I use this code:
foreach (Zone zn in ZonesList)
{
ListItem item = new ListItem(zn.Name, zn.Id.ToString());
item.Attributes.Add("ImageUrl", zn.Image );
item.Selected = false;
visitPlaceList.Items.Add(item);
}
visitPlaceList.DataBind();
but it still doesn't show any properties other than the defaults.
How can this be achieved?
A property does get added, but as a span element surrounding the input and label. It looks like this
<span imageurl="www.google.nl">
<input id="ctl00_mainContentPane_visitPlaceList_1" type="checkbox" name="ctl00$mainContentPane$visitPlaceList$1" />
<label for="ctl00_mainContentPane_visitPlaceList_1">Name 1</label>
</span>
So if you need it with jQuery, get the correct element.
<asp:CheckBoxList ID="visitPlaceList" runat="server" ClientIDMode="Static"></asp:CheckBoxList>
<script>
$("#visitPlaceList input").change(function () {
var imageurl = $(this).closest('span').attr('imageurl');
console.log(imageurl);
});
</script>
Very interesting question! It exposes the limitations that web controls sometimes have.
I believe the proper way to solve it is by creating a custom (web) control. It is far from trivial though especially since both ListItem and CheckBoxList are sealed classes.
It can also be solved by creating a user control (ascx). The following can be improved but you get the idea:
ImageCheckBoxList.ascx
<asp:Repeater ID="Repeater1" runat="server">
<HeaderTemplate>
<table>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td>
<asp:CheckBox ID="CheckBox1" runat="server" /><asp:Image ID="Image1" runat="server" ImageUrl='<%# Eval("ImageUrl") %>' /><asp:Label ID="Label1" runat="server" AssociatedControlID="CheckBox1" Text='<%# Eval("Text") %>'></asp:Label>
</td>
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>
ImageCheckBoxList.ascx.cs
public partial class ImageCheckBoxList : System.Web.UI.UserControl
{
public IList<ImageListItem> Items { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
Repeater1.DataSource = Items;
Repeater1.DataBind();
}
for (int i = 0; i < Repeater1.Items.Count; i++)
{
var checkBox = (CheckBox)Repeater1.Items[i].FindControl("CheckBox1");
if (checkBox != null)
{
Items[i].Checked = checkBox.Checked;
}
}
}
}
where ImageListItem is:
public class ImageListItem
{
public string Text { get; set; }
public string Value { get; set; }
public string ImageUrl { get; set; }
public bool Checked { get; set; }
public ImageListItem(string text, string value, string imageUrl)
{
Text = text;
Value = value;
ImageUrl = imageUrl;
Checked = false;
}
}
Here's how to use it in a Web Forms page:
ASPX
<%# Register TagPrefix="uc" TagName="ImageCheckBoxList" Src="ImageCheckBoxList.ascx" %>
<uc:ImageCheckBoxList ID="ImageCheckBoxList1" runat="server" />
<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
Code-behind
protected void Page_Load(object sender, EventArgs e)
{
ImageCheckBoxList1.Items = new List<ImageListItem>()
{
new ImageListItem("Item 1", "Item1", "Images/1.png"),
new ImageListItem("Item 2", "Item2", "Images/2.png"),
new ImageListItem("Item 3", "Item3", "Images/3.png")
};
}
protected void Button1_Click(object sender, EventArgs e)
{
StringBuilder sb = new StringBuilder();
foreach (ImageListItem item in ImageCheckBoxList1.Items)
{
if (item.Checked)
{
sb.AppendLine($"{item.Text} with value {item.Value} is checked.");
}
}
Label1.Text = sb.ToString();
}

Rendering child controls of custom composite control inside a Repeater

I have a custom control derived from the CompositeControl class, which is defined as:
[ToolboxData("<{0}:ContractControl runat=server></{0}:ContractControl>")]
public class ContractControl : CompositeControl
{
private int contractID = 0;
private ContractTileControl tileControl = null;
private ContractDetailControl detailControl = null;
private HtmlGenericControl contractMainDiv = null;
public int ContractID
{
get { return this.contractID; }
set { this.contractID = value; }
}
public ContractTileControl TileControl
{
get { return this.tileControl; }
set { this.tileControl = value; }
}
public ContractDetailControl DetailControl
{
get { return this.detailControl; }
set { this.detailControl = value; }
}
public ContractControl()
{
this.contractMainDiv = new HtmlGenericControl("div");
this.contractMainDiv.ID = "contractMainDiv";
this.contractMainDiv.Attributes.Add("class", "contractMain");
}
#region protected override void OnPreRender(EventArgs e)
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
CreateChildControls();
}
#endregion
#region protected override void CreateChildControls()
protected override void CreateChildControls()
{
if (tileControl != null)
{
this.contractMainDiv.Controls.Add(tileControl);
}
if (detailControl != null)
{
this.contractMainDiv.Controls.Add(detailControl);
}
this.Controls.Add(contractMainDiv);
}
#endregion
}
When I add a number of them to a placeholder control they render fine, but when I try to bind the same ones to a Repeater, the child composite controls tileControl and detailControl do not render, only the contractMainDiv does.
The repeater is defined as:
<asp:Repeater ID="myRepeater" runat="server" EnableTheming="true">
<HeaderTemplate>
<table border="0" cellpadding="0" cellspacing="0">
</HeaderTemplate>
<ItemTemplate>
<tr><td><easit:ContractControl ID="contractControl" runat="server" />
</td></tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>
And I bind to it by first generating a List<ContractControl> and then calling:
List<ContractControl> controls = new List<ContractControl>();
//Generate custom controls for each element in input dictionary
//Create TileControl and DetailControl
//Create ContractControl and add it to collection
ContractControl contractControl = new ContractControl();
contractControl.ContractID = contract.Contract.Id;
contractControl.TileControl = tile;
contractControl.DetailControl = detail;
controls.Add(contractControl);
myRepeater.DataSource = controls;
myRepeater.DataBind();
Yet the resulting table contains the right number of items, but the child 'CompositeControl's do not get rendered at all, only the contractMainDiv shows up.

How to create a custom control with INamingContainer?

I am trying the following:
[PersistenceMode(PersistenceMode.InnerProperty)]
[TemplateContainer(typeof(TemplateContainer))]
public virtual ITemplate LayoutTemplate { get; set; }
protected void Page_Init(object sender, EventArgs e)
{
this.Controls.Clear();
if (LayoutTemplate != null)
{
var data = Enumerable.Range(0, 10);
foreach (int index in data)
{
TemplateContainer container = new TemplateContainer(index);
LayoutTemplate.InstantiateIn(container);
this.Controls.Add(container);
}
}
}
My container class:
public class TemplateContainer : Control, INamingContainer
{
public int Index { get; set; }
internal TemplateContainer(int index)
{
this.Index = index;
}
}
And my markup:
<uc:TemplateControl ID="ucTemplateControl" runat="server">
<LayoutTemplate>
<b>Index:</b>
<asp:TextBox ID="Literal1" runat="server"
Text='<%# Container.Index %>'
ReadOnly="true" />
<br />
</LayoutTemplate>
</uc:TemplateControl>
But for some reason Container.Index is not rendering any value, just empty. 10 controls are being created, but none shows a value.
What did I do wrong? How can I fix it so it will show the Index value?
I tried something similar to MSDN example:
How to: Create Templated ASP.NET User Controls
To bind the value you need to call the DataBind method of the custom control.
Calling
ucTemplateControl.DataBind();
from the page made the data to be bound on the template.

C# DataBinding List<string> to DataGridColumn

I defined a example class below:
class Shops : INotifyPropertyChanged
{
private int _productId;
private string _productName;
private List<string> _test;
private bool _active;
public int ProductId
{
get { return _productId; }
set { _productId = value; }
}
public string ProductName
{
get { return _productName; }
set { _productName = value; }
}
public List<string> Test
{
get { return _test; }
set { _test = value; }
}
public bool Active
{
get { return _active; }
set { _active = value; }
}
}
When I databind this to a DataGridView, everything except the Test is bound correctly. The checkbox for Active is also created automaticly in the datagridview.
Is there way to tell the databinding that the List needs to be bound as a ComboBox?
Thanks for the time.
I am assuming you are trying to bind the list to a Column Cell that is flagged as a ComboBox.
You will need to specify the column in the data grid "Columns" collection, and set data binding for the column. If you have already done that, please post a more detailed description.
Try this, use a template field to add the combo box and then add the data to is in the OnRowDataBound Event.
<asp:GridView ID="GridView1" runat="server"
OnRowDataBound="bindCombobox">
<Columns>
//other columns
<asp:TemplateField HeaderText="status" >
<ItemTemplate>
<asp:DropDownList ID="comboBox1" runat="server"></asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
in the code behind:
public void bindComboBox(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType != DataControlRowType.DataRow)
{
return;
}
DropDownList ddlist = (DropDownList)e.Row.FindControl("comboBox1");
ddlist.AppendDataBoundItems = true;
ddlist.DataSource = Test; //insert your list here
ddlist.DataBind();
}

User Control Postback

I am working on rendering a set of cart items using a user control. Each cart item can be removed via a button in the user control. When a cart item is removed I need to visually show it's removal. However, since the cart item existed during the loading of the page it remains until the page is refreshed again. What I am after is a means to refresh the page after the work to remove the cartitem has been completed.
The code behind cart.aspx.cs looks like:
protected void Page_Init(object sender, EventArgs e)
{
CreateCartItemControls();
}
private void CreateCartItemControls()
{
foreach (CartItem ci in Profile.Cart.Items)
{
ASP.CartItemControl cic = new ASP.CartItemControl();
cic.ItemName = ci.Name;
cic.CartID = ci.ID;
cic.Cost = ci.BaseCost.ToString("c");
cic.ItemComponents = ci.Components;
cic.CartItemRemoved += new EventHandler(CartItemRemoved);
Content.Controls.Add(cic);
}
}
void CartItemRemoved(object sender, EventArgs e)
{
Master.UpdateCartItemCount();
}
Markup for CartItemControl.ascx
<%# Control Language="C#" ClassName="CartItemControl" AutoEventWireup="true"
CodeFile="CartItemControl.ascx.cs"
Inherits="UserControls_CartItemControl" %>
<fieldset id="FieldSet" runat="server">
<legend>
<asp:HyperLink ID="ItemLink" runat="server" />
</legend>
<asp:ImageButton ID="RemoveCartItem" AlternateText="Remove Item"
ImageUrl="~/img/buttons/remove_4c.gif" runat="server"
CommandName="Remove" OnCommand="RemoveCartItem_Command" />
<asp:Label ID="TotalItemCost" runat="server" Text="$0.00" />
<ol>
<li runat="server" id="ComponentsLI" visible="false">
<fieldset id="ComponentsFieldSet" runat="server">
<legend>Item Components</legend>
<asp:CheckBoxList ID="ItemComponentsCheckList"
runat="server" />
</fieldset>
</li>
</ol>
</fieldset>
Code behind for the UserControl CartItemControl.ascx.cs
public partial class UserControls_CartItemControl
: System.Web.UI.UserControl
{
public string ItemName { get; set; }
public int CartID { get; set; }
public string Cost { get; set; }
public IDictionary<int, SoftwareComponent> ItemComponents { get; set; }
protected void Page_PreRender(object sender, EventArgs e)
{
SetCartItemControlAttributes();
}
private void SetCartItemControlAttributes()
{
ItemLink.Text = ItemName;
TotalItemCost.Text = Cost;
RemoveCartItem.CommandArgument = CartID.ToString();
if (!ItemComponents.Count.Equals(0))
{
ComponentsLI.Visible = true;
foreach (KeyValuePair<int, ItemComponent> kvp in
ItemComponents)
{
ItemComponentsCheckList.Items.Add(
new ListItem(string.Format("{0} {1}",
kvp.Value.ComponentName,
kvp.Value.ComponentCost.ToString("c")),
kvp.Key.ToString()));
}
}
}
public event EventHandler CartItemRemoved;
protected void RemoveCartItem_Command(object sender, CommandEventArgs e)
{
int itemID;
if (int.TryParse(e.CommandArgument.ToString(), out itemID))
{
Profile.Cart.RemoveCartItem(itemID);
CartItemRemoved(sender, e);
Parent.Controls.Remove(this);
}
}
}
Just as you add CartItemControls to Content's Controls collection on init, you need to remove them on RemoveCartItem_Command. Do so by either exposing your own ItemRemoved event and handling it in the main page or by calling Parent.Controls.Remove(this) inside the RemoveCartItem_Command.
Or am I missing something?
Response.Redirect back to the page after you do the work to remove the cart item.
Try Server.Transfer to the same page.
That said, consider using Ruslan approach, by exposing an ItemRemoved event and handling it on the main page. You can do Content.Controls.Clear and call CreateCartItemControls again.
Just rebind the everything on the main page on every postback.

Categories