2008-09-24 4 views

ответ

46

Я использовал стандартный элемент управления в прошлом и просто добавил простой ControlAdapter, который мог бы переопределить поведение по умолчанию, чтобы он мог отображать < optgroup> s в определенных местах. Это отлично работает, даже если у вас есть элементы управления, которые не требуют особого поведения, потому что дополнительная функция не мешает.

Обратите внимание, что это было для определенной цели и написано на .Net 2.0, поэтому оно может и не устраивать вас, но оно должно по крайней мере дать вам отправную точку. Кроме того, вам нужно подключить его с помощью .browserfile в вашем проекте (см. Конец сообщения для примера).

'This codes makes the dropdownlist control recognize items with "--" 
'for the label or items with an OptionGroup attribute and render them 
'as <optgroup> instead of <option>. 
Public Class DropDownListAdapter 
    Inherits System.Web.UI.WebControls.Adapters.WebControlAdapter 

    Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter) 
     Dim list As DropDownList = Me.Control 
     Dim currentOptionGroup As String 
     Dim renderedOptionGroups As New Generic.List(Of String) 

     For Each item As ListItem In list.Items 
      Page.ClientScript.RegisterForEventValidation(list.UniqueID, item.Value) 
      If item.Attributes("OptionGroup") IsNot Nothing Then 
       'The item is part of an option group 
       currentOptionGroup = item.Attributes("OptionGroup") 
       If Not renderedOptionGroups.Contains(currentOptionGroup) Then 
        'the header was not written- do that first 
        'TODO: make this stack-based, so the same option group can be used more than once in longer select element (check the most-recent stack item instead of anything in the list) 
        If (renderedOptionGroups.Count > 0) Then 
         RenderOptionGroupEndTag(writer) 'need to close previous group 
        End If 
        RenderOptionGroupBeginTag(currentOptionGroup, writer) 
        renderedOptionGroups.Add(currentOptionGroup) 
       End If 
       RenderListItem(item, writer) 
      ElseIf item.Text = "--" Then 'simple separator 
       RenderOptionGroupBeginTag("--", writer) 
       RenderOptionGroupEndTag(writer) 
      Else 
       'default behavior: render the list item as normal 
       RenderListItem(item, writer) 
      End If 
     Next item 

     If renderedOptionGroups.Count > 0 Then 
      RenderOptionGroupEndTag(writer) 
     End If 
    End Sub 

    Private Sub RenderOptionGroupBeginTag(ByVal name As String, ByVal writer As HtmlTextWriter) 
     writer.WriteBeginTag("optgroup") 
     writer.WriteAttribute("label", name) 
     writer.Write(HtmlTextWriter.TagRightChar) 
     writer.WriteLine() 
    End Sub 

    Private Sub RenderOptionGroupEndTag(ByVal writer As HtmlTextWriter) 
     writer.WriteEndTag("optgroup") 
     writer.WriteLine() 
    End Sub 

    Private Sub RenderListItem(ByVal item As ListItem, ByVal writer As HtmlTextWriter) 
     writer.WriteBeginTag("option") 
     writer.WriteAttribute("value", item.Value, True) 
     If item.Selected Then 
      writer.WriteAttribute("selected", "selected", False) 
     End If 

     For Each key As String In item.Attributes.Keys 
      writer.WriteAttribute(key, item.Attributes(key)) 
     Next key 

     writer.Write(HtmlTextWriter.TagRightChar) 
     HttpUtility.HtmlEncode(item.Text, writer) 
     writer.WriteEndTag("option") 
     writer.WriteLine() 
    End Sub 
End Class 

Вот C# реализация одного и того же класса:

