ListItems attributes in a DropDownList are lost on postback?

69,189

Solution 1

I had the same problem and wanted to contribute this resource where the author created an inherited ListItem Consumer to persist attributes to ViewState. Hopefully it will save someone the time I wasted until I stumbled on it.

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

Solution 2

Thanks, Laramie. Just what I was looking for. It keeps the attributes perfectly.

To expand, below is a class file I created using Laramie's code to create a dropdownlist in VS2008. Create the class in the App_Code folder. After you create the class, use this line on the aspx page to register it:

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

You can then put the control on your webform with this

<aspNewControls:NewDropDownList ID="ddlWhatever" runat="server">
                                                </aspNewControls:NewDropDownList>

Ok, here's the class...

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

Solution 3

Simple solution is to add the tooltip attributes in the pre-render event of the dropdown. Any changes to the state should be done at pre-render event.

sample code :

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

Solution 4

If you only want to load the listitems on the first load of the page then you will need to enable ViewState so that the control can serialize its state there and reload it when the page posts back.

There are several places where ViewState can be enabled - check the <pages/> node in the web.config and also in the <%@ page %> directive at the top of the aspx file itself for the EnableViewState property. This setting will need to be true for ViewState to work.

If you don't want to use ViewState, simply remove the if (!IsPostBack) { ... } from around the code that adds the ListItems and the items will be recreated on each postback.

Edit: I apologize - I misread your question. You are correct that the attributes do no survive postback as they are not serialized in ViewState. You must re-add those attributes on each postback.

Solution 5

One simple solution- Call your drop down loading function on the click event where you request for post back.

Share:
69,189
David Hodgson
Author by

David Hodgson

Updated on July 09, 2022

Comments

  • David Hodgson
    David Hodgson almost 2 years

    A coworker showed me this:

    He has a DropDownList and a button on a web page. Here's the code behind:

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

    On the page load, the items' tooltips are showing, but on the first postback, the attributes are lost. Why is this the case, and are there any workarounds?

  • Ted
    Ted over 10 years
    why so cryptic? if this is meant to inherit from a ListItem then it doesn't work
  • Markus  Szumovski
    Markus Szumovski over 10 years
    You have to inherit a class from DropDownList and then use this, just as gleapman explained below ;)
  • Markus  Szumovski
    Markus Szumovski over 10 years
    Could be that you'll have to add the Assembly to the Reference-Tag, even if it's in the same Assembly... I think it depends if it's a Web Application Project or a Website. This would, for a Web Application named "MyWebApplication", then read: <%@ Register Assembly="MyWebApplication" TagPrefix="aspNewControls" Namespace="NewControls"%>
  • m3div0
    m3div0 over 9 years
    I tried your sollution, but if I use your inherrited control, it is somehow inaccessible in the code behind. I mean if I try ddlWhatever.Items it throws null exception from the ddlWhatever Any idea why?
  • Admin
    Admin over 9 years
    The solution involves creating a new control that I don't like. There is a way to do this without any subclassing.
  • Gabriel GM
    Gabriel GM about 9 years
    @david : It doesn't work if you create a UserControl and try to inherit the DropDownList.
  • Patrick
    Patrick almost 8 years
    Don't forget to store the dropdown.SelectedIndex before you reload the dropdown so you can restore the user's selection afterward.
  • nZeus
    nZeus almost 8 years
    With this solution you do requests to the database on every post-back. It's better to use ViewState.
  • rdans
    rdans over 7 years
    used this successfully but needed to make one bug fix to get it to work right. In the two nested loops within the LoadViewState I moved the i increment to within the first loop but before the second loop and I also initialised i to 0 before the first loop
  • abney317
    abney317 over 6 years
    Worked great for me for ListBox. Now I can use custom attributes like data-data to properly render out my controls through jQuery plugins like selectize on postback
  • Andrew Morton
    Andrew Morton about 6 years
    @MPaul As it is generally considered impolite here to alter someone else's code, would you like to make the correction that rdans pointed out or would you like me to do it for you?
  • Leo
    Leo over 4 years
    Thanks, this answer solve the problem but are there any update for better solution?