Writing a Days of the Week Selector Control
page 2 of 2
by Steven Smith
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 28115/ 70

In Control - Extending the CheckBoxList

[ Download Code ]
For the simplest case, we are just taking a CheckBoxList and hard-coding its contents.  We also want to add a couple of properties to save us some work with getting the bitmask data out of it, since that's the whole intent of the control (without that piece, it would hardly be worth writing a control to do this -- even with it it is barely worth it...).

So the first thing we need to do is inherit from the CheckBoxList control:

    public class DaysOfWeekSelector : System.Web.UI.WebControls.CheckBoxList
    {
 ...
    }

Next, we need to populate the control with the values we want.  However, we don't want to blow away the Items collection every time the control is rendered, since this would cause the control to lose its ViewState (if any).  The simplest way to do this is to only populate the control when it has no items, which we do in the OnLoad event (overriding and calling the CheckBoxList version of this method):

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            if (this.Items.Count == 0)
            {
                this.Items.Add(new ListItem("Sunday", "1"));
                this.Items.Add(new ListItem("Monday", "2"));
                this.Items.Add(new ListItem("Tuesday", "4"));
                this.Items.Add(new ListItem("Wednesday", "8"));
                this.Items.Add(new ListItem("Thursday", "16"));
                this.Items.Add(new ListItem("Friday", "32"));
                this.Items.Add(new ListItem("Saturday", "64"));
            }
        }

Now we have the basic functionality.  Let's add our properties, which will do the only real work involved.  I want to the bitmask, either as an integer or as a 7-digit 0-padded binary string.  So, we'll create two properties, DaysOfWeekBitMask and DaysOfWeekBitMaskString, and include functionality to get or set each of them:

        public int DaysOfWeekBitMask
        {
            get
            {
                int daysOfWeekBitMask = 0;
                foreach (ListItem item in this.Items)
                {
                    if (item.Selected)
                    {
                        daysOfWeekBitMask += Int32.Parse(item.Value);
                    }
                }
                return daysOfWeekBitMask;
            }
            set
            {
                int daysOfWeekBitMask = value;
                foreach (ListItem item in this.Items)
                {
                    if (IsBitSet(daysOfWeekBitMask, Int32.Parse(item.Value)))
                    {
                        item.Selected = true;
                    }
                    else
                    {
                        item.Selected = false;
                    }
                }
            }
        }


        public string DaysOfWeekBitMaskString
        {
            get
            {
                return System.Convert.ToString(DaysOfWeekBitMask, 2).PadLeft(7, '0');
            }
            set
            {
                DaysOfWeekBitMask = System.Convert.ToInt32(value, 2);
            }
        }

One helper function worth noting is IsBitSet, which simply does a bitwise AND to determine if a particular position in the bitmask is set.  It's shown here:

        private bool IsBitSet(int bitmask, int bitpositionvalue)
        {
            return (bitmask & bitpositionvalue) > 0;
        }

I'm sure at some point I'll want to list out a comma separated list of which days were chosen, and overriding the ToString() method seems like the best place to put this:

        public override string ToString()
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder(100);
            foreach (ListItem item in this.Items)
            {
                if (item.Selected)
                {
                    sb.Append(item.Text);
                    sb.Append(", ");
                }
            }
            if (sb.Length > 0)
            {
                return sb.ToString(0, sb.Length - 2);
            }
            return "";
        }


Now, at this point, all that really remains is to try and keep the user from doing anything stupid.  Since this isn't a commercial control, I'm not going to go overboard here.  I expect the primary user to be me, or at least somebody who can figure out that this control's contents are not to be messed with (via DataBinding or manipulating the Items collection, for instance), but we'll add a couple checks just for good measure:
        public override void DataBind()
        {
            throw new NotImplementedException();
        }


        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            if (this.Items.Count != 7)
            {
                throw new InvalidOperationException("Days of Week Items cannot be manipulated.");
            }
        }

To put this on a page, you would compile it into a DLL and then reference the DLL using the <%@ Register %> directive, specifying the tag prefix, assembly name (minus .dll extension), and namespace.  For example, if my namespace were AspAlliance.Web.Controls and my assembly were AspAlliance.Web.Controls.dll, then to use this control on a page I would add this directive to the top of the page:

<%@Register TagPrefix="AA" Namespace="AspAlliance.Web.Controls" 
Assembly="AspAlliance.Web.Controls" %>

Then, to place the control on the page, I would just use this code:

    <AA:DaysOfWeekSelector ID="WeekDays" Runat="server" />

And finally, if I want to display the same text as I did on my no controls version, I would just need this code in my Page_Load:

    void Button1_Click(object sender, EventArgs e)
    {
        Label1.Text = "Bitmask: " + WeekDays.DaysOfWeekBitMask + 
            " (" + WeekDays.DaysOfWeekBitMaskString + ")";
    }

Note - Whidbey / ASP.NET 2.0
In ASP.NET 2.0 you will be able to globally register controls by placing the necessary information in the web.config.  Scott Watermasysk describes the process
here.  To add the necessary information for this control, you would use this code (inside the <system.web> tag):

<pages>
   <controls>
      <add tagPrefix="AA" namespace="AspAlliance.Web.Controls"/>
   </controls>
</pages>

Download the source files used for this article (tested with Whidbey Beta 1 but the control code should work in .NET 1.1 and the pages will work with minimal modification).


View Entire Article

User Comments

Title: Improvement   
Name: JSx
Date: 2004-09-13 7:47:43 AM
Comment:
It is better to initialize ListItems in OnInit instead of OnLoad page processing phase. Adding days into the checkbox list in an OnInit will save some ViewState bytes, adding them in OnLoad results in the "ViewState garbage problem".

Istead of

protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (this.Items.Count == 0)
{
this.Items.Add(new ListItem("Sunday", "1"));
this.Items.Add(new ListItem("Monday", "2"));
this.Items.Add(new ListItem("Tuesday", "4"));
this.Items.Add(new ListItem("Wednesday", "8"));
this.Items.Add(new ListItem("Thursday", "16"));
this.Items.Add(new ListItem("Friday", "32"));
this.Items.Add(new ListItem("Saturday", "64"));
}
}

add all days in OnInit for smaller ViewState.






Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2019 ASPAlliance.com  |  Page Processed at 2019-06-19 7:07:58 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search