Click here to Skip to main content
15,895,084 members
Articles / Web Development / ASP.NET

How to build a web user control that allows single or multiple selection of items

Rate me:
Please Sign up or sign in to vote.
3.80/5 (7 votes)
27 Nov 2006CPOL9 min read 44.6K   316   18   2
This article describes how to build a control that extends the notions of CheckBox and RadioButton in order to allow users to select one or more items from a list.

Sample Image - SelectItemsControl.jpg

The problem

How many times where you required to let users select some data from a database? This means allowing the user to check some ticks on the data from the database and then to perform some actions based on the selected items.

In ASP.NET 2.0, there are two controls that could do the trick: CheckBoxList and RadioButtonList. These controls allow you to set a DataSource property to them, and they will handle all the work. However, if you need to get the selected items in the list, you have to do that by hand, checking each element if it is selected. This is not a bad approach, it is just error prone one, because you have to write the same code over and over again.

Another issue is the fact that I do not have a way of choosing how the elements will be displayed. If I want, at some point, to change the way elements get selected from a multiple selection to a single selection, then I need to remove the CheckBoxList, add the RadioButtonList, and then (re)write the necessary logic to make it work. This is a big productivity issue because instead of being able to change a single property, you need to remake it from scratch (almost).

What if you need to preselect some of the elements in the list you are displaying? The regular way would be to investigate each element as it comes from the database and set the Selected property on the bound item. Wouldn't it be nice if you could just pass an array of elements to be selected to a control and have it do all the work?

These are the problems I am addressing with this article.

Goals:

  • I want to have a control that will permit me to display selections to users.
  • I want to be able to change the display method from single select (i.e., RadioButton) to multiple select (i.e., CheckBox) by changing a single property.
  • I want to be able to retrieve a list of selected items from the control.
  • I want to be able to preselect some items in the list by passing in an array of selected elements.

A little background

In a selection dialog, we usually want to be able to display some text to the user, and when the user selects that text, to have a way of associating the text with a more meaningful element from the data, say the primary key of the selected element.

So, besides showing the text to the user, we must also keep the data that the element represents. This is what happens in ASP.NET also. For a ComboBox, you have a SelectedText and a SelectedValue. SelectedText is the text displayed to the user, and SelectedValue is the meaningful value (database wise) of that text.

Let's say we have some data that we need to display to the user and allow them to select some items from it. You could pass in a DataTable and specify which column is the display column and which columns is the value, or you could just pass in a Connection String and a SQL query instead of a DataTable and have the control retrieve the data from the database. This control will support both scenarios.

We need this dual approach because setting the Connection String and SqlCommand can be done in Design, while setting a DataTable is most likely to happen in code. Because a connection string can be stored in a web.config, you could just pass the name of the configuration element where the connection string is stored.

The idea

The idea is to use existing controls from ASP.NET and add some extra functionality to them. For example, we could use the CheckBox control to display an item in a multi-select list, and a RadioButton to display an element when in simple select mode. This way, we reuse the existing code and its functionality.

For instance, a RadioButton will deselect all of the other RadioButtons in a group when it is selected. This is a very useful behavior that we get "for free".

Solution

When stating this problem, the solution becomes clear very quickly. We get a DataTable. For each row in that DataTable, we must create a corresponding control. The type of control will be determined based on the selection mode specified. If we want single selection, we will add RadioButtons, and if we want multiple selection, we would add CheckBoxes.

There is only one catch here: we need to store two pieces of information in a CheckBox (or RadioButton) control:

  1. Text - which represents the part that is displayed to the user.
  2. Value - the corresponding value associated with the text. This value should not be displayed to the user.

The problem is that, by design, a CheckBox or a RadioButton only has room to store one of these, in a property called Text. Of course, one could choose to embed the associated value with the ID of the control (the ID is used to uniquely identify the control on a page).

