DropDownList 中的 ListItems 属性在回发时丢失?
-
19-09-2019 - |
题
一位同事向我展示了这个:
他在网页上有一个 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;
}
在页面加载时,会显示项目的工具提示,但在第一次回发时,属性会丢失。为什么会出现这种情况,有什么解决方法吗?
解决方案
我遇到了同样的问题并想做出贡献 这 作者在其中创建了一个继承的 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"%>
您可以然后将控制在你的网页表单与此
<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/>
节点在web.config并在<%@ page %>
指令在aspx文件本身的EnableViewState
财产的顶部。此设置将需要true
为ViewState的工作。
如果你不想使用ViewState的,只是从周围添加if (!IsPostBack) { ... }
代码删除ListItems
和项目将在每次回发重新创建。
编辑:我道歉 - 我误解你的问题。你是正确的的属性的,因为它们不是在ViewState中连载就没有生存的回传。在每次回发,必须重新添加这些属性。
一个简单的解决方案 - 打电话给你的下拉你在哪里的POST请求回click事件加载功能。
此问题的典型解决方案涉及创建在正常情况下不太可行的新控件。对于这个问题有一个简单但微不足道的解决方案。
问题是 ListItem
在回发时丢失其属性。但是,列表本身永远不会丢失任何自定义属性。因此,人们可以以一种简单而有效的方式利用这一点。
脚步:
使用上面答案中的代码序列化您的属性(https://stackoverflow.com/a/3099755/3624833)
将其存储到 ListControl 的自定义属性(下拉列表、复选框等)。
回发时,从 ListControl 读回自定义属性,然后将其反序列化为属性。
这是我用来(反)序列化属性的代码(我需要做的是跟踪从后端检索时最初呈现为选择的列表中的哪些项目,然后根据所做的更改保存或删除行用户界面上的用户):
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]);
}
(附:我有一个执行错误处理和数据转换的库函数,为简洁起见,此处省略了相同的内容)。
下面是由拉勒米提出并由gleapman精制溶液的VB.Net代码。
<强>更新强> I中发布的代码实际上是列表框控件。只要改变继承为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“值”出了一个价值,作为一种解决方法,以逃脱不不必创建重新用户控制。特别是如果你只有几个额外的属性,如果它是不会太长。你也可以使用一个JSON值到下拉的价值属性,然后分析出你从那里需要什么。
//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
简单的解决方案,而不视图状态,创建新的服务器控制或不服络合物:
创建:
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”加的数摘自“的”周期元素(它会像attr0,attR1位,attR2位,等...),并在其中的我属性的值保存(一路径图像在我的情况),回发时发生(在“其他”),我只是循环列表,并添加使用的“for”循环“整数”,也就是同样的加载页面时从Session变量所采取的属性(这是因为在这个页面我里面做不是元素添加到列表中只是选择让他们有始终不变的指数)和属性被重新设置,我希望这可以帮助别人的未来,问候!