TitleCheckBoxList - Adding Column Headers / Titles to a CheckBoxList Control






4.64/5 (8 votes)
Extending the CheckBoxList control to allow ListItems that serve as column headers / titles / category headings and not render a CheckBox.
Introduction
The TitleCheckBoxList
control is designed to allow you to insert column headers / titles into a CheckBoxList
. The titles are included in the Items
collection of the ListControl
, but do not render a CheckBox
on the form.
This control is useful if you have a number of options that you want to display in a list of checkboxes and you want to group and organize the checkboxes according to the type or category of the option it represents.
Using the Control
Step 1: Add the TitleCheckBoxList.cs class file to your project in the App_Code folder.
You can download this C# class file by clicking the 'Download source code' link at the top of this article.
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.IO;
namespace VwdCms
{
public class TitleCheckBoxList : CheckBoxList
{
public const string TitleValue = "###title###";
public const string SpacerValue = "###spacer###";
private Unit _titleWidth;
public Unit TitleWidth
{
get { return _titleWidth; }
set { _titleWidth = value; }
}
protected override void RenderItem(ListItemType itemType,
int repeatIndex, RepeatInfo repeatInfo, HtmlTextWriter writer)
{
ListItem itm = this.Items[repeatIndex];
string val = itm.Value;
if (val == TitleValue || val == SpacerValue )
{
if (repeatIndex == 0)
{
StringWriter sw = new StringWriter();
HtmlTextWriter dummyWriter = new HtmlTextWriter(sw);
base.RenderItem(itemType, repeatIndex,
repeatInfo, dummyWriter);
}
if (val == TitleValue)
{
Label lbl = new Label();
lbl.Font.CopyFrom(this.Font);
lbl.Font.Bold = true;
lbl.Text = itm.Text;
if (this.TitleWidth != null)
{
lbl.Width = this.TitleWidth;
}
lbl.RenderControl(writer);
}
else if (val == SpacerValue)
{
writer.Write(HtmlTextWriter.SpaceChar);
}
}
else
{
base.RenderItem(itemType, repeatIndex, repeatInfo, writer);
}
}
}
}
Step 2: Update your web.config file's Controls
section (inside the system.web
section):
<pages>
<controls>
<add tagPrefix="VwdCms" namespace="VwdCms"/>
</controls>
</pages>
Step 3: Add a TitleCheckBoxList
control to your Web Form:
<VwdCms:TitleCheckBoxList runat="server" ID="cblTitles"
RepeatDirection="Vertical" RepeatColumns="5"
style="font-family:Arial;font-size:8pt;text-align:left;
border:solid 1px #336699;" TitleWidth="90px" />
Some Examples
You can download the code for these examples by clicking the 'Download demo' link at the top of this article.
Example 1
This example demonstrates how the TitleCheckBoxList
will render when all of the columns have the same number of items.
Since we know in advance in this example that we will have five different "Categories" (Titles), we can set the RepeatColumns
property of the CheckBoxList
to 5 and we get a clean looking display of 5 columns with an equal number of CheckBox
es.
By setting the TitleWidth
property of TitleCheckBoxList
, we can get the spacing between the columns without having to work with the RepeatLayout
of the CheckBoxList
.
Here is the code to load this TitleCheckBoxList
:
if (!this.Page.IsPostBack)
{
// if this is the initial load (HTTP GET) of the page
// then put some items in the checkboxlist
int catNum = 0;
int itmNum = 0;
int i = 0;
ListItem itm = null;
for (catNum = 0; catNum < 5; catNum++)
{
itm = new ListItem("Category " + catNum.ToString(),
VwdCms.TitleCheckBoxList.TitleValue);
this.cblTitles.Items.Add(itm);
itmNum++;
for (i = 0; i < 6; i++)
{
itm = new ListItem("Item " + itmNum.ToString(),
itmNum.ToString());
// select the even ones
itm.Selected = (itmNum % 2 == 0);
this.cblTitles.Items.Add(itm);
itmNum++;
}
}
}
Example 2
This example demonstrates how the TitleCheckBoxList
will render when the number of items in each category is different.
For some applications, this might be acceptable; for others, it may be necessary to make the display a little bit cleaner. See Example 3 to see how to clean up the display.
Here is the code to load this TitleCheckBoxList
:
if (!this.Page.IsPostBack)
{
// if this is the initial load (HTTP GET) of the page
// then put some items in the checkboxlist
// this example demonstates how the TitleCheckBoxList will
// render when the nubmer of items under
// each category is different
int catNum = 0;
int itmNum = 0;
int i = 0;
int itemCount = 6;
ListItem itm = null;
for (catNum = 0; catNum < 5; catNum++)
{
itm = new ListItem("Category " + catNum.ToString(),
VwdCms.TitleCheckBoxList.TitleValue);
this.cblTitles.Items.Add(itm);
itmNum++;
for (i = 0; i < itemCount; i++)
{
itm = new ListItem("Item " + itmNum.ToString(),
itmNum.ToString());
// select the even ones
itm.Selected = (itmNum % 2 == 0);
this.cblTitles.Items.Add(itm);
itmNum++;
}
itemCount--;
}
}
Example 3
This example demonstrates how the TitleCheckBoxList
will render when the number of items in each category is different, but we can get a clean column display by adding "spacer" ListItem
s.
Here is the code to load this TitleCheckBoxList
:
if (!this.Page.IsPostBack)
{
// if this is the initial load (HTTP GET) of the page
// then put some items in the checkboxlist
// this example demonstates how the TitleCheckBoxList will
// render when the nubmer of items under
// each category is different
int catNum = 0;
int itmNum = 0;
int i = 0;
int itemCount = 6;
// getting the itemCountMax in a real world application will be
// more challenging, you may have to loop through the collection
// once before looping through and adding list items.
int itemCountMax = 6;
ListItem itm = null;
for (catNum = 0; catNum < 5; catNum++)
{
itm = new ListItem("Category " + catNum.ToString(),
VwdCms.TitleCheckBoxList.TitleValue);
this.cblTitles.Items.Add(itm);
itmNum++;
for (i = 0; i < itemCountMax; i++)
{
if (i > itemCount - 1)
{
// add a spacer
itm = new ListItem(string.Empty,
VwdCms.TitleCheckBoxList.SpacerValue);
}
else
{
itm = new ListItem("Item " + itmNum.ToString(),
itmNum.ToString());
// select the even ones
itm.Selected = (itmNum % 2 == 0);
}
this.cblTitles.Items.Add(itm);
itmNum++;
}
itemCount--;
}
}
Handling Postbacks
There is a slight difference during postBack processing that you will need to keep in mind when using the TitleCheckBoxList
. The underlying CheckBoxList
thinks that all of the ListItem
s in the Items
collection are valid CheckBox
controls. In fact, they are valid ListItem
s, but the TitleCheckBoxList
's RenderItem
method prevents the CheckBox
control from being rendered for the title and spacer items.
The result is that your code will need to check for titles and spacers as you iterate through the Items
collection to determine which items are checked and not checked.
Here is some example code:
void btnPostback_Command(object sender, CommandEventArgs e)
{
// loop through the items and add the selected ones
// to the "selected" label control and add the
// non-selected ones to the "non-selected" label
// Note: the CheckBoxList control thinks that the
// Title items and Spacer items are valid ListItems,
// but we want to ignore them when looping through
// the collection
StringBuilder sbSelected = new StringBuilder();
StringBuilder sbNonSelected = new StringBuilder();
foreach (ListItem itm in this.cblTitles.Items)
{
if (itm.Value != VwdCms.TitleCheckBoxList.TitleValue
&& itm.Value != VwdCms.TitleCheckBoxList.SpacerValue )
{
if (itm.Selected)
{
sbSelected.Append(itm.Text);
sbSelected.Append(", ");
}
else
{
sbNonSelected.Append(itm.Text);
sbNonSelected.Append(", ");
}
}
}
this.lblSelected.Text = sbSelected.ToString();
this.lblNonSelected.Text = sbNonSelected.ToString();
}
Points of Interest
What the heck is dummyWriter doing in the RenderItem method of TitleCheckBoxList?
I ran into a strange problem when I tried out the overridden RenderItem
method - all of the checkboxes in the TitleCheckBoxList
were disabled. When I tried it without any title ListItem
s, the checkboxes were enabled. After a few minutes of experimentation, I figured out that it only happens when the first ListItem
is a title. Apparently, the RenderItem
method of the base class, CheckBoxList
, is doing something when the first ListItem
is rendered, and if you don't use the RenderItem
of the base class for that first ListItem
, all of the checkboxes will be disabled.
I looked at the CheckBoxList
class documentation and could not find any way to control this behavior. So, the bottom line is that I need to call the CheckBoxList
's RenderItem
method on the first ListItem
, despite the fact that I really don't want to use it. What I want is to occasionally override the behavior to render titles, but otherwise use the base class implementation for rendering checkboxes. In my opinion, this issue is a bug in the CheckBoxList
class since you should not be forced to use the base method if the class is truly virtual. I haven't bothered to report this to Microsoft - feel free to let them know about it!
The simplest solution that I could think of was to execute the base class' RenderItem
method, but don't let it output any HTML to the Web Form. This is where the dummyWriter
comes in to play; the base class does its work on the first ListItem
and outputs HTML to the dummyWriter
which never gets used for anything.
Taking it Further...
Rendering horizontally
For the moment, the TitleCheckBoxList
is doing exactly what I need, so I have not added any other feature or tried any other variations on the output. Your needs will certainly vary, and you should feel free to experiment with it even further. Maybe you will want to render the checkboxes in rows rather than columns, this doesn't seem like it would be too difficult to accomplish. I would approach it by setting the RepeatDirection
to "Horizontal
" and then setting the RepeatColumns
property to the maximum number of ListItem
s that you want displayed on a row. There might be some tricky code when you load the TitleCheckBoxList
so that you insert spacers and the row titles in the correct spots, but it should be possible.
Inserting other controls or content into a CheckBoxList
Is there any reason why we can't insert other types of controls into the CheckBoxList
? I needed to get a title into the CheckBoxList
, but maybe you want to insert a Button
or an Image
. Can this be done? Absolutely! If you follow the example of how to render a Label
control in the TitleCheckBoxList
's RenderItem
method, you can basically render any kind of control in place of a ListItem
.
Conclusion
The .NET Framework and ASP.NET makes it really easy to modify and extend the functionality of the classes and controls. All you need is a little bit of knowledge of how the framework is built and how the components interact with each other.
By extending the CheckBoxList
class with less than 70 lines of code, I was able to get the exact functionality that I was looking for. So, if the built-in controls are not doing what you want, don't give up on them, change the way they work to suit your needs better!