71

一位同事给我看了这个:

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

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

4

11 回答 11

73

我遇到了同样的问题,想贡献这个资源,作者创建了一个继承的 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];
            }
        }
    }
}
于 2010-06-23T07:35:34.113 回答
40

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

为了展开,下面是我使用 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];
                }
            }
        }
    }
  }
}
于 2011-05-10T21:58:21.760 回答
15

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");
        }
于 2012-10-25T15:43:58.887 回答
8

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

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

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

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

于 2009-08-21T18:12:42.277 回答
6

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

于 2011-07-12T12:59:29.210 回答
3

这是 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
于 2016-03-11T21:58:34.730 回答
2

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

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

于 2015-01-06T03:53:22.797 回答
1

没有 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));
        }
    }
}
于 2015-11-11T19:05:20.380 回答
1

@Sujay You could add a semi-colon separated text into the dropdown's value attribute (like csv style), and use String.Split(';') to get 2 "values" out of the one value, as a workaround to get away with not having to create anew user control. Especially if you only have few extra attributes, and if it is not too long. You could also use a JSON value into the dropdown's value attribute and then parse out whatever you need from there.

于 2017-04-13T08:58:25.420 回答
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  
于 2013-07-12T15:00:37.367 回答
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();
        }
    }
}

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

于 2016-07-18T15:10:19.263 回答