Jeff and .NET

The .NET musings of Jeff Putz

Sponsors

News

My Sites

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.

 

Posted: Dec 27 2006, 01:23 PM by Jeff | with 15 comment(s)
Filed under:

Comments

todd brooks said:

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

Great job, btw.

# December 27, 2006 4:18 PM

Gustavo said:

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

Thanks,

Gustavo

# April 15, 2007 6:41 AM

Jeff said:

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

# April 15, 2007 10:05 AM

Atakmim said:

10x man, great job !

If you want make custom control and share it with the world ;)

# May 10, 2007 9:49 AM

Pran said:

Great job!!!!!

Thanks a lot

:)

# December 6, 2007 10:00 AM

3f blog » Blog Archive » DropDownList y OptionGroups said:

Pingback from  3f blog  &raquo; Blog Archive   &raquo; DropDownList y OptionGroups

# February 26, 2008 1:21 PM

Ben Sloan said:

This was a great starting point. The problem is that on a postback, the groups are lost because the fact it is an optgroup is not then picked up and re-rendered out.

The answer is not to use attributes as you suggest, but instead to set the "value" of the listitem which is to be the group header. Set it to something very unlikely to be used again like "$$GROUPHEADER$$GROUPHEADER$$"

Your test against the attribute (to see whether it is null) then becomes: "If item.Value = "$$GROUPHEADER$$GROUPHEADER$$" Then"

Next is to make it a bit easier for the programmer to add groups without having to remember this value string. To do this, I created a class called "OptionGroup" (I'm sure you can work out the contents of it) and then added a public sub on the control you wrote:

       Public Sub AddOptionGroup(ByVal og As OptionGroup)

           Dim optGrpTitle As New ListItem(og.GroupName, "$$GROUPHEADER$$GROUPHEADER$$")

           Me.Items.Add(optGrpTitle)

           For Each li As ListItem In og.Items

               Me.Items.Add(New ListItem(li.Text, li.Value))

           Next

       End Sub

To then add groups in an .aspx page:

   Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

       If Not IsPostBack Then

           Dim G1Items As New ListItemCollection

           G1Items.Add(New ListItem("G1V1", "G1V1"))

           G1Items.Add(New ListItem("G1V2", "G1V2"))

           G1Items.Add(New ListItem("G1V3", "G1V3"))

           Dim G2Items As New ListItemCollection

           G2Items.Add(New ListItem("G2V1", "G2V1"))

           G2Items.Add(New ListItem("G2V2", "G2V2"))

           G2Items.Add(New ListItem("G2V3", "G2V3"))

           Dim og1 As New OptionGroup("Group1", G1Items)

           Dim og2 As New OptionGroup("Group2", G2Items)

           Me.ddlTest.AddOptionGroup(og1)

           Me.ddlTest.AddOptionGroup(og2)

       End If

   End Sub

Except for that the code remains the same, and works perfectly across postbacks.

This is because the value property of the items does persist in viewstate, I suppose.

Anyway an excellent article, thanks.

Ben.

# February 29, 2008 8:33 AM

ra00l said:

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

www.codeplex.com/SharpPieces

# May 21, 2008 8:45 AM

Muhammad Dzaki said:

Hi Jeff,

This is the solution that exactly I need. Thanks to you.

Thank you to Ben Sloan as well, for fixing the "PostBack" problems.

This is a great article.

Regards,

Dzaki.

# June 5, 2008 3:50 AM

JItendra said:

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

# September 25, 2008 6:23 AM

Abhishek Udiya,Diaspark Inc Indore. said:

By CodeProject url:

www.codeproject.com/.../DropDownListOptionGroup.aspx

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 < this.Items.Count; i++)

{

if (this.Items[i].Attributes.Count > 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);

}

# February 17, 2009 7:49 AM

Himanshu said:

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

# July 8, 2009 10:08 AM

sukumarraju said:

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

# July 29, 2009 9:53 AM

Alex said:

This is bad example!

# August 14, 2009 1:18 PM

Scott said:

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.

# December 8, 2011 12:14 PM
Leave a Comment

(required) 

(required) 

(optional)

(required)