2009-08-21 3 views
61

Сотрудник показал мне это:Атрибуты ListItems в DropDownList теряются при обратной передаче?

У него есть DropDownList и кнопка на веб-странице. Вот код позади:

protected void Page_Load(object sender, EventArgs e) 
    { 
     if (!IsPostBack) 
     { 
      ListItem item = new ListItem("1"); 
      item.Attributes.Add("title", "A"); 

      ListItem item2 = new ListItem("2"); 
      item2.Attributes.Add("title", "B"); 

      DropDownList1.Items.AddRange(new[] {item, item2}); 
      string s = DropDownList1.Items[0].Attributes["title"]; 
     } 
    } 

    protected void Button1_Click(object sender, EventArgs e) 
    { 
     DropDownList1.Visible = !DropDownList1.Visible; 
    } 

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

+0

Pobably должен также показать ваш .aspx-код. – madcolor

ответ

66

У меня была такая же проблема, и я хотел внести свой вклад this ресурс, где автор создал унаследованного потребителя ListItem для сохранения атрибутов ViewState. Надеюсь, это спасет кого-то, когда я потрачу впустую, пока не наткнулся на него.

protected override object SaveViewState() 
{ 
    // create object array for Item count + 1 
    object[] allStates = new object[this.Items.Count + 1]; 

    // the +1 is to hold the base info 
    object baseState = base.SaveViewState(); 
    allStates[0] = baseState; 

    Int32 i = 1; 
    // now loop through and save each Style attribute for the List 
    foreach (ListItem li in this.Items) 
    { 
     Int32 j = 0; 
     string[][] attributes = new string[li.Attributes.Count][]; 
     foreach (string attribute in li.Attributes.Keys) 
     { 
      attributes[j++] = new string[] {attribute, li.Attributes[attribute]}; 
     } 
     allStates[i++] = attributes; 
    } 
    return allStates; 
} 

protected override void LoadViewState(object savedState) 
{ 
    if (savedState != null) 
    { 
     object[] myState = (object[])savedState; 

     // restore base first 
     if (myState[0] != null) 
      base.LoadViewState(myState[0]); 

     Int32 i = 1; 
     foreach (ListItem li in this.Items) 
     { 
      // loop through and restore each style attribute 
      foreach (string[] attribute in (string[][])myState[i++]) 
      { 
       li.Attributes[attribute[0]] = attribute[1]; 
      } 
     } 
    } 
} 
+8

Как я могу использовать этот код? –

+0

Почему так загадочно? если это предназначено для наследования из ListItem, тогда это не работает. – Ted

+2

. Вы должны наследовать класс из DropDownList, а затем использовать его, как пояснял ниже gleapman;) –

8

Если вы хотите загрузить список только при первой загрузке страницы, вам необходимо включить ViewState, чтобы элемент управления мог сериализовать его состояние и перезагрузить его, когда страница заходит назад.

Есть несколько мест, где ViewState можно включить - проверить <pages/> узел в web.config, а также в <%@ page %> директивы в верхней части самого ASPX файла для EnableViewState собственности. Для работы ViewState этот параметр должен быть true.

Если вы не хотите использовать ViewState, просто удалите if (!IsPostBack) { ... } со всего кода, который добавляет ListItems, и элементы будут воссозданы при каждой обратной передаче.

Редактировать: Извиняюсь - я неверно сформулировал ваш вопрос. Вы правы, что атрибуты не выдерживают обратной передачи, поскольку они не сериализованы в ViewState. Вы должны повторно добавлять эти атрибуты при каждой обратной передаче.

+0

Не знаю, почему, но он не работает с viewstate! – TheVillageIdiot

31

Спасибо, Ларами. Только то, что я искал. Он отлично сохраняет атрибуты.

Чтобы расширить, ниже представлен файл класса, созданный с использованием кода Laramie для создания выпадающего списка в VS2008. Создайте класс в папке App_Code. После создания класса, используйте эту строку на странице ASPX, чтобы зарегистрировать его:

<%@ Register TagPrefix="aspNewControls" Namespace="NewControls"%> 

Вы можете поставить контроль над вашей веб-форму с этим

