Атрибуты ListItems в выпадающем списке теряются при обратной передаче?
коллега показал мне это:
у него есть выпадающий список и кнопка на веб-странице. Вот код:
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;
}
при загрузке страницы отображаются подсказки элементов, но при первой обратной передаче атрибуты теряются. Почему это так, и есть ли какие-либо обходные пути?
11 ответов:
У меня была такая же проблема, и я хотел внести свой вклад этой ресурс, где автор создал унаследованный потребитель 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]; } } } }
Спасибо, Ларами. Именно то, что я искал. Он отлично сохраняет атрибуты.
чтобы развернуть, ниже приведен файл класса, который я создал с помощью кода Laramie для создания выпадающего списка в VS2008. Создайте класс в папке App_Code. После создания класса используйте эту строку на странице aspx для его регистрации:
<%@ Register TagPrefix="aspNewControls" Namespace="NewControls"%>
затем вы можете поместить элемент управления в свою веб-форму с помощью этого
<aspNewControls:NewDropDownList ID="ddlWhatever" runat="server"> </aspNewControls:NewDropDownList>
хорошо, вот класс...
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]; } } } } } }
простое решение-добавить атрибуты всплывающей подсказки в
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"); }
если вы хотите загрузить listitems только при первой загрузке страницы, вам нужно будет включить ViewState, чтобы элемент управления мог сериализовать свое состояние там и перезагрузить его, когда страница отправляет обратно.
есть несколько мест, где ViewState может быть включен-проверьте
<pages/>
узел в интернете.config, а также в<%@ page %>
директива в верхней части самого файла aspx дляEnableViewState
собственность. Этот параметр должен бытьtrue
для ViewState to работа.если вы не хотите использовать ViewState, просто удалите
if (!IsPostBack) { ... }
из всего кода, который добавляетListItems
и элементы будут воссозданы на каждой обратной передачи.Edit: я прошу прощения - я неправильно понял ваш вопрос. Вы правы, что атрибуты не выжить обратной передачи, поскольку они не сериализованы в ViewState. Вы должны повторно добавить эти атрибуты при каждой обратной передаче.
одно простое решение-вызовите выпадающую функцию загрузки на событие click, где вы запрашиваете post back.
типичные решения этой проблемы включают в себя создание новых элементов управления, которые не совсем выполнимы в обычных условиях. Существует простое, но тривиальное решение этой проблемы.
проблема в том, что
ListItem
теряет свои атрибуты при обратной передаче. Однако сам список никогда не теряет никаких пользовательских атрибутов. Можно воспользоваться этим простым, но эффективным образом.действия:
сериализация атрибутов с помощью код в ответе выше (https://stackoverflow.com/a/3099755/3624833)
сохраните его в пользовательский атрибут ListControl (dropdownlist, checklistbox, whatever).
на пост обратно, считывать пользовательский атрибут элементе управления listcontrol и затем десериализовать его обратно в качестве атрибутов.
вот код, который я использовал для (de)сериализации атрибутов (что мне нужно было сделать, чтобы отслеживать, какие элементы из списка были первоначально представлены как выбранные при извлечении из бэкэнда, а затем сохранить или удалить строки в соответствии с изменениями, внесенными Пользователем в пользовательском интерфейсе):
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
контроль).на post back (в моем случае событие нажатия кнопки отправки) я использую приведенный ниже код для получения того же и сохранения их в словаре для последующей обработки:
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: у меня есть библиотека функций, которые выполняют обработку ошибок и преобразование данных, опуская то же самое здесь для краткости).
вот VB.Net код решения, предложенного Ларами и уточненного глипманом.
обновление: код, который я опубликовал ниже, на самом деле для элемента управления 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
@Sujay Вы можете добавить разделенный точкой с запятой текст в атрибут значения раскрывающегося списка (например, стиль csv) и использовать строку.Split (';'), чтобы получить 2 "значения" из одного значения, как обходной путь, чтобы уйти от необходимости создавать новый пользовательский элемент управления. Особенно если у вас есть только несколько дополнительных атрибутов, и если это не слишком долго. Вы также можете использовать значение JSON в атрибуте value выпадающего списка, а затем проанализировать все, что вам нужно оттуда.
//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
простое решение без 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)); } } }
мне удалось добиться этого с помощью переменных сеанса, в моем случае мой список не будет содержать много элементов, поэтому он работает довольно хорошо, вот как я это сделал:
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" плюс номер элемента, взятого из цикла " for " (это будет похоже на attr0, attr1, attr2, так далее...) и в них я сохраняю значение атрибута (путь к изображению в моем случае), когда происходит обратная передача (внутри "else") я просто зацикливаю список и добавляю атрибут, взятый из переменной сеанса, используя "int" цикла "for", который такой же, как и при загрузке страницы (это потому, что на этой странице Я не добавляю элементы в список, просто выбирая, чтобы они всегда имели один и тот же индекс), и атрибуты устанавливаются снова, я надеюсь, что это поможет кому-то в будущем, привет!