/* This codes makes the dropdownlist control recognize items with "--" 
* for the label or items with an OptionGroup attribute and render them 
* as <optgroup> instead of <option>. 
*/ 
public class DropDownListAdapter : WebControlAdapter 
{ 
    protected override void RenderContents(HtmlTextWriter writer) 
    { 
     //System.Web.HttpContext.Current.Response.Write("here"); 
     var list = (DropDownList)this.Control; 
     string currentOptionGroup; 
     var renderedOptionGroups = new List<string>(); 

     foreach (ListItem item in list.Items) 
     { 
      Page.ClientScript.RegisterForEventValidation(list.UniqueID, item.Value); 
      //Is the item part of an option group? 
      if (item.Attributes["OptionGroup"] != null) 
      { 
       currentOptionGroup = item.Attributes["OptionGroup"]; 
       //Was the option header already written, then just render the list item 
       if (renderedOptionGroups.Contains(currentOptionGroup)) 
        RenderListItem(item, writer); 
       //The header was not written,do that first 
       else 
       { 
        //Close previous group 
        if (renderedOptionGroups.Count > 0) 
         RenderOptionGroupEndTag(writer); 

        RenderOptionGroupBeginTag(currentOptionGroup, writer); 
        renderedOptionGroups.Add(currentOptionGroup); 
        RenderListItem(item, writer); 
       } 
      } 
      //Simple separator 
      else if (item.Text == "--") 
      { 
       RenderOptionGroupBeginTag("--", writer); 
       RenderOptionGroupEndTag(writer); 
      } 
      //Default behavior, render the list item as normal 
      else 
       RenderListItem(item, writer); 
     } 

     if (renderedOptionGroups.Count > 0) 
      RenderOptionGroupEndTag(writer); 
    } 

    private void RenderOptionGroupBeginTag(string name, HtmlTextWriter writer) 
    { 
     writer.WriteBeginTag("optgroup"); 
     writer.WriteAttribute("label", name); 
     writer.Write(HtmlTextWriter.TagRightChar); 
     writer.WriteLine(); 
    } 
    private void RenderOptionGroupEndTag(HtmlTextWriter writer) 
    { 
     writer.WriteEndTag("optgroup"); 
     writer.WriteLine(); 
    } 
    private void RenderListItem(ListItem item, HtmlTextWriter writer) 
    { 
     writer.WriteBeginTag("option"); 
     writer.WriteAttribute("value", item.Value, true); 
     if (item.Selected) 
      writer.WriteAttribute("selected", "selected", false); 

     foreach (string key in item.Attributes.Keys) 
      writer.WriteAttribute(key, item.Attributes[key]); 

     writer.Write(HtmlTextWriter.TagRightChar); 
     HttpUtility.HtmlEncode(item.Text, writer); 
     writer.WriteEndTag("option"); 
     writer.WriteLine(); 
    } 
} 

Мой файл-браузер был назван "App_Browsers \ BrowserFile.browser" и выглядел следующим образом:

<!-- 
    You can find existing browser definitions at 
    <windir>\Microsoft.NET\Framework\<ver>\CONFIG\Browsers 
--> 
<browsers> 
    <browser refID="Default"> 
     <controlAdapters> 
     <adapter controlType="System.Web.UI.WebControls.DropDownList" 
       adapterType="DropDownListAdapter" /> 
     </controlAdapters> 
    </browser> 
</browsers> 
+0

Идеально, спасибо Джоэл. – Nick 2008-09-24 21:50:44

+4

Это отлично подходит для переопределения всех выпадающих списков в веб-приложении. Но существует ли аналогичное решение для создания настраиваемого элемента управления, который может использоваться только там, где необходимы группы параметров? – 2009-01-06 00:41:00

+0

Вместо этого вы можете наследовать от элемента управления dropdownlist и адаптировать этот код, чтобы переопределить событие рендеринга для этого элемента управления. Но на самом деле этот код не должен мешать другим спискам на странице. – 2009-01-06 05:26:51

19

Спасибо, Джоэл! все ... здесь C# версия, если вы хотите:



