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


70

一位同事向我展示了这一点:

他在网页上有一个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;
    }

在页面加载时,将显示项目的工具提示,但在第一次回发时,属性将丢失。为什么会这样,并且有任何解决方法?


可能还应该显示您的.aspx代码。
madcolor

Answers:


72

我遇到了同样的问题,想在作者创建一个继承的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];
            }
        }
    }
}

为什么如此神秘?如果要从ListItem继承,则它不起作用
Ted

2
您必须从DropDownList继承一个类,然后使用它,就像下面的gleapman解释的一样;)
Markus Szumovski 2014年

2
解决方案包括创建一个我不喜欢的新控件。有一种方法可以执行此操作而无需任何子类化。

38

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

为了进行扩展,下面是我使用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];
                }
            }
        }
    }
  }
}

可能是即使您在同一Assembly中,也必须将Assembly添加到Reference-Tag。我认为这取决于它是Web应用程序项目还是网站。对于名为“ MyWebApplication”的Web应用程序,这将是:<%@ Register Assembly =“ MyWebApplication” TagPrefix =“ aspNewControls” Namespace =“ NewControls”%>
Markus Szumovski 2014年

2
我尝试了您的解决方案,但是如果使用您的继承控件,则在后面的代码中无法以某种方式访问​​它。我的意思是,如果我尝试ddlWhatever.Items从“ddlWhatever任何想法”中抛出null异常,为什么?
大卫

@david:如果您创建UserControl并尝试继承,则此方法无效DropDownList
加百利GM

对ListBox来说,对我来说很棒。现在我可以使用自定义属性,如数据的数据正确呈现出通过jQuery插件像selectize我控制在回发
abney317

谢谢,这个答案解决了问题,但是是否有任何更新以获得更好的解决方案?
狮子座

14

一种简单的解决方案是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");
        }

8

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

可以在多个位置启用ViewState-检查<pages/>web.config中的节点以及<%@ page %>aspx文件本身顶部的指令中的EnableViewState属性。此设置需要trueViewState才能起作用。

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

编辑:很抱歉-我读错了你的问题。您是正确的,因为这些属性没有在ViewState中序列化,因此它们不会在回发后继续存在。您必须在每个回发中重新添加这些属性。


6

一种简单的解决方案-在请求回发的click事件上调用下拉加载功能。


在重新加载下拉列表之前,请不要忘记存储dropdown.SelectedIndex,以便以后可以恢复用户的选择。
Patrick

2

解决此问题的典型方法是创建在正常情况下不太可行的新控件。有一个简单但不重要的解决方案。

问题在于,ListItem失败者在回发时会失去其属性。但是,列表本身永远不会丢失任何自定义属性。因此,可以以一种简单而有效的方式利用这一点。

脚步:

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

  2. 将其存储到ListControl的自定义属性(下拉列表,清单框等)。

  3. 在回发时,从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控件)。

在回发时(在我的情况下为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:我有一个执行错误处理和数据转换的库函数,为简洁起见,这里省略了该库函数)。


2

这是Laramie提出并由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

1
成功使用了此功能,但需要修复一个错误才能使其正常工作。在LoadViewState中的两个嵌套循环中,我将i增量移到了第一个循环内,但在第二个循环前,并且在第一个循环之前,我也将i初始化为0
rdans

@MPaul在这里通常认为更改他人代码是不礼貌的,您是否要进行rdans指出的更正,或者您希望我为您这样做?
安德鲁·莫顿

1

@Sujay您可以将一个用分号分隔的文本添加到下拉列表的value属性中(例如csv样式),并使用String.Split(';')从一个值中获取2个“值”,作为一种解决方法无需创建新的用户控件。尤其是在您只有很少的额外属性且时间不长的情况下。您还可以在下拉列表的value属性中使用JSON值,然后从那里解析出所需的内容。


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  

0

没有ViewState的简单解决方案,创建新的服务器控件或复杂的其他:

创建:

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

使用此解决方案,您可以在每次回发时向数据库发出请求。最好使用ViewState。
nZeus

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

首次加载Page时,将填充列表,并添加一个Image属性,该属性在回发后会丢失:(因此,在添加具有其属性的元素时,我创建了一个Session变量“ attr”以及所采用元素的数量从“ for”周期(类似于attr0,attr1,attr2等)中,当回发发生时,在其中保存属性值(在我的情况下为图像的路径)(在“ else”)我只是循环列表,并使用“ for”循环的“ int”添加从Session变量获取的属性,该属性与加载页面时相同(这是因为在此页面中,我不添加元素(只需选择该列表即可使它们始终具有相同的索引),并再次设置属性,希望这对以后的问候有所帮助!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.