回发时丢失了 DropDownList 中的 ListItems 属性?

一个同事给我看了这个:

他有一个 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;
}

在页面加载时,将显示项的工具提示,但在第一次回发时,属性将丢失。为什么会这样,有什么变通的办法吗?

70394 次浏览

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

有几个地方可以启用 ViewState ——检查 web.config 中的 <pages/>节点和 aspx 文件本身顶部的 <%@ page %>指令中的 EnableViewState属性。要使 ViewState 工作,此设置需要为 true

如果不想使用 ViewState,只需从添加 ListItems的代码周围删除 if (!IsPostBack) { ... },这些项将在每次回发时重新创建。

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

我遇到了同样的问题,希望提供 这个资源,其中作者创建了一个继承的 ListItem Consumer 来将属性持久化到 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];
}
}
}
}

谢谢,拉勒米,正是我想要的。它保持了完美的属性。

为了扩展,下面是我用拉勒米的代码创建的一个类文件,用于在 vs2008中创建一个下拉列表。在 App _ Code 文件夹中创建类。创建这个类之后,在 aspx 页面上使用这一行来注册它:

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

然后您可以使用以下命令将控件放置在您的 webform 上

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

这个问题的典型解决方案包括创建在正常情况下不太可行的新控件。这个问题有一个简单而琐碎的解决方案。

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

步骤:

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

  2. 将它存储到 ListControl 的一个自定义属性(下拉列表、复选框等等)。

  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控件)。

在回发时(在我的例子中是一个 Submit 按钮 Click 事件) ,我使用下面的代码来检索相同的内容,并将它们存储到一个 Dictionary 中进行后期处理:

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: 我有一个库函数,它可以执行错误处理和数据转换,为了简洁起见,这里省略了相同的功能)。

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

下面是由拉勒米提出并由 gleapman 改进的解决方案的 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 = 0
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
i += 1
For Each attribute As String() In DirectCast(myState(i), String()())
li.Attributes(attribute(0)) = attribute(1)
Next
Next
End If
End Sub
End Class
End Namespace

我设法使用会话变量实现了这一点,在我的例子中,我的列表不会包含很多元素,所以它工作得很好,我是这样做的:

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

当第一次加载 Page 时,列表被填充,我添加了一个 Image 属性,这个属性在回发后丢失: (所以当我添加带有属性的元素时,我创建了一个 Session 变量“ attr”加上从“ for”循环中获取的元素的数量(类似于 attr0,attr1,attr2,等等)。.)在这些页面中,我保存了属性的值(在我的例子中是一个到图像的路径) ,当回发发生时(在“ else”中) ,我只是循环列表并使用“ for”循环中的“ int”添加从 Session 变量获取的属性,这与页面加载时是一样的(这是因为在这个页面中,我没有添加元素到列表中,只是选择,所以它们总是相同的索引) ,并且属性被再次设置,我希望这对将来的某人有所帮助,问候!

@ Sujay 您可以将分号分隔的文本添加到下拉列表的 value 属性中(如 csv 样式) ,并使用 String。拆分(’;’)从一个值中取出2个“值”,作为不必创建新用户控件的解决方案。特别是如果您只有很少的额外属性,并且不太长的话。您还可以在下拉列表的 value 属性中使用一个 JSON 值,然后从中解析出您需要的任何内容。