I have seen this practice and I am not comfortable with it. The reason is that, in order to retrieve the value from the control, we must resort to some string manipulation. Moreover, the value is available in clear text in the page source, and thus it is vulnerable to change. Imagine someone using as a value the Credit Card number of a user. That data would be available in clear text to anybody!!!

So we need to find a way of not showing the Value in clear text.

Fortunately, this is not such a big problem. Because a CheckBox is a class in .NET, we can simply derive from that class and create our own CheckBox and call it CheckBoxWithValue. In this derived class, we can declare a property Value that can contain the associated value for a control. Because we are deriving from the CheckBox class, we get the behavior of the CheckBox with no extra charge. We can now use our derived control as if it was a regular CheckBox. And, it will feel like a CheckBox. The only difference will be in the fact that now, we have a way of storing both the Text and the Value in one control.

C#
internal class CheckBoxWithValue : System.Web.UI.WebControls.CheckBox
{
    private object myValue;
 
    /// <summary>
    /// This property will be used to store the associated Value
    /// </summary>
    public object Value
    {
        get { return myValue; }
        set { myValue = value; }
    }
}

The Value is implemented as an object in order to have the possibility to store any type of object in that property.

What do we gain by doing this? Well, besides the fact that now there is room to store the associated value in the control, the value is also encrypted. The encryption is part of the ASP.NET ViewState mechanism. The ViewState will encrypt all data regarding the controls on the form, and this includes our controls, thus the data will not be available to an attacker. Furthermore, ASP.NET will detect any change to the ViewState and report it to the user. More information regarding the ViewState can be found here.

Now that we have this class, we can use it in order to create the list of controls. The code that builds the list of controls is shown below:

C#
foreach (DataRow myRow in myData.Rows)
{
    Control myControl = null;
    switch (myOptionsType)
    {
        case OptionsSelect.Single:
            {
                RadioButtonWithValue myBut = new RadioButtonWithValue();
                myBut.Text = myRow[myDisplayField].ToString();
                myBut.Value = myRow[myValueField];
                myBut.ID = string.Format("{0}_{1}", "RadioButton", i++);
                myBut.GroupName = string.Format("GroupName{0}", this.ID);
                //We select one of the RadioButtons (the first one)
                if (i == 1)
                    myBut.Checked = true;

                myControl = myBut;

                break;
            }
        case OptionsSelect.Multiple:
            {
                CheckBoxWithValue myCheck = new CheckBoxWithValue();
                myCheck.Text = myRow[myDisplayField].ToString();
                myCheck.ID = string.Format("{0}_{1}", "CheckBox", i++);
                myCheck.Value = myRow[myValueField];
                myControl = myCheck;
                break;
            }
    }
    ObjectsPanel.Controls.Add(myControl);
    ObjectsPanel.Controls.Add(new LiteralControl("<br/>"));
}

You can see that we add the RadioButtons to a single group of elements. We do this so we can benefit from the auto-deselect of other items when we select a new one.

The control also provides the means of retrieving the selected elements in the list. For this, we look at each control that was added and check its Checked property. If the property is set, then we add the Value of it to a list. After we look at all of the controls, we return the list.

C#
List<object> myItems = new List<object>();
switch (myOptionsType)
{
    case OptionsSelect.Single:
        {
            foreach (Control myControl in ObjectsPanel.Controls)
            {
                RadioButtonWithValue myRadio = null;
                if (myControl.GetType() == typeof(RadioButtonWithValue))
                {
                    myRadio = (RadioButtonWithValue)myControl;
                    if (myRadio.Checked == true)
                    {
                        myItems.Add(myRadio.Value);
                        break;
                    }
                }
            }
            break;
        }
    case OptionsSelect.Multiple:
        {
            foreach (Control myControl in ObjectsPanel.Controls)
            {
                CheckBoxWithValue myCheck = null;
                if (myControl.GetType() == typeof(CheckBoxWithValue))
                {
                    myCheck = (CheckBoxWithValue)myControl;
                    if (myCheck.Checked == true)
                    {
                        myItems.Add(myCheck.Value);
                    }
                }
            }
            break;
        }
}
return myItems.ToArray();

