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.