Элемент управления раскрывающимся списком с <optgroup> для asp.net (веб-формы)?

Может ли кто-нибудь порекомендовать элемент управления раскрывающимся списком для asp.net (3.5), который может отображать группы параметров? Спасибо

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
65
0
50 574
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

Ответ принят как подходящий

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

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

'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>

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

Ash Machine 06.01.2009 03:41

Вместо этого вы можете унаследовать от элемента управления dropdownlist и адаптировать этот код, чтобы переопределить событие рендеринга для этого элемента управления. Но на самом деле этот код не должен мешать другим спискам на странице.

Joel Coehoorn 06.01.2009 08:26

Я добавил версию, которая исправит ошибку viewstate.

Glennular 18.11.2009 23:46

Я нашел это решение через поиск в Google и смог реализовать его в моем существующем веб-приложении за несколько минут. Блестяще;)

Pete 10.12.2009 23:22

@Ash Machine - Другой вариант, который сработал для меня, заключался в создании подкласса элемента управления System.Web.UI.WebControls.DropDownList с классом-оболочкой YourNamespace.DropDownListWithOptionGroups, ссылка на ваш подкласс в файле браузеров. Затем, когда вы хотите использовать специальный раскрывающийся список, вы ссылаетесь на DropDownListWithOptionGroups в своем файле ASPX вместо DropDownList.

JohnFx 13.04.2011 01:19

Я реализовал это, но получаю сообщение об ошибке проверки события на странице Любые с выпадающим списком после этого. У кого-нибудь еще есть эта проблема?

Mehdi Maujood 05.08.2012 22:27

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

Jon 21.09.2012 23:48

Проект Sharp Pieces на CodePlex снимает это (и несколько других) ограничений управления.

Спасибо, Джоэл! всем ... вот версия 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();
    }

}



Есть ли способ получить источник данных из результата sql, я пробовал следующее: ddlCountry.Attributes.Add ("OptionGroup", "Region"); Мне нужно было связать страны с регионом как optgroup

sudheshna 06.08.2011 11:38

Я использую отражатель, чтобы понять, почему не поддерживается. Вот почему. В методе рендеринга 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();
                }
            }
        }
    }

Итак, я создаю свой собственный раскрывающийся список Control с переопределением метода 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();
          }
        }
      }

    }
  }

@ cedric-boivin: Мне пришлось немного изменить код, чтобы он заработал. Кроме того, было бы лучше, если бы вы могли упомянуть, как будет выглядеть код страницы .aspx. Дайте мне знать, если вам это потребуется от меня.

iMatoria 03.10.2012 15:37

Как использовать в aspx.cs?

Chưa biết 27.07.2015 10:59

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

 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

Я использовал 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;"); 
});

У @mhu есть более общий ответ, основанный на этом. stackoverflow.com/a/21481634/295011

Michael 31.07.2019 00:32

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

        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);
        }
    }

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

Приведенный выше код отображает конечный тег для 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>

<% @ Register TagPrefix = "MyCompany" Namespace = "MyCompany.Framework.Web.CustomControls" Assembly = "MyCompany.Framework.Web"%>, чтобы зарегистрировать пространство имен, в котором находится класс.

Andrew 17.03.2011 14:34

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

Более общий подход к решению на основе 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 + '">');
});

Я сделал это с помощью внешнего повторителя для групп select и optgroups и внутреннего повторителя для элементов в этой группе:

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

Я расширил mkl ответ, чтобы создать элемент управления DropDownList, к которому вы можете привязать DataBind. Вот результат (который может быть улучшен):

public class UcDropDownListWithOptGroup : DropDownList
{
    public const string OptionGroupTag = "optgroup";
    private const string OptionTag = "option";

    protected override void RenderContents(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["data-optgroup"] != null)
                    {
                        optgroupLabel = item.Attributes["data-optgroup"];

                        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);
        }
    }

    protected override void PerformDataBinding(IEnumerable dataSource)
    {
        base.PerformDataBinding(dataSource);

        if (!string.IsNullOrWhiteSpace(DataOptGroupField) && OptGroupTitles != null)
        {
            var currentItems = Items;
            var dataSourceItems = dataSource.Cast<object>().ToList();

            var staticItemsCount = Items.Count - dataSourceItems.Count;

            for (var i = staticItemsCount; i < Items.Count; i++)
            {
                var dataSourceItem = dataSourceItems[i - staticItemsCount];
                var optGroupValue = DataBinder.GetPropertyValue(dataSourceItem, DataOptGroupField);
                currentItems[i].Attributes.Add("data-optgroup", OptGroupTitles[optGroupValue]);
            }
        }
    }

    public Dictionary<object, string> OptGroupTitles { get; set; }
    public string DataOptGroupField { get; set; }
}

На заметку:

  • Вы можете установить свойство DataOptGroupField, подобное DataTextField и DataValueField. Он определяет, какое свойство элемента привязки данных следует учитывать для определения правильной группы optgroup.
  • Вы можете установить свойство OptGroupTitles, которое определяет, в какую метку optgroup следует преобразовать свойство, определенное в DataOptGroupField.

В качестве примера возьмите следующий код на своей странице или в пользовательском элементе управления:

<MyControls:UcDropDownListWithOptGroup runat = "server" DataSourceID = "dsX" DataTextField = "MyDataTextField" DataValueField = "MyDataValueField" DataOptGroupField = "IsActive" OptGroupTitles='<%# MyGroupTitles %>' />

Где свойство IsActive элемента привязки данных является логическим.

В коде программной части страницы или пользовательского элемента управления вы определяете следующее свойство:

public Dictionary<object, string> MyGroupTitles
{
    get
    {
        return new Dictionary<object, string>
        {
            { true, "Active" },
            { false, "Inactive" }
        };
    }
}

Другие вопросы по теме