This function is actually a get accesor for the property SelectedItems of the control. This property adheres to the design pattern of other controls that expose this functionality (i.e., WinForms ListBox).

We can also provide the control with an InitialState, by passing an array of objects representing the Values of the elements that need to be selected.

C#
List<object> myItems = new List<object>();
foreach (Control myControl in ObjectsPanel.Controls)
{
    if (myControl.GetType() == typeof(RadioButtonWithValue))
    {
        RadioButtonWithValue myButt = (RadioButtonWithValue)myControl;

        foreach (object o in values)
        {
            if (myButt.Value.ToString() == o.ToString())
                myButt.Checked = true;
        }
    }
    else if (myControl.GetType() == typeof(CheckBoxWithValue))
    {
        CheckBoxWithValue myButt = (CheckBoxWithValue)myControl;

        foreach (object o in values)
        {
            if (myButt.Value.ToString() == o.ToString())
                myButt.Checked = true;
        }
    }
}

And with this function, the functionality of the control is complete. You can find the source code for the control at the top of this article, along with a sample web site that uses it. Read more to see how you can use this control.

Using the control

Because the OptionsControl is implemented as a UserControl, it can be dragged & dropped from Solution Explorer onto a page.

There are a few properties that need to be set on the control before it can be used. Below, you will find a table of the properties, its role, and a suggested value.

PropertyDescription
HeightThe height of the control. Can be left blank.
WidthThe width of the control. Can be left blank.
OptionsType

The select mode. Available options are:

  • Single
  • Multiple
ConnectionString

The connection string used to connect to the database. This value can contain either a full connection string or the name of the connection string from the web.config where the full connection string is stored.

This allows you to use the same connection string in more places.

CommandStringThe SQL query that should be run on the database in order to retrieve the data. This value can be left blank if the data is set on the control by using the DataTable property.
CommandTypeThe type of the command to use. This has to be set to StoredProcedure when the text specified in CommandString is the name of a Stored Procedure. When we specify a SQL query, this property must be set to Text.
DisplayFieldThe name of the column in the database that contains the text to be displayed by the user.
ValueFieldThe name of the column in the database that contains the value that will be associated with the text.
DataTableThe DataTable to use for displaying controls instead of retrieving data from the database.
InitialStateAn array of objects that contains the values that must be checked. This array should contain the elements specified in the ValueField, and not the ones in DisplayField.

For a demonstration of this control, please refer to the top of the article where you will find both the control and a web site that utilizes the control.

Conclusion

In this article, I have described the way to build a user control that allows single and multiple selection of elements from a DataTable.

The control created follows the goals set in the introduction, and can be used in ASP.NET web applications in order to display a selection of items to users and allow them to choose from them.

Hopefully you enjoyed the read and you will use this control in your applications.

Happy coding!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Microsoft
United States United States
I am working on the C# compiler at Microsoft since 2007.

Microsoft Certified Professional since 2006

Interests: C#, ASP.NET, LINQ

Comments and Discussions

 
GeneralLicence Pin
Jan Seda27-Nov-06 6:46
professionalJan Seda27-Nov-06 6:46 
GeneralRe: Licence Pin
Alexandru Ghiondea27-Nov-06 7:29
Alexandru Ghiondea27-Nov-06 7:29 
Hi Jan and thank you for your apreciation.

I would like to continue this discution in private if you agree. My email address is alexg [at] microsoft-lab [dot] pub [dot] ro.

From what I know, CodeProject has the following restrictions by default on the code presented here:
You can use code snippets and source code downloads in your applications as long as:
- You keep all copyright notices in the code intact.
- You do not sell or republish the code or it's associated article without the author's written agreement
- You agree the code is provided as-is without any implied or express warranty.
Some authors may also have specific restrictions on using code in commercial apps such as providing credit in documentation or sending them an email first.

Hope to hear from you,


Alexandru Ghiondea
MCP

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.