<aspNewControls:NewDropDownList ID="ddlWhatever" runat="server"> 
               </aspNewControls:NewDropDownList> 

Ok, вот класс ...

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Security.Permissions; 
using System.Linq; 
using System.Text; 
using System.Web; 
using System.Web.UI; 
using System.Web.UI.WebControls; 

namespace NewControls 
{ 
    [DefaultProperty("Text")] 
    [ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")] 
    public class NewDropDownList : DropDownList 
    { 
    [Bindable(true)] 
    [Category("Appearance")] 
    [DefaultValue("")] 
    [Localizable(true)] 

    protected override object SaveViewState() 
    { 
     // create object array for Item count + 1 
     object[] allStates = new object[this.Items.Count + 1]; 

     // the +1 is to hold the base info 
     object baseState = base.SaveViewState(); 
     allStates[0] = baseState; 

     Int32 i = 1; 
     // now loop through and save each Style attribute for the List 
     foreach (ListItem li in this.Items) 
     { 
      Int32 j = 0; 
      string[][] attributes = new string[li.Attributes.Count][]; 
      foreach (string attribute in li.Attributes.Keys) 
      { 
       attributes[j++] = new string[] { attribute, li.Attributes[attribute] }; 
      } 
      allStates[i++] = attributes; 
     } 
     return allStates; 
    } 

    protected override void LoadViewState(object savedState) 
    { 
     if (savedState != null) 
     { 
      object[] myState = (object[])savedState; 

      // restore base first 
      if (myState[0] != null) 
       base.LoadViewState(myState[0]); 

      Int32 i = 1; 
      foreach (ListItem li in this.Items) 
      { 
       // loop through and restore each style attribute 
       foreach (string[] attribute in (string[][])myState[i++]) 
       { 
        li.Attributes[attribute[0]] = attribute[1]; 
       } 
      } 
     } 
    } 
    } 
} 
+0

Возможно, вам нужно будет добавить Assembly в Tag-Tag, даже если он находится в той же сборке ... Я думаю, что это зависит от того, является ли это проектом веб-приложения или веб-сайтом. Это было бы для веб-приложения с именем «MyWebApplication», а затем читать: <% @ Register Assembly = «MyWebApplication» TagPrefix = «aspNewControls» Namespace = «NewControls»%> –

+1

Я пробовал ваше решение, но если я использую ваш унаследованный элемент управления , это как-то недоступно в коде. Я имею в виду, если я попробую 'ddlWhatever.Items', он выдает исключение null из' ddlWhatever' Любая идея, почему? – david

+0

@ david: Это не работает, если вы создаете 'UserControl' и пытаетесь наследовать' DropDownList'. –

6

Одно простое решение. Вызовите функцию загрузки вниз по событию клика, где вы запрашиваете пост.

+0

. Не забудьте сохранить dropdown.SelectedIndex, прежде чем перезагружать выпадающее меню, чтобы потом восстановить выбор пользователя. – Patrick

8

Простым решением является добавление атрибутов всплывающей подсказки в событие pre-render выпадающего списка. Любые изменения состояния должны быть выполнены на мероприятии pre-render.

Пример кода:

protected void drpBrand_PreRender(object sender, EventArgs e) 
     { 
      foreach (ListItem _listItem in drpBrand.Items) 
      { 
       _listItem.Attributes.Add("title", _listItem.Text); 
      } 
      drpBrand.Attributes.Add("onmouseover", "this.title=this.options[this.selectedIndex].title"); 
     } 
0
//In the same block where the ddl is loaded (assuming the dataview is retrieved whether postback or not), search for the listitem and re-apply the attribute 
    if(IsPostBack) 
    foreach (DataRow dr in dvFacility.Table.Rows) 
{       
    //search the listitem 
    ListItem li = ddl_FacilityFilter.Items.FindByValue(dr["FACILITY_CD"].ToString()); 
    if (li!=null) 
{ 
    li.Attributes.Add("Title", dr["Facility_Description"].ToString());  
}     
} //end for each 
2

Типовые решения этой проблемы заключается в создании новых элементов управления, которые не вполне осуществимы в нормальных условиях.Существует простое, но тривиальное решение этой проблемы.

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

Шаги:

