DropDownList with optgroup

Many moons ago, I asked if anyone else thought it was strange that there was no way to add optgroups to the DropDownList. I've seen a number of solutions, including those involving control adapters, but I wanted to make something a little more simple, if hackish, for the old toolbox. Believe it or not, this tag has been around since HTML 4, and you've probably seen it before as non-selectable headings in a drop down or list box.

The first issue is that, not surprisingly, ListItem is sealed and can't be messed with. I say not surprisingly because it's used in more than one type of control, so what might appear in one might not make sense in another. RadioButtonLists obviously would have no use for this, but DropDownList and ListBox do.

Anyway,  it seemed to me that the easiest thing to do was to find the method that did the ListItem rendering, hack it, and use it as the replacement. DropDownList has just such a method called RenderContents, so I Reflector'd it and hacked it and came up with this:

protected override void RenderContents(System.Web.UI.HtmlTextWriter writer)
{
    if (this.Items.Count > 0)
    {
        bool selected = false;
        bool optGroupStarted = false;
        for (int i = 0; i < this.Items.Count; i++)
        {
            ListItem item = this.Items[i];
            if (item.Enabled)
            {
                if (item.Attributes["optgroup"] != null)
                {
                    if (optGroupStarted)
                        writer.WriteEndTag("optgroup");
                    writer.WriteBeginTag("optgroup");
                    writer.WriteAttribute("label", item.Text);
                    writer.Write('>');
                    writer.WriteLine();
                    optGroupStarted = true;
                }
                else
                {
                    writer.WriteBeginTag("option");
                    if (item.Selected)
                    {
                        if (selected)
                        {
                            this.VerifyMultiSelect();
                        }
                        selected = true;
                        writer.WriteAttribute("selected", "selected");
                    }
                    writer.WriteAttribute("value", item.Value, true);
                    if (item.Attributes.Count > 0)
                    {
                        item.Attributes.Render(writer);
                    }
                    if (this.Page != null)
                    {
                        this.Page.ClientScript.RegisterForEventValidation(this.UniqueID, item.Value);
                    }
                    writer.Write('>');
                    HttpUtility.HtmlEncode(item.Text, writer);
                    writer.WriteEndTag("option");
                    writer.WriteLine();
                }
            }
        }
        if (optGroupStarted)
            writer.WriteEndTag("optgroup");

    }
}

I said it was hackish because of the way you create the groups. You'll need to add ListItems to the collection, and add an attribute to them to let this rendering piece know you mean business. Something like this:

ListItem item = new ListItem("some group name", String.Empty);
item.Attributes["optgroup"] = "optgroup";
myDropDown.Items.Add(item);

Works like a champ, and has the desired output. You don't need to worry about someone selecting a group since you can't. There are still some viewstate and postback issues I haven't worked out, but nothing that an experienced control developer (i.e., someone other than me) hasn't seen before.

 

12 Comments

  • Now if you could just hack in checkboxes like the comparable WinForms control I'd be ecstatic :)

    Great job, btw.

  • Sorry, new to .net, can you please explain a little how to use your code?
    Thanks,
    Gustavo

  • It goes in a class that inherits DropDownList. It should be either compiled in a class library or in App_Code.

  • 10x man, great job !
    If you want make custom control and share it with the world ;)

  • Great job!!!!!
    Thanks a lot

    :)

  • Well, you could use a control that already has that.
    It has about the same code as this one presented here, but also a few more tricks, like finding out to which group does the selected item belongs to.

    Check it out
    http://www.codeplex.com/SharpPieces/

  • HI thanks for submitting the very useful code
    but i am not being able to add items in dropdownlist
    will u please tell me how can i add items as well as groups in dropdown list.
    my code is running fine and has no error.but dont know how to add items with option group.

    thanks

  • By CodeProject url:
    http://www.codeproject.com/KB/custom-controls/DropDownListOptionGroup.aspx?display=PrintAll&fid=338699&df=90&mpp=25&noise=3&sort=Position&view=Quick&fr=26

    Add this to method in ur Class file.

    protected override object SaveViewState()
    {
    // Create an object array with one element for the CheckBoxList's
    // ViewState contents, and one element for each ListItem in skmCheckBoxList
    object[] state = new object[this.Items.Count + 1];

    object baseState = base.SaveViewState();
    state[0] = baseState;

    // Now, see if we even need to save the view state
    bool itemHasAttributes = false;
    for (int i = 0; i 0)
    {
    itemHasAttributes = true;

    // Create an array of the item's Attribute's keys and values
    object[] attribKV = new object[this.Items[i].Attributes.Count * 2];
    int k = 0;
    foreach (string key in this.Items[i].Attributes.Keys)
    {
    attribKV[k++] = key;
    attribKV[k++] = this.Items[i].Attributes[key];
    }

    state[i + 1] = attribKV;
    }
    }

    // return either baseState or state, depending on whether or not
    // any ListItems had attributes
    if (itemHasAttributes)
    return state;
    else
    return baseState;
    }

    protected override void LoadViewState(object savedState)
    {
    if (savedState == null) return;

    // see if savedState is an object or object array
    if (savedState is object[])
    {
    // we have an array of items with attributes
    object[] state = (object[])savedState;
    base.LoadViewState(state[0]); // load the base state

    for (int i = 1; i < state.Length; i++)
    {
    if (state[i] != null)
    {
    // Load back in the attributes
    object[] attribKV = (object[])state[i];
    for (int k = 0; k < attribKV.Length; k += 2)
    this.Items[i - 1].Attributes.Add(attribKV[k].ToString(), attribKV[k + 1].ToString());
    }
    }
    }
    else
    // we have just the base state
    base.LoadViewState(savedState);
    }

  • i can not understand

    RenderContents
    not found override mthd

    again

    this.VerifyMultiSelect();
    not found

    i want full code
    i copy above top and make class and paste but no o/p

  • It would be nice to have a tutorial that explains how to use your code and get a drop down that is bindable to Database Tables(Table 1: OptionGroup, Table 2: Option Name).

  • This is bad example!

  • Hi. Great posting. Where does the Bold and Italics come from when the optgroup != null items are rendered? i.e. what if you wanted only italics. How would you do that?

    Thank you.

Comments have been disabled for this content.