using System; 
using System.Web.UI.WebControls.Adapters; 
using System.Web.UI; 
using System.Web.UI.WebControls; 
using System.Collections.Generic; 
using System.Web; 

//This codes makes the dropdownlist control recognize items with "--"' 
//for the label or items with an OptionGroup attribute and render them' 
//as instead of .' 
public class DropDownListAdapter : WebControlAdapter 
{ 

    protected override void RenderContents(HtmlTextWriter writer) 
    { 
     DropDownList list = Control as DropDownList; 
     string currentOptionGroup; 
     List renderedOptionGroups = new List(); 

     foreach(ListItem item in list.Items) 
     { 
      if (item.Attributes["OptionGroup"] != null) 
      { 
       //'The item is part of an option group' 
       currentOptionGroup = item.Attributes["OptionGroup"]; 
       //'the option header was already written, just render the list item' 
       if(renderedOptionGroups.Contains(currentOptionGroup)) 
        RenderListItem(item, writer); 
       else 
       { 
        //the header was not written- do that first' 
        if (renderedOptionGroups.Count > 0) 
         RenderOptionGroupEndTag(writer); //'need to close previous group' 
        RenderOptionGroupBeginTag(currentOptionGroup, writer); 
        renderedOptionGroups.Add(currentOptionGroup); 
        RenderListItem(item, writer); 
       } 
      } 
      else if (item.Text == "--") //simple separator 
      { 
       RenderOptionGroupBeginTag("--", writer); 
       RenderOptionGroupEndTag(writer); 
      } 
      else 
      { 
       //default behavior: render the list item as normal' 
       RenderListItem(item, writer); 
      } 
     } 

     if(renderedOptionGroups.Count > 0) 
      RenderOptionGroupEndTag(writer); 
    } 

    private void RenderOptionGroupBeginTag(string name, HtmlTextWriter writer) 
    { 
     writer.WriteBeginTag("optgroup"); 
     writer.WriteAttribute("label", name); 
     writer.Write(HtmlTextWriter.TagRightChar); 
     writer.WriteLine(); 
    } 

    private void RenderOptionGroupEndTag(HtmlTextWriter writer) 
    { 
     writer.WriteEndTag("optgroup"); 
     writer.WriteLine(); 
    } 

    private void RenderListItem(ListItem item, HtmlTextWriter writer) 
    { 
     writer.WriteBeginTag("option"); 
     writer.WriteAttribute("value", item.Value, true); 
     if (item.Selected) 
      writer.WriteAttribute("selected", "selected", false); 


     foreach (string key in item.Attributes.Keys) 
      writer.WriteAttribute(key, item.Attributes[key]); 

     writer.Write(HtmlTextWriter.TagRightChar); 
     HttpUtility.HtmlEncode(item.Text, writer); 
     writer.WriteEndTag("option"); 
     writer.WriteLine(); 
    } 

} 



6

Я использую отражатель, чтобы понять, почему он не поддерживается. Вот почему. В методе рендеринга ListControl нет условия для создания optgroup.

