I made one sometime ago and it worked fine at the time. I'll post the relevant pieces so you can build yours (mine had also a readonly mode that rendered a Label instead but you probably don't need it).
[DefaultProperty("Text"),
ValidationProperty("SelectedItem"),
ToolboxData("<{0}:EqualDropDownList runat=server></{0}:EqualDropDownList>"),
ToolboxBitmap(typeof(EqualDropDownList), "dropdown.BMP")]
public class EqualDropDownList:DropDownList
{
/// The field in the datasource which provides values for groups
/// </summary>
[DefaultValue(""), Category("Data")]
public virtual string DataGroupField
{
get
{
object obj = this.ViewState["DataGroupField"];
if (obj != null)
{
return (string) obj;
}
return string.Empty;
}
set
{
this.ViewState["DataGroupField"] = value;
}
}
/// <summary>
/// Render this control to the output parameter specified.
/// Based on the source code of the original DropDownList method
/// </summary>
/// <param name="writer"> The HTML writer to write out to </param>
protected override void RenderContents(HtmlTextWriter writer)
{
ListItemCollection items = this.Items;
int itemCount = this.Items.Count;
string curGroup = String.Empty;
string itemGroup;
bool bSelected = false;
if (itemCount <= 0)
{
return;
}
for (int i = 0; i < itemCount; i++)
{
ListItem item = items[i];
itemGroup = item.Attributes["DataGroupField"];
if (itemGroup != null && itemGroup != curGroup)
{
if (curGroup != String.Empty)
{
writer.WriteEndTag("optgroup");
writer.WriteLine();
}
curGroup = itemGroup;
writer.WriteBeginTag("optgroup");
writer.WriteAttribute("label", curGroup, true);
writer.Write('>');
writer.WriteLine();
}
writer.WriteBeginTag("option");
if (item.Selected)
{
if (bSelected)
{
throw new HttpException("Cant_Multiselect_In_DropDownList");
}
bSelected = true;
writer.WriteAttribute("selected", "selected", false);
}
foreach (string key in item.Attributes.Keys)
{
writer.WriteAttribute(key, item.Attributes[key], false);
}
writer.WriteAttribute("value", item.Value, true);
writer.Write('>');
HttpUtility.HtmlEncode(item.Text, writer);
writer.WriteEndTag("option");
writer.WriteLine();
}
if (curGroup != String.Empty)
{
writer.WriteEndTag("optgroup");
writer.WriteLine();
}
}
/// <summary>
/// Perform data binding logic that is associated with the control
/// </summary>
/// <param name="e">An EventArgs object that contains the event data</param>
protected override void OnDataBinding(EventArgs e)
{
// Call base method to bind data
base.OnDataBinding(e);
if (this.DataGroupField == String.Empty)
{
return;
}
// For each Item add the attribute "DataGroupField" with value from the datasource
IEnumerable dataSource = Util.GetResolvedDataSource(this.DataSource, this.DataMember);
if (dataSource != null)
{
ListItemCollection items = this.Items;
int i = 0;
string groupField = this.DataGroupField;
foreach (object obj in dataSource)
{
string groupFieldValue = DataBinder.GetPropertyValue(obj, groupField, null);
ListItem item = items[i];
item.Attributes.Add("DataGroupField", groupFieldValue);
i++;
}
}
}
}
You'll need this class too:
public class Util
{
/// <summary>
/// This is copy of the internal ListControl method
/// </summary>
/// <param name="dataSource"></param>
/// <param name="dataMember"></param>
/// <returns></returns>
public static IEnumerable GetResolvedDataSource(object dataSource, string dataMember)
{
if (dataSource != null)
{
IListSource source1 = dataSource as IListSource;
if (source1 != null)
{
IList list1 = source1.GetList();
if (!source1.ContainsListCollection)
{
return list1;
}
if ((list1 != null) && (list1 is ITypedList))
{
ITypedList list2 = (ITypedList) list1;
PropertyDescriptorCollection collection1 = list2.GetItemProperties(new PropertyDescriptor[0]);
if ((collection1 == null) || (collection1.Count == 0))
{
throw new HttpException("ListSource_Without_DataMembers");
}
PropertyDescriptor descriptor1 = null;
if ((dataMember == null) || (dataMember.Length == 0))
{
descriptor1 = collection1[0];
}
else
{
descriptor1 = collection1.Find(dataMember, true);
}
if (descriptor1 != null)
{
object obj1 = list1[0];
object obj2 = descriptor1.GetValue(obj1);
if ((obj2 != null) && (obj2 is IEnumerable))
{
return (IEnumerable) obj2;
}
}
throw new HttpException("ListSource_Missing_DataMember");
}
}
if (dataSource is IEnumerable)
{
return (IEnumerable) dataSource;
}
}
return null;
}
}