Atrybuty ListItems w DropDownList są tracone na postback?

Współpracownik pokazał mi to:

Ma DropDownList i przycisk na stronie internetowej. Oto kod za:

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

Podczas ładowania strony wyświetlane są podpowiedzi elementów, ale przy pierwszym postbacku atrybuty są tracone. Dlaczego tak jest i czy istnieją jakieś obejścia?

Author: David Hodgson, 2009-08-21

11 answers

Miałem ten sam problem i chciałem dodać ten zasób, w którym autor stworzył odziedziczoną listitem konsumenta, aby utrzymać atrybuty ViewState. Mam nadzieję, że zaoszczędzi to komuś czasu, który zmarnowałem, dopóki na niego nie natknąłem się.

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];
            }
        }
    }
}
 68
Author: Laramie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-26 21:06:21

Dzięki, Laramie. Właśnie tego szukałem. Doskonale zachowuje atrybuty.

Aby rozwinąć, poniżej znajduje się plik klasy, który stworzyłem za pomocą kodu Laramie, aby utworzyć dropdownlist w VS2008. Utwórz klasę w folderze App_Code. Po utworzeniu klasy użyj tej linii na stronie aspx, aby ją zarejestrować:

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

Możesz następnie umieścić kontrolkę na swoim formularzu za pomocą tego

<aspNewControls:NewDropDownList ID="ddlWhatever" runat="server">
                                                </aspNewControls:NewDropDownList>
Ok, oto Klasa...
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];
                }
            }
        }
    }
  }
}
 34
Author: gleapman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-10 21:58:21

Prostym rozwiązaniem jest dodanie atrybutów tooltip w zdarzeniu pre-render rozwijanego menu. Wszelkie zmiany stanu powinny być dokonywane przy zdarzeniu pre-render.

Przykładowy kod:

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");
        }
 10
Author: ram,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-25 16:03:44

Jeśli chcesz załadować listy tylko przy pierwszym załadowaniu strony, musisz włączyć ViewState, aby kontrolka mogła serializować swój stan i przeładować go, gdy strona zostanie ponownie załadowana.

Istnieje kilka miejsc, w których można włączyć ViewState-sprawdź węzeł <pages/> w sieci.config, a także w dyrektywie <%@ page %> u góry samego pliku aspx dla właściwości EnableViewState. Aby ViewState zadziałało, to ustawienie musi być true.

Jeśli nie chcesz używać ViewState, po prostu usuń if (!IsPostBack) { ... } z kodu, który dodaje ListItems, a elementy zostaną odtworzone na każdym postbacku.

Edit: przepraszam-źle odczytałem twoje pytanie. Masz rację, że atrybuty nie przetrwają postback, ponieważ nie są serializowane w ViewState. Musisz ponownie dodać te atrybuty na każdym postbacku.

 8
Author: Andrew Hare,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-21 19:01:59

Jedno proste rozwiązanie-wywołanie funkcji ładowania rozwijanego na zdarzeniu kliknięcia, w którym prosisz o powrót posta.

 6
Author: Subhash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-12 12:59:29

Typowe rozwiązania tego problemu obejmują tworzenie nowych kontroli, które nie są całkiem możliwe w normalnych okolicznościach. Istnieje proste, ale trywialne rozwiązanie tego problemu.

Problem polega na tym, że ListItem traci swoje atrybuty na postbacku. Jednak sama lista nigdy nie traci żadnych niestandardowych atrybutów. Można z tego skorzystać w prosty, ale skuteczny sposób.

Kroki:

  1. Serializuj swoje atrybuty za pomocą kodu w odpowiedzi powyżej ( https://stackoverflow.com/a/3099755/3624833)

  2. Przechowuj go do niestandardowego atrybutu ListControl(dropdownlist, checklistbox, cokolwiek).

  3. Po ponownym odczytaniu atrybutu niestandardowego z kontrolki ListControl, a następnie deserializuj go jako atrybuty.

Oto kod, którego użyłem do (De)serializacji atrybutów (co musiałem zrobić, aby śledzić, które elementy listy zostały pierwotnie renderowane jako wybrane podczas pobierania z zaplecza, a następnie zapisz lub Usuń wiersze zgodnie ze zmianami wprowadzonymi przez użytkownika w interfejsie użytkownika):

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

(powyżej "użytkownicy" to kontrolka CheckboxList).

Po powrocie postu (w moim przypadku przycisk Submit Click event), używam poniższego kodu, aby pobrać to samo i zapisać je w słowniku do przetwarzania postu:

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: mam funkcje biblioteczne, które wykonują obsługę błędów i konwersje danych, pomijając to samo tutaj dla zwięzłości).

 2
Author: Sujay Sarma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:25:36

Oto VB.Net kod rozwiązania zaproponowanego przez Laramie i dopracowanego przez gleapmana.

Update: kod, który umieściłem poniżej, jest właściwie przeznaczony do kontroli ListBox. Wystarczy zmienić dziedziczenie na DropDownList I zmienić nazwę klasy.

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
 1
Author: MPaul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-14 14:13:15

@Sujay Możesz dodać tekst oddzielony dwukropkiem do atrybutu wartości rozwijanej (jak styl csv) i użyć ciągu znaków.Split (';'), aby uzyskać 2 "wartości" z jednej wartości, jako obejście, aby uniknąć konieczności tworzenia nowej kontroli użytkownika. Zwłaszcza jeśli masz tylko kilka dodatkowych atrybutów i jeśli nie jest zbyt długi. Możesz również użyć wartości JSON do atrybutu wartości rozwijanej, a następnie przetworzyć wszystko, czego potrzebujesz.

 1
Author: APetersen786,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-13 08:58:25
    //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
Author: Raphael Soliman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-12 15:20:15

Proste rozwiązanie bez ViewState, tworzenie nowej kontroli serwera lub kompleksu smth:

Tworzenie:

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

Aktualizacja:

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

Przykład:

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));
        }
    }
}
 0
Author: Andrei Shostik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-11 19:05:20

Udało mi się to osiągnąć przy użyciu zmiennych sesji, w moim przypadku moja lista nie będzie zawierać wielu elementów, więc działa całkiem dobrze, tak to zrobiłem:

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

Gdy strona jest ładowana po raz pierwszy lista jest wypełniona i dodaję atrybut obrazu, który jest utracony po postbacku : (tak więc w czasie dodawania elementów z jego atrybutami tworzę jedną zmienną sesyjną " attr "plus numer elementu wziętego z cyklu" for " (będzie to jak attr0, attr1, attr2, itd...) i w nich zapisuję wartość atrybutu (ścieżka do obrazu w moim przypadku), gdy postback występuje (wewnątrz "else") po prostu zapętlam listę i dodaję atrybut pobrany ze zmiennej sesji za pomocą" int "z pętli" for", która jest taka sama, jak gdy strona została załadowana (dzieje się tak dlatego, że na tej stronie nie dodaję elementów do listy tylko wybierając tak, że zawsze mają ten sam indeks) i atrybuty są ustawione ponownie, mam nadzieję, że to pomoże komuś w przyszłości, pozdrawiam!

 0
Author: JCO9,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-18 15:10:19