.NET, C#, CheckBoxList, Delegates, Events, ListItem, Unordered List
CheckBoxList as an HTML Unordered List with Subheadings
I was confronted with creating an array of checkboxes sorted into different categories. Keeping it as a single CheckBoxList control was preferable since it would be really easy to save the form.
The first thing I did was create my own control that inherits from CheckboxList. The table-based output of the standard checkbox list is harder to style, and in general I like to used unordered lists wherever possible, so I followed this simple method for overloading the Render method and making my own output as an <ul> and each selection as a <li>. The example Chris Pollock gives is in VB, below is my loose adaptation in C#.
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
string inputFormatString = "<input id=\"{0}\" name=\"{1}\" type=\"checkbox\" value=\"{2}\" {3} {4} />";
string labelFormatString = "{1}";
if (!string.IsNullOrEmpty(this.CssClass))
writer.WriteLine("<ul class=\"" + this.CssClass + "\">");
else
writer.WriteLine("<ul>");
for (int i = 0; i < Items.Count; i++)
{
string postbackScript = "";
if(this.AutoPostBack)
{
postbackScript = String.Format("onclick=\"javascript:setTimeout('__doPostBack(\'{0}\',\'\')', 0)\"", this.UniqueID + "$" + i.ToString());
}
string itemChecked = "";
if(Items[i].Selected)
{
itemChecked = "checked=\"checked\"";
}
writer.WriteLine("<li>");
writer.WriteLine(string.Format(inputFormatString, this.ClientID + "_" + i.ToString(), this.UniqueID + "$" + i.ToString(), Items[i].Value, itemChecked, postbackScript));
writer.WriteLine(string.Format(labelFormatString, this.ClientID + "_" + i.ToString(), Items[i].Text));
writer.WriteLine("</li>");
}
writer.WriteLine("</ul>");
}
It’s a nice start, but it doesn’t solve my problem of adding headings to the list. I could go pretty far into making this CheckBoxList control really custom so that it accepts a heirarchical data source, but i decided that would be good for another time. I simply decided to create an event on the CheckBoxList that would be called before each item renders.
public delegate void ItemPreRenderHandler(System.Collections.IEnumerable data, int index, ListItem item, System.Web.UI.HtmlTextWriter writer);
public event ItemPreRenderHandler ItemPreRender;
public void OnItemPreRender(System.Collections.IEnumerable data, int index, ListItem item, System.Web.UI.HtmlTextWriter writer)
{
if (ItemPreRender != null)
{
ItemPreRender(data, index, item, writer);
}
}
And call OnItemPreRender in the Render method as the first command in the for loop for each item.
OnItemPreRender(((System.Collections.IEnumerable)this.DataSource), i ,Items[i], writer);
With this, on the page using the control, I hooked a method up to the Event that checked to see if the ‘Category’ was different than the last item, and if so output an <li> with a heading. Very old school, but rather simple and flexible.
From → C#, Development, Web Development
I realize this may be outside the scope of this article, but could you share your implementation of the following:
“… On the page using the control, I hooked a method up to the Event that checked to see if the ‘Category’ was different than the last item”
Sure thing. In the Page_Load method of the code behind for my .aspx page, i put in a line to bind the ItemPreRender event of my control to a method.
// Bind event for ListItem PreRender, so we can add the Organization headings in the middle cblInvestigators.ItemPreRender += CheckBox_ItemPreRender;Then I created the method CheckBox_ItemPreRender in the codebehind as well. This is a pretty sloppy version, but I’m handing the event the original list of data being bound, as well as the current index and list item being operated on. The HtmlTextWriter is what you’ll use to create output (kind of like Response.Write for classic ASP).
protected void CheckBox_ItemPreRender(System.Collections.IEnumerable data, int index, ListItem item, System.Web.UI.HtmlTextWriter writer) { // Get the item that was databound to the checkbox Model.Profile profile = data.Cast().ElementAt(index); // if this is the first item belonging to this org, show the header if (previousOrgId != profile.Org.Id) writer.Write("my header text that is different from the last row!"); // store the orgId to compare against the next item previousOrgId = profile.Org.Id; }Sorry if this does not format the code correctly in the comments, you may need to just cut and paste :)
Thanks for this. It helped me out :) I read that in ASP.NET 4 unordered list will be available from the base control. Until then this is excelent.