【问题标题】:ListItems attributes in a DropDownList are lost on postback?DropDownList 中的 ListItems 属性在回发时丢失?
【发布时间】:2010-11-21 18:09:48
【问题描述】:

一位同事给我看了这个:

他在网页上有一个 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 代码。

标签: asp.net drop-down-menu postback behavior listitem


【解决方案1】:

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

【讨论】:

  • 为什么这么神秘?如果这是从 ListItem 继承的,那么它不起作用
  • 你必须从 DropDownList 继承一个类然后使用它,就像下面 gleapman 解释的那样;)
  • 解决方案涉及创建一个我不喜欢的新控件。有一种方法可以在没有任何子类的情况下做到这一点。
【解决方案2】:

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

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

【讨论】:

  • 可能您必须将程序集添加到引用标签中,即使它在同一个程序集中...我认为这取决于它是 Web 应用程序项目还是网站。对于名为“MyWebApplication”的 Web 应用程序,这将是:
  • 我尝试了您的解决方案,但是如果我使用您的继承控件,则在后面的代码中无法访问它。我的意思是如果我尝试ddlWhatever.Items 它会从ddlWhatever 抛出空异常知道为什么吗?
  • @david : 如果你创建一个UserControl 并尝试继承DropDownList 是行不通的。
  • 对我来说非常适合 ListBox。现在我可以使用自定义属性(如 data-data)通过 jQuery 插件(如 selectize on postback)正确呈现我的控件
  • 谢谢,这个答案解决了问题,但是有没有更新更好的解决方案?
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

    有几个地方可以启用 ViewState - 检查 web.config 中的 &lt;pages/&gt; 节点以及 aspx 文件本身顶部的 &lt;%@ page %&gt; 指令中的 EnableViewState 属性。此设置需要为 true 才能使 ViewState 正常工作。

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

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

    【讨论】:

      【解决方案5】:

      一个简单的解决方案 - 在您请求回发的点击事件上调用您的下拉加载函数。

      【讨论】:

      • 不要忘记在重新加载下拉列表之前存储 dropdown.SelectedIndex,以便之后恢复用户的选择。
      【解决方案6】:

      这是 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
      

      【讨论】:

      • 成功地使用了它,但需要修复一个错误才能使其正常工作。在 LoadViewState 内的两个嵌套循环中,我将 i 增量移动到第一个循环内但在第二个循环之前,并且我还在第一个循环之前将 i 初始化为 0
      • @MPaul 因为在这里修改别人的代码通常被认为是不礼貌的,所以你想更正 rdans 指出的更正还是让我帮你做?
      【解决方案7】:

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

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

      步骤:

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

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

      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:我有一个库函数,用于执行错误处理和数据转换,为简洁起见在此省略)。

      【讨论】:

        【解决方案8】:

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

        【讨论】:

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

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

        【讨论】:

          【解决方案10】:
              //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  
          

          【讨论】:

            【解决方案11】:

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

            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 属性,该属性在回发后丢失:( 所以在添加元素及其属性时,我创建了一个会话变量“attr”加上当回发发生时,从“for”循环中获取的元素(它将像 attr0、attr1、attr2 等......)并在其中保存属性的值(在我的情况下是图像的路径)(在“else”中)我只是循环列表并使用“for”循环的“int”添加从 Session 变量中获取的属性,这与加载页面时相同(这是因为在这个页面中我做不只是选择将元素添加到列表中,因此它们始终具有相同的索引)并再次设置属性,我希望这对将来的某人有所帮助,问候!

            【讨论】:

              猜你喜欢
              • 2013-03-23
              • 1970-01-01
              • 1970-01-01
              • 2015-01-17
              • 1970-01-01
              • 2014-09-27
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多