65.9K
CodeProject is changing. Read more.
Home

TitleCheckBoxList - Adding Column Headers / Titles to a CheckBoxList Control

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (8 votes)

Apr 19, 2007

CPOL

5 min read

viewsIcon

77313

downloadIcon

1078

Extending the CheckBoxList control to allow ListItems that serve as column headers / titles / category headings and not render a CheckBox.

Screenshot - TitleCheckBoxList1.gif

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 CheckBoxes.

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.

TitleCheckBoxList renders Titles at the top of each column

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.

TitleCheckBoxList renders Titles at the top of each column

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" ListItems.

TitleCheckBoxList renders Titles at the top of each column

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 ListItems in the Items collection are valid CheckBox controls. In fact, they are valid ListItems, 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 ListItems, 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 ListItems 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!