Add custom properties to ASP.net checkboxlist - c#

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();
}

Related

UserControl doesn't display the values of the exposed controls

I have a user control:
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="WebUserControl1.ascx.cs" Inherits="StratPlan.Main.UserCons.WebUserControl1" %>
<div>
<table>
<tr>
<td>title: </td>
<td>
<asp:TextBox ID="TitleTextBox" runat="server"/>
</td>
</tr>
<tr>
<td>strategy id: </td>
<td>
<asp:TextBox ID="StrategyIdTextBox" runat="server"/>
</td>
</tr>
<tr>
<td>company: </td>
<td>
<asp:TextBox ID="CompanyTextBox" runat="server"/>
</td>
</tr>
</table>
</div>
In its code behind:
public partial class WebUserControl1 : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
TitleTextBox.Text = ExpTitle;
StrategyIdTextBox.Text = ExpStrategyId;
CompanyTextBox.Text = ExpCompany;
}
public string ExpTitle
{
get { return this.TitleTextBox.Text; }
}
public string ExpStrategyId
{
get { return this.StrategyIdTextBox.Text; }
}
public string ExpCompany
{
get { return this.CompanyTextBox.Text; }
}
}
Then in my page, I have a list view:
<div>
<asp:ListView ID="ListView1" runat="server">
<ItemTemplate>
<uc1:WebUserControl1 runat="server" ID="WebUserControl1" ExpStrategyId='<%# Bind(StrategyId) %>' ExpTitle='<%# Bind(Title) %>' ExpCompany='<%# Bind(CompanyName) %>'/>
</ItemTemplate>
</asp:ListView>
</div>
And I bind the datasource like this in code behind:
public void LoadGridView()
{
localVm.EntityList = localVm.RetrieveMany(localVm.SearchItem);
ItemsGridView.AutoGenerateColumns = false;
ListView1.DataSource = localVm.EntityList;
ListView1.DataBind();
}
But whenever I go to my page, it doesn't give the value to the user control. what am I doing wrong?
The pages Load event is called before data bound controls are bound to their data. Change it to PreRenderComplete or Render like this
public partial class WebUserControl1 : System.Web.UI.UserControl
{
protected void Page_PreRenderComplete(object sender, EventArgs e)
{
TitleTextBox.Text = ExpTitle;
StrategyIdTextBox.Text = ExpStrategyId;
CompanyTextBox.Text = ExpCompany;
}
...
}
have a look at ASP.NET Page Life Cycle Overview

FindControl in Custom ITemplate

I have a custom UserControl which uses a simple ITemplate:
<asp:Panel runat="server" ID="pnlExpander" CssClass="expander">
<asp:HyperLink runat="server" ID="lnkExpand" Text="More Options" NavigateUrl="#" CssClass="lnkExpand"/>
<asp:Panel runat="server" ID="pnlContent" CssClass="expanderContent" style="display: none">
<asp:PlaceHolder runat="server" ID="plcContent"/>
</asp:Panel>
</asp:Panel>
The template is rendered with two simple properties:
public class Expander {
private ITemplate _contentTemplate;
public ITemplate ContentTemplate {
get { return _contentTemplate; }
set { _contentTemplate = value; }
}
protected override void OnPreRender(EventArgs e) {
if (ContentTemplate != null) {
ContentTemplate.InstantiateIn(plcContent);
}
}
Everything displays correctly, but I can't use FindControl within the template. I get a reference to my combobox from VS intellisense, but a compilation error that it's null whern I actually load the page.
To find the combobox in th the template, I'm using:
var cboFilterCriticality = AspNetUtils.FindControlRecursive(optionsExpander,"cboFilterCriticality") as DropDownList;
And the actual template looks like this on the page:
<l49:Expander runat="server" ID="optionsExpander">
<ContentTemplate>
... other controls
<asp:DropDownList ID="cboFilterCriticality" runat="server" ValidationGroup="filterGrid" DataTextField="Key" DataValueField="Value" />
</ContentTemplate>
</l49:Expander>
I resolved this by changing the UserControl which used an ITemplate. For some reason, it was calling InstantiateIn in OnPreRender, which is clearly way too late to render anything to be picked up by Page_Load in the page - see the Page LifeCycle and UserControls (half way down). I moved InstantiateIn to OnInit in the UserControl, and the problem solved itself.
The Asp.net's WebForm page:
<asp:Panel runat="server" ID="pnlExpander" CssClass="expander">
<asp:HyperLink runat="server" ID="lnkExpand" Text="More Options" NavigateUrl="#" CssClass="lnkExpand"/>
<asp:Panel runat="server" ID="pnlContent" CssClass="expanderContent" style="display: none">
<asp:PlaceHolder runat="server" ID="plcContent"/>
</asp:Panel>
</asp:Panel>
define the Expander class as following:
public class Expander {
public ITemplate ContentTemplate {get ;set;}
public HtmlGenericControl ContentTemplateContainer{get;set;}
protected override void OnInit(EventArgs e) {
this.ContentTemplateContainer = new HtmlGenericControl("div");
if (ContentTemplate != null) {
ContentTemplate.InstantiateIn(container);
}
plcContent.Controls.Add(container);
}
}
in OnInit of Page:
public override void OnInit(EventArgs e){
base.OnInit(e);
ViewState["ContentTemplateContainerID"] = ContentTemplateContainer.ClientID;
}
and finally in Javascript :
var containerID = ViewState("ContentTemplateContainerID");
var elID = $get(containerID)[0].id;
var expander = $find(elID);

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.

Unable to populate RadioButtonList inside ASP.NET Repeater Template

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; }

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