protected internal override void RenderContents(HtmlTextWriter writer) 
    { 
     ListItemCollection items = this.Items; 
     int count = items.Count; 
     if (count > 0) 
     { 
      bool flag = false; 
      for (int i = 0; i < count; i++) 
      { 
       ListItem item = items[i]; 
       if (item.Enabled) 
       { 
        writer.WriteBeginTag("option"); 
        if (item.Selected) 
        { 
         if (flag) 
         { 
          this.VerifyMultiSelect(); 
         } 
         flag = true; 
         writer.WriteAttribute("selected", "selected"); 
        } 
        writer.WriteAttribute("value", item.Value, true); 
        if (item.HasAttributes) 
        { 
         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(); 
       } 
      } 
     } 
    } 

Таким образом, я создаю свой собственный выпадающий элемент управления с переопределением метода RenderContents. Есть мой контроль. Работает нормально. Я использую точно такой же код Microsoft, просто добавлю небольшое условие для поддержки listItem с атрибутом optgroup для создания optgroup, а не с опцией.

Дайте мне кормить назад

public class DropDownListWithOptionGroup : DropDownList 
    { 
    public const string OptionGroupTag = "optgroup"; 
    private const string OptionTag = "option"; 
    protected override void RenderContents(System.Web.UI.HtmlTextWriter writer) 
    { 
     ListItemCollection items = this.Items; 
     int count = items.Count;  
     string tag; 
     string optgroupLabel; 
     if (count > 0) 
     { 
     bool flag = false; 
     for (int i = 0; i < count; i++) 
     { 
      tag = OptionTag; 
      optgroupLabel = null; 
      ListItem item = items[i]; 
      if (item.Enabled) 
      { 
      if (item.Attributes != null && item.Attributes.Count > 0 && item.Attributes[OptionGroupTag] != null) 
      { 
       tag = OptionGroupTag; 
       optgroupLabel = item.Attributes[OptionGroupTag]; 
      }   
      writer.WriteBeginTag(tag); 
      // NOTE(cboivin): Is optionGroup 
      if (!string.IsNullOrEmpty(optgroupLabel)) 
      { 
       writer.WriteAttribute("label", optgroupLabel); 
      } 
      else 
      { 
       if (item.Selected) 
       { 
       if (flag) 
       { 
        this.VerifyMultiSelect(); 
       } 
       flag = true; 
       writer.WriteAttribute("selected", "selected"); 
       } 
       writer.WriteAttribute("value", item.Value, true); 
       if (item.Attributes != null && 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(tag); 
      writer.WriteLine(); 
      } 
     } 
     } 

    } 
    } 
1

Как ответы выше, что перегрузить метод RenderContents сделать работу. Вы также должны помнить о том, чтобы изменить представление. Я столкнулся с проблемой при использовании не измененного состояния просмотра в UpdatePanels. Это части, взятые из Sharp Pieces Project.

Protected Overloads Overrides Sub RenderContents(ByVal writer As HtmlTextWriter) 
    Dim list As DropDownList = Me 

    Dim currentOptionGroup As String 
    Dim renderedOptionGroups As New List(Of String)() 

    For Each item As ListItem In list.Items 
     If item.Attributes("OptionGroup") Is Nothing Then 
      RenderListItem(item, writer) 
     Else 
      currentOptionGroup = item.Attributes("OptionGroup") 

      If renderedOptionGroups.Contains(currentOptionGroup) Then 
       RenderListItem(item, writer) 
      Else 
       If renderedOptionGroups.Count > 0 Then 
        RenderOptionGroupEndTag(writer) 
       End If 

       RenderOptionGroupBeginTag(currentOptionGroup, writer) 
       renderedOptionGroups.Add(currentOptionGroup) 

       RenderListItem(item, writer) 
      End If 
     End If 
    Next 

    If renderedOptionGroups.Count > 0 Then 
     RenderOptionGroupEndTag(writer) 
    End If 
End Sub 

Private Sub RenderOptionGroupBeginTag(ByVal name As String, ByVal writer As HtmlTextWriter) 
    writer.WriteBeginTag("optgroup") 
    writer.WriteAttribute("label", name) 
    writer.Write(HtmlTextWriter.TagRightChar) 
    writer.WriteLine() 
End Sub 

Private Sub RenderOptionGroupEndTag(ByVal writer As HtmlTextWriter) 
    writer.WriteEndTag("optgroup") 
    writer.WriteLine() 
End Sub 

Private Sub RenderListItem(ByVal item As ListItem, ByVal writer As HtmlTextWriter) 
    writer.WriteBeginTag("option") 
    writer.WriteAttribute("value", item.Value, True) 

    If item.Selected Then 
     writer.WriteAttribute("selected", "selected", False) 
    End If 

    For Each key As String In item.Attributes.Keys 
     writer.WriteAttribute(key, item.Attributes(key)) 
    Next 

    writer.Write(HtmlTextWriter.TagRightChar) 
    HttpUtility.HtmlEncode(item.Text, writer) 
    writer.WriteEndTag("option") 
    writer.WriteLine() 
End Sub 
Protected Overrides Function SaveViewState() As Object 
    ' Create an object array with one element for the CheckBoxList's 
    ' ViewState contents, and one element for each ListItem in skmCheckBoxList 
    Dim state(Me.Items.Count + 1 - 1) As Object 'stupid vb array 
    Dim baseState As Object = MyBase.SaveViewState() 

    state(0) = baseState 
    ' Now, see if we even need to save the view state 
    Dim itemHasAttributes As Boolean = False 

    For i As Integer = 0 To Me.Items.Count - 1 
     If Me.Items(i).Attributes.Count > 0 Then 
      itemHasAttributes = True 
      ' Create an array of the item's Attribute's keys and values 
      Dim attribKV(Me.Items(i).Attributes.Count * 2 - 1) As Object 'stupid vb array 
      Dim k As Integer = 0 
      For Each key As String In Me.Items(i).Attributes.Keys 
       attribKV(k) = key 
       k += 1 
       attribKV(k) = Me.Items(i).Attributes(key) 
       k += 1 
      Next 
      state(i + 1) = attribKV 
     End If 
    Next 
    ' return either baseState or state, depending on whether or not 
    ' any ListItems had attributes 
    If (itemHasAttributes) Then 
     Return state 
    Else 
     Return baseState 
    End If 
End Function 


Protected Overrides Sub LoadViewState(ByVal savedState As Object) 
    If savedState Is Nothing Then Return 
    ' see if savedState is an object or object array 
    If Not savedState.GetType.GetElementType() Is Nothing AndAlso savedState.GetType.GetElementType().Equals(GetType(Object)) Then 

     ' we have just the base state 
     MyBase.LoadViewState(savedState(0)) 
     'we have an array of items with attributes 
     Dim state() As Object = savedState 
     MyBase.LoadViewState(state(0)) '/ load the base state 
     For i As Integer = 1 To state.Length - 1 
      If Not state(i) Is Nothing Then 
       ' Load back in the attributes 
       Dim attribKV() As Object = state(i) 
       For k As Integer = 0 To attribKV.Length - 1 Step +2 
        Me.Items(i - 1).Attributes.Add(attribKV(k).ToString(), attribKV(k + 1).ToString()) 
       Next 
      End If 
     Next 
    Else 
     'load it normal 
     MyBase.LoadViewState(savedState) 
    End If 
End Sub 
22

Я использовал JQuery для достижения этой задачи. Я первый добавил новый атрибут для каждого ListItem из внутреннего интерфейса, а затем использовал этот атрибут в JQuery wrapAll() метода для создания групп ...

C#:

foreach (ListItem item in ((DropDownList)sender).Items) 
{ 
    if (System.Int32.Parse(item.Value) < 5) 
     item.Attributes.Add("classification", "LessThanFive"); 
    else 
     item.Attributes.Add("classification", "GreaterThanFive"); 
} 

JQuery:

$(document).ready(function() { 
    //Create groups for dropdown list 
    $("select.listsmall option[@classification='LessThanFive']") 
     .wrapAll("&lt;optgroup label='Less than five'&gt;"); 
    $("select.listsmall option[@classification='GreaterThanFive']") 
     .wrapAll("&lt;optgroup label='Greater than five'&gt;"); 
}); 
5

Основываясь на сообщениях выше, я создал aC# версию этого элемента управления с рабочим состоянием просмотра.

 public const string OptionGroupTag = "optgroup"; 
    private const string OptionTag = "option"; 
    protected override void RenderContents(System.Web.UI.HtmlTextWriter writer) 
    { 
     ListItemCollection items = this.Items; 
     int count = items.Count; 
     string tag; 
     string optgroupLabel; 
     if (count > 0) 
     { 
      bool flag = false; 
      for (int i = 0; i < count; i++) 
      { 
       tag = OptionTag; 
       optgroupLabel = null; 
       ListItem item = items[i]; 
       if (item.Enabled) 
       { 
        if (item.Attributes != null && item.Attributes.Count > 0 && item.Attributes[OptionGroupTag] != null) 
        { 
         tag = OptionGroupTag; 
         optgroupLabel = item.Attributes[OptionGroupTag]; 
        } 
        writer.WriteBeginTag(tag); 
        // NOTE(cboivin): Is optionGroup 
        if (!string.IsNullOrEmpty(optgroupLabel)) 
        { 
         writer.WriteAttribute("label", optgroupLabel); 
        } 
        else 
        { 
         if (item.Selected) 
         { 
          if (flag) 
          { 
           this.VerifyMultiSelect(); 
          } 
          flag = true; 
          writer.WriteAttribute("selected", "selected"); 
         } 
         writer.WriteAttribute("value", item.Value, true); 
         if (item.Attributes != null && 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(tag); 
        writer.WriteLine(); 
       } 
      } 
     } 
    } 

     protected override object SaveViewState() 
    { 
     object[] state = new object[this.Items.Count + 1]; 
     object baseState = base.SaveViewState(); 
     state[0] = baseState; 
     bool itemHasAttributes = false; 

     for (int i = 0; i < this.Items.Count; i++) 
     { 
      if (this.Items[i].Attributes.Count > 0) 
      { 
       itemHasAttributes = true; 
       object[] attributes = new object[this.Items[i].Attributes.Count * 2]; 
       int k = 0; 

       foreach (string key in this.Items[i].Attributes.Keys) 
       { 
        attributes[k] = key; 
        k++; 
        attributes[k] = this.Items[i].Attributes[key]; 
        k++; 
       } 
       state[i + 1] = attributes; 
      } 
     } 

     if (itemHasAttributes) 
      return state; 
     return baseState; 
    } 

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

     if (!(savedState.GetType().GetElementType() == null) && 
      (savedState.GetType().GetElementType().Equals(typeof(object)))) 
     { 
      object[] state = (object[])savedState; 
      base.LoadViewState(state[0]); 

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

Я надеюсь, что это помогает некоторым людям :-)

17

Приведенный выше код выход дает конечный тег для OPTGROUP перед какой-либо из вариантов, поэтому варианты не получают с отступом, как они должны в дополнение к разметка не соответствует представлениям группы. Вот моя слегка измененная версия кода Тома:

public class ExtendedDropDownList : System.Web.UI.WebControls.DropDownList 
{ 
    public const string OptionGroupTag = "optgroup"; 
    private const string OptionTag = "option"; 
    protected override void RenderContents(System.Web.UI.HtmlTextWriter writer) 
    { 
     ListItemCollection items = this.Items; 
     int count = items.Count; 
     string tag; 
     string optgroupLabel; 
     if (count > 0) 
     { 
      bool flag = false; 
      string prevOptGroup = null; 
      for (int i = 0; i < count; i++) 
      { 
       tag = OptionTag; 
       optgroupLabel = null; 
       ListItem item = items[i]; 
       if (item.Enabled) 
       { 
        if (item.Attributes != null && item.Attributes.Count > 0 && item.Attributes[OptionGroupTag] != null) 
        { 
         optgroupLabel = item.Attributes[OptionGroupTag]; 

         if (prevOptGroup != optgroupLabel) 
         { 
          if (prevOptGroup != null) 
          { 
           writer.WriteEndTag(OptionGroupTag); 
          } 
          writer.WriteBeginTag(OptionGroupTag); 
          if (!string.IsNullOrEmpty(optgroupLabel)) 
          { 
           writer.WriteAttribute("label", optgroupLabel); 
          } 
          writer.Write('>'); 
         } 
         item.Attributes.Remove(OptionGroupTag); 
         prevOptGroup = optgroupLabel; 
        } 
        else 
        { 
         if (prevOptGroup != null) 
         { 
          writer.WriteEndTag(OptionGroupTag); 
         } 
         prevOptGroup = null; 
        } 

        writer.WriteBeginTag(tag); 
        if (item.Selected) 
        { 
         if (flag) 
         { 
          this.VerifyMultiSelect(); 
         } 
         flag = true; 
         writer.WriteAttribute("selected", "selected"); 
        } 
        writer.WriteAttribute("value", item.Value, true); 
        if (item.Attributes != null && item.Attributes.Count > 0) 
        { 
         item.Attributes.Render(writer); 
        } 
        if (optgroupLabel != null) 
        { 
         item.Attributes.Add(OptionGroupTag, optgroupLabel); 
        } 
        if (this.Page != null) 
        { 
         this.Page.ClientScript.RegisterForEventValidation(this.UniqueID, item.Value); 
        } 

        writer.Write('>'); 
        HttpUtility.HtmlEncode(item.Text, writer); 
        writer.WriteEndTag(tag); 
        writer.WriteLine(); 
        if (i == count - 1) 
        { 
         if (prevOptGroup != null) 
         { 
          writer.WriteEndTag(OptionGroupTag); 
         } 
        } 
       } 
      } 
     } 
    } 

    protected override object SaveViewState() 
    { 
     object[] state = new object[this.Items.Count + 1]; 
     object baseState = base.SaveViewState(); 
     state[0] = baseState; 
     bool itemHasAttributes = false; 

     for (int i = 0; i < this.Items.Count; i++) 
     { 
      if (this.Items[i].Attributes.Count > 0) 
      { 
       itemHasAttributes = true; 
       object[] attributes = new object[this.Items[i].Attributes.Count * 2]; 
       int k = 0; 

       foreach (string key in this.Items[i].Attributes.Keys) 
       { 
        attributes[k] = key; 
        k++; 
        attributes[k] = this.Items[i].Attributes[key]; 
        k++; 
       } 
       state[i + 1] = attributes; 
      } 
     } 

     if (itemHasAttributes) 
      return state; 
     return baseState; 
    } 

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

     if (!(savedState.GetType().GetElementType() == null) && 
      (savedState.GetType().GetElementType().Equals(typeof(object)))) 
     { 
      object[] state = (object[])savedState; 
      base.LoadViewState(state[0]); 

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

Используйте это так:

  ListItem item1 = new ListItem("option1"); 
     item1.Attributes.Add("optgroup", "CatA"); 
     ListItem item2 = new ListItem("option2"); 
     item2.Attributes.Add("optgroup", "CatA"); 
     ListItem item3 = new ListItem("option3"); 
     item3.Attributes.Add("optgroup", "CatB"); 
     ListItem item4 = new ListItem("option4"); 
     item4.Attributes.Add("optgroup", "CatB"); 
     ListItem item5 = new ListItem("NoOptGroup"); 

     ddlTest.Items.Add(item1); 
     ddlTest.Items.Add(item2); 
     ddlTest.Items.Add(item3); 
     ddlTest.Items.Add(item4); 
     ddlTest.Items.Add(item5); 

и вот сгенерированный разметки (с отступом для удобства просмотра):

<select name="ddlTest" id="Select1"> 
    <optgroup label="CatA"> 
     <option selected="selected" value="option1">option1</option> 
     <option value="option2">option2</option> 
    </optgroup> 
    <optgroup label="CatB"> 
     <option value="option3">option3</option> 
     <option value="option4">option4</option> 
    </optgroup> 
    <option value="NoOptGroup">NoOptGroup</option> 
</select> 
1
// How to use: 
    // 1. Create items in a select element or asp:DropDownList control 
    // 2. Set value of an option or ListItem to "_group_", those will be converted to optgroups 
    // 3. On page onload call createOptGroups(domElement), for example like this: 
    // - var lst = document.getElementById('lst'); 
    // - createOptGroups(lst, "_group_"); 
    // 4. You can change groupMarkerValue to anything, I used "_group_" 
    function createOptGroups(lst, groupMarkerValue) { 
     // Get an array containing the options 
     var childNodes = []; 
     for (var i = 0; i < lst.options.length; i++) 
      childNodes.push(lst.options[i]); 

     // Get the selected element so we can preserve selection 
     var selectedIndex = lst.selectedIndex; 
     var selectedChild = childNodes[selectedIndex]; 
     var selectedValue = selectedChild.value; 

     // Remove all elements 
     while (lst.hasChildNodes()) 
      lst.removeChild(lst.childNodes[0]); 

     // Go through the array of options and convert some into groups 
     var group = null; 
     for (var i = 0; i < childNodes.length; i++) { 
      var node = childNodes[i]; 
      if (node.value == groupMarkerValue) { 
       group = document.createElement("optgroup"); 
       group.label = node.text; 
       group.value = groupMarkerValue; 
       lst.appendChild(group); 
       continue; 
      } 

      // Add to group or directly under list 
      (group == null ? lst : group).appendChild(node); 
     } 

     // Preserve selected, no support for multi-selection here, sorry 
     selectedChild.selected = true; 
    } 

Протестировано на Chrome 16, Firefox 9 и IE8.

3

Более общий подход к решению Irfan's JQuery на основе:

бэкэнд

private void _addSelectItem(DropDownList list, string title, string value, string group = null) { 
    ListItem item = new ListItem(title, value); 
    if (!String.IsNullOrEmpty(group)) 
    { 
     item.Attributes["data-category"] = group; 
    } 
    list.Items.Add(item); 
} 

... 
_addSelectItem(dropDown, "Option 1", "1"); 
_addSelectItem(dropDown, "Option 2", "2", "Category"); 
_addSelectItem(dropDown, "Option 3", "3", "Category"); 
... 

клиента

var groups = {}; 
$("select option[data-category]").each(function() { 
    groups[$.trim($(this).attr("data-category"))] = true; 
}); 
$.each(groups, function (c) { 
    $("select option[data-category='"+c+"']").wrapAll('<optgroup label="' + c + '">'); 
}); 
2

я сделал это, используя внешний ретранслятор для select и optgroups и внутренний повторитель для элементов w ithin этой группы:

<asp:Repeater ID="outerRepeater" runat="server"> 
    <HeaderTemplate> 
     <select id="<%= outerRepeater.ClientID %>"> 
    </HeaderTemplate> 
    <ItemTemplate> 
      <optgroup label="<%# Eval("GroupText") %>"> 
       <asp:Repeater runat="server" DataSource='<%# Eval("Items") %>'> 
        <ItemTemplate> 
         <option value="<%# Eval("Value") %>"><%# Eval("Text") %></option> 
        </ItemTemplate> 
       </asp:Repeater> 
      </optgroup> 
    </ItemTemplate> 
    <FooterTemplate> 
     </select> 
    </FooterTemplate> 
</asp:Repeater> 

Источник данных для outerRepeater является простой группировкой следующим образом:

var data = (from o in thingsToDisplay 
      group oby GetAlphaGrouping(o.Name) into g 
      orderby g.Key 
      select new 
      { 
       Alpha = g.Key, 
       Items = g 
      }); 

И чтобы получить альфа группирования характер:

private string GetAlphaGrouping(string value) 
{ 
    string firstChar = value.Substring(0, 1).ToUpper(); 
    int unused; 

    if (int.TryParse(firstChar, out unused)) 
     return "#"; 

    return firstChar.ToUpper(); 
} 

Это не идеальное решение но он работает. Решение исправить было бы больше не использовать WebForms, а мы вместо MVC. :)