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.