  1. сериализации атрибуты, используя код в ответ выше (https://stackoverflow.com/a/3099755/3624833)

  2. хранить его в пользовательский атрибут ListControl (DropDownList, checklistbox, что угодно).

  3. В ответ на сообщение, верните пользовательский атрибут из ListControl и затем десериализуйте его как атрибуты.

Вот код, который я использовал для (де) сериализации атрибуты (Что мне нужно сделать, чтобы отслеживать, какие элементы списка были первоначально визуализируется как выбран при извлечении из внутреннего интерфейса, а затем сохранить или удалить строки в соответствии с изменениями, сделанных пользователем на UI):

string[] selections = new string[Users.Items.Count]; 
for(int i = 0; i < Users.Items.Count; i++) 
{ 
    selections[i] = string.Format("{0};{1}", Users.Items[i].Value, Users.Items[i].Selected); 
} 
Users.Attributes["data-item-previous-states"] = string.Join("|", selections); 

(выше, «пользователь» является CheckboxList контролем).

На пост обратно (в моем случае кнопку Отправить Нажмите событий), я использую следующий код, чтобы получить то же самое и хранить их в словарь для последующей обработки:

Dictionary<Guid, bool> previousStates = new Dictionary<Guid, bool>(); 
string[] state = Users.Attributes["data-item-previous-states"].Split(new char[] {'|'}, StringSplitOptions.RemoveEmptyEntries); 
foreach(string obj in state) 
{ 
    string[] kv = obj.Split(new char[] { ';' }, StringSplitOptions.None); 
    previousStates.Add(kv[0], kv[1]); 
} 

(PS: У меня есть библиотечные функции, которые выполняют обработку ошибок и преобразование данных, опуская их здесь для краткости).

0

Простое решение без ViewState, создавая новый элемент управления сервера или SMTH комплекс:

Создание:

public void AddItemList(DropDownList list, string text, string value, string group = null, string type = null) 
{ 
    var item = new ListItem(text, value); 

    if (!string.IsNullOrEmpty(group)) 
    { 
     if (string.IsNullOrEmpty(type)) type = "group"; 
     item.Attributes["data-" + type] = group; 
    } 

    list.Items.Add(item); 
} 

Обновление:

public void ChangeItemList(DropDownList list, string eq, string group = null, string type = null) 
{ 
    var listItem = list.Items.Cast<ListItem>().First(item => item.Value == eq); 

    if (!string.IsNullOrEmpty(group)) 
    { 
     if (string.IsNullOrEmpty(type)) type = "group"; 
     listItem.Attributes["data-" + type] = group;  
    } 
} 

Пример:

protected void Page_Load(object sender, EventArgs e) 
{ 
    if (!Page.IsPostBack) 
    { 
     using (var context = new WOContext()) 
     { 
      context.Report_Types.ToList().ForEach(types => AddItemList(DropDownList1, types.Name, types.ID.ToString(), types.ReportBaseTypes.Name)); 
      DropDownList1.DataBind(); 
     } 
    } 
    else 
    { 
     using (var context = new WOContext()) 
     { 
      context.Report_Types.ToList().ForEach(types => ChangeItemList(DropDownList1, types.ID.ToString(), types.ReportBaseTypes.Name)); 
     } 
    } 
} 
+0

С помощью этого решения вы выполняете запросы к базе данных на каждой обратной стороне. Лучше использовать ViewState. – nZeus

1

Вот код VB.Net решения, предложенного Laramie и уточненный gleapman.

Обновление: Код, который я разместил ниже, фактически предназначен для элемента управления ListBox. Просто измените наследование на DropDownList и переименуйте класс.

Imports System.Collections.Generic 
Imports System.ComponentModel 
Imports System.Security.Permissions 
Imports System.Linq 
Imports System.Text 
Imports System.Web 
Imports System.Web.UI 
Imports System.Web.UI.WebControls 

Namespace CustomControls 

<DefaultProperty("Text")> _ 
<ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")> 
Public Class PersistentListBox 
    Inherits ListBox 

    <Bindable(True)> _ 
    <Category("Appearance")> _ 
    <DefaultValue("")> _ 
    <Localizable(True)> _ 
    Protected Overrides Function SaveViewState() As Object 
     ' Create object array for Item count + 1 
     Dim allStates As Object() = New Object(Me.Items.Count + 1) {} 

     ' The +1 is to hold the base info 
     Dim baseState As Object = MyBase.SaveViewState() 
     allStates(0) = baseState 

     Dim i As Int32 = 1 
     ' Now loop through and save each attribute for the List 
     For Each li As ListItem In Me.Items 
      Dim j As Int32 = 0 
      Dim attributes As String()() = New String(li.Attributes.Count - 1)() {} 
      For Each attribute As String In li.Attributes.Keys 
       attributes(j) = New String() {attribute, li.Attributes(attribute)} 
       j += 1 
      Next 
      allStates(i) = attributes 
      i += 1 
     Next 


     Return allStates 
    End Function 

    Protected Overrides Sub LoadViewState(savedState As Object) 
     If savedState IsNot Nothing Then 
      Dim myState As Object() = DirectCast(savedState, Object()) 

      ' Restore base first 
      If myState(0) IsNot Nothing Then 
       MyBase.LoadViewState(myState(0)) 
      End If 

      Dim i As Int32 = 1 
      For Each li As ListItem In Me.Items 
       ' Loop through and restore each attribute 
       ' NOTE: Ignore the first item as that is the base state and is represented by a Triplet struct 
       For Each attribute As String() In DirectCast(myState(i), String()()) 
        li.Attributes(attribute(0)) = attribute(1) 
        i += 1 
       Next 
      Next 
     End If 
    End Sub 
End Class 
End Namespace 
+0

использовал это успешно, но нужно было сделать одно исправление ошибок, чтобы заставить его работать правильно. В двух вложенных циклах внутри LoadViewState я переместил i с шагом в первый цикл, но перед вторым циклом, и я также инициализировал i до 0 перед первым циклом – rdans

+0

@MPaul. Как обычно считается, что здесь нечестно изменять чужой код, вы хотели бы сделать исправление, которое указал rdans, или вы хотите, чтобы я сделал это для вас? –

0

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

protected void Page_Load(object sender, EventArgs e) 
{ 
    if (!IsPostBack) 
    { 
     string[] elems;//Array with values to add to the list 
     for (int q = 0; q < elems.Length; q++) 
     { 
      ListItem li = new ListItem() { Value = "text", Text = "text" }; 
      li.Attributes["data-image"] = elems[q]; 
      myList.Items.Add(li); 
      HttpContext.Current.Session.Add("attr" + q, elems[q]); 
     } 
    } 
    else 
    { 
     for (int o = 0; o < webmenu.Items.Count; o++) 
     { 
      myList.Items[o].Attributes["data-image"] = HttpContext.Current.Session["attr" + o].ToString(); 
     } 
    } 
} 

Когда Страница загружается с первого раза, когда список заполняется, и я добавляю атрибут изображения, который теряется после обратной передачи: (поэтому в момент добавления элементов с его атрибутами я создаю одну переменную сеанса «attr» плюс номер элемента, взятого из «для» цикла (он будет подобен attr0, attr1, attr2 и т. д.)), и в них я сохраняю значение атрибута (путь к изображению в моем случае), когда происходит обратная передача (внутри «else») Я просто перебираю список и добавляю атрибут, взятый из переменной Session, используя «int "цикла" для ", который совпадает с тем, когда была загружена страница (это потому, что на этой странице я не добавляю элементы в список, просто выбрав так, чтобы они всегда имели один и тот же индекс), и атрибуты снова установлены, я надеюсь, что это поможет кому-то в будущем, привет!

1

@Sujay Вы можете добавить текст с разделителем в виде двоеточия в атрибут значения раскрывающегося списка (например, стиль csv) и использовать String.Split (';'), чтобы получить 2 "значения" из одного значения, так как обходной путь, чтобы избавиться от необходимости создавать новый пользовательский контроль. Особенно, если у вас есть только несколько дополнительных атрибутов, и если он не слишком длинный. Вы также можете использовать значение JSON в атрибуте значения раскрывающегося списка, а затем проанализировать все, что вам нужно.

Смежные вопросы