DropDownList中的ListItems属性在回发中丢失?

一位同事告诉我:

他有一个DropDownList和一个网页上的button。 这是后面的代码:

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

在页面加载时,项目的工具提示正在显示,但在第一次回发时,属性会丢失。 为什么会出现这种情况,是否有任何解决方法?

我有同样的问题,并希望贡献作者创build一个inheritance的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中创build一个下拉列表创build的类文件。 在App_Code文件夹中创build类。 在创build这个类之后,在aspx页面上使用这一行来注册它:

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

然后你可以把控制放在你的webform上

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

好的,这是class级

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

如果您只想在页面的第一次加载时加载列表项,则需要启用ViewState,以便控件可以在其中序列化其状态,并在页面回发时重新加载它。

有几个地方可以启用ViewState – 查看web.config中的<pages/>节点以及EnableViewState属性的aspx文件顶部的<%@ page %>指令。 这个设置将需要是true的ViewState工作。

如果您不想使用ViewState,只需从添加ListItems的代码周围删除if (!IsPostBack) { ... } ,并且将在每个回发中重新创build项目。

编辑:我道歉 – 我误解了你的问题。 你是正确的,因为它们没有在ViewState中序列化, 属性不会生存回发。 您必须在每次回发中重新添加这些属性。

一个简单的解决scheme – 在请求回发的点击事件中调用您的下拉加载function。

简单的解决scheme是在下拉列表的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"); } 

这个问题的典型解决scheme涉及创build新的控制,在正常情况下不太可行。 这个问题有一个简单而平凡的解决scheme。

问题是ListItem在回发时丢失了它的属性。 但是,List本身从不会丢失任何自定义属性。 因此,可以简单而有效地利用这一点。

脚步:

  1. 使用上面的答案中的代码序列化您的属性( https://stackoverflow.com/a/3099755/3624833

  2. 将它存储到ListControl的自定义属性(dropdownlist,checklistbox,无论)。

  3. 在回发之后,从ListControl读回自定义属性,然后将其反序列化为属性。

这里是我用来(去)序列化属性的代码(我需要做的是跟踪列表中的哪些项目最初是从后端检索时select的,然后保存或删除行按照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控件)。

在回发(在我的情况下提交button单击事件),我使用下面的代码来检索相同的,并将其存储到字典进行后处理:

 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:我有一个库funcs执行error handling和数据转换,为简洁起见,在这里省略)。

这是Laramie提出的由gleapman提炼的解决scheme的VB.Net代码。

更新:我下面发布的代码实际上是为ListBox控件。 只要将inheritance更改为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风格),并使用String.Split(';')从一个值中获取2个“值”,作为解决方法逃脱而不必创build新的用户控制。 特别是如果你只有一些额外的属性,如果不是太长。 你也可以在下拉的值属性中使用JSON值,然后parsing出你需要的任何东西。

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

简单的解决scheme,无需ViewState,创build新的服务器控制或复杂的:

创build:

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

我设法实现使用会话variables,在我的情况下,我的列表不会包含很多元素,所以它工作得很好,这是我做到这一点:

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

当第一次加载页面时,列表被填充,我添加一个Image属性,在回发后丢失:(所以当我添加元素的属性时,我创build一个会话variables“attr”加上元素的数量从“for”循环(它将像attr0,attr1,attr2等),并在其中我保存属性的值(在我的情况下图像的path),当发生回发(在“别的“)我只是循环列表,并添加使用”for“循环的”int“从Sessionvariables中取得的属性是相同的,当页面被加载(这是因为在这个页面我不添加元素到刚才select的列表中,以便它们始终具有相同的索引),并重新设置属性,我希望这有助于未来的人,问候!