ListItems-Attribute in einer DropDownList gehen beim Postback verloren?


70

Ein Mitarbeiter hat mir folgendes gezeigt:

Er hat eine DropDownList und eine Schaltfläche auf einer Webseite. Hier ist der Code dahinter:

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

Beim Laden der Seite werden die QuickInfos der Elemente angezeigt, beim ersten Postback gehen die Attribute jedoch verloren. Warum ist dies der Fall und gibt es Problemumgehungen?


Möglicherweise sollte auch Ihr ASPX-Code angezeigt werden.
Madcolor

Antworten:


72

Ich hatte das gleiche Problem und wollte diese Ressource beitragen , bei der der Autor einen geerbten ListItem-Konsumenten erstellt hat, um Attribute für ViewState beizubehalten. Hoffentlich spart es jemandem die Zeit, die ich verschwendet habe, bis ich darauf gestoßen bin.

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

warum so kryptisch? Wenn dies von einem ListItem erben soll, funktioniert es nicht
Ted

2
Sie müssen eine Klasse von DropDownList erben und diese dann verwenden, genau wie Gleapman unten erklärt;)
Markus Szumovski

2
Die Lösung besteht darin, ein neues Steuerelement zu erstellen, das mir nicht gefällt. Es gibt eine Möglichkeit, dies ohne Unterklassen zu tun.

38

Danke, Laramie. Genau das, wonach ich gesucht habe. Es behält die Attribute perfekt.

Im Folgenden finden Sie eine Klassendatei, die ich mit Laramies Code erstellt habe, um eine Dropdown-Liste in VS2008 zu erstellen. Erstellen Sie die Klasse im Ordner App_Code. Verwenden Sie nach dem Erstellen der Klasse diese Zeile auf der Aspx-Seite, um sie zu registrieren:

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

Damit können Sie das Steuerelement auf Ihr Webformular setzen

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

Ok, hier ist die Klasse ...

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

Möglicherweise müssen Sie die Assembly zum Referenz-Tag hinzufügen, auch wenn sie sich in derselben Assembly befindet. Ich denke, es hängt davon ab, ob es sich um ein Webanwendungsprojekt oder eine Website handelt. Dies würde für eine Webanwendung mit dem Namen "MyWebApplication" lauten: <% @ Register Assembly = "MyWebApplication" TagPrefix = "aspNewControls" Namespace = "NewControls"%>
Markus Szumovski

2
Ich habe Ihre Lösung ausprobiert, aber wenn ich Ihre ererbte Steuerung verwende, ist sie im Code dahinter irgendwie nicht zugänglich. Ich meine, wenn ich es versuche ddlWhatever.Items, wird eine Null-Ausnahme von der ddlWhateverIdee ausgelöst, warum?
david

@david: Es funktioniert nicht, wenn Sie ein erstellen UserControlund versuchen, das zu erben DropDownList.
Gabriel GM

Hat bei ListBox hervorragend funktioniert. Jetzt kann ich benutzerdefinierte Attribute wie Daten-Daten verwenden, um meine Steuerelemente über jQuery-Plugins wie Selectize on Postback
Abney317

Danke, diese Antwort löst das Problem, aber gibt es ein Update für eine bessere Lösung?
Leo

14

Eine einfache Lösung besteht darin, die Tooltip-Attribute im pre-renderFalle des Dropdowns hinzuzufügen. Änderungen am Status sollten bei der pre-renderVeranstaltung vorgenommen werden.

Beispielcode :

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

Wenn Sie die Listenelemente nur beim ersten Laden der Seite laden möchten, müssen Sie ViewState aktivieren, damit das Steuerelement seinen Status dort serialisieren und neu laden kann, wenn die Seite zurückgesendet wird.

Es gibt mehrere Stellen, an denen ViewState aktiviert werden kann. Überprüfen Sie den <pages/>Knoten in der Datei web.config und in der <%@ page %>Direktive oben in der Aspx-Datei selbst auf die EnableViewStateEigenschaft. Diese Einstellung muss vorhanden sein, damit trueViewState funktioniert.

Wenn Sie ViewState nicht verwenden möchten, entfernen Sie einfach if (!IsPostBack) { ... }den Code aus dem Code, der das hinzufügt, ListItemsund die Elemente werden bei jedem Postback neu erstellt.

Edit: Ich entschuldige mich - ich habe Ihre Frage falsch verstanden. Sie haben Recht, dass die Attribute kein Postback überleben, da sie in ViewState nicht serialisiert sind. Sie müssen diese Attribute bei jedem Postback erneut hinzufügen.


6

Eine einfache Lösung: Rufen Sie Ihre Dropdown-Ladefunktion für das Klickereignis auf, bei dem Sie eine Rückmeldung anfordern.


Vergessen Sie nicht, die Dropdown-Liste zu speichern. SelectedIndex, bevor Sie die Dropdown-Liste neu laden, damit Sie die Auswahl des Benutzers anschließend wiederherstellen können.
Patrick

2

Typische Lösungen für dieses Problem sind das Erstellen neuer Steuerelemente, die unter normalen Umständen nicht ganz realisierbar sind. Es gibt eine einfache, aber triviale Lösung für dieses Problem.

Das Problem ist, dass der ListItemseine Attribute beim Postback verliert. Die Liste selbst verliert jedoch niemals benutzerdefinierte Attribute. Man kann dies also auf einfache und doch effektive Weise nutzen.

Schritte:

  1. Serialisieren Sie Ihre Attribute mit dem Code in der obigen Antwort ( https://stackoverflow.com/a/3099755/3624833 ).

  2. Speichern Sie es in einem benutzerdefinierten Attribut des ListControl (Dropdown-Liste, Checkliste, was auch immer).

  3. Lesen Sie beim Zurücksenden das benutzerdefinierte Attribut aus dem ListControl zurück und deserialisieren Sie es dann wieder als Attribute.

Hier ist der Code, mit dem ich Attribute (de) serialisiert habe (Was ich tun musste, war zu verfolgen, welche Elemente der Liste ursprünglich als ausgewählt gerendert wurden, als sie aus dem Backend abgerufen wurden, und dann Zeilen gemäß den von vorgenommenen Änderungen zu speichern oder zu löschen der Benutzer auf der Benutzeroberfläche):

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

(oben ist "Benutzer" ein CheckboxListSteuerelement).

Beim Zurücksenden (in meinem Fall ein Ereignis zum Senden der Schaltfläche "Klicken") verwende ich den folgenden Code, um dasselbe abzurufen und zur Nachbearbeitung in einem Wörterbuch zu speichern:

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: Ich habe eine Bibliotheksfunktion, die Fehlerbehandlung und Datenkonvertierung durchführt und diese hier der Kürze halber weglässt.)


2

Hier ist der VB.Net-Code der von Laramie vorgeschlagenen und von gleapman verfeinerten Lösung.

Update: Der Code, den ich unten gepostet habe, ist eigentlich für das ListBox-Steuerelement. Ändern Sie einfach die Vererbung in DropDownList und benennen Sie die Klasse um.

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
Dies wurde erfolgreich verwendet, musste jedoch eine Fehlerbehebung durchgeführt werden, damit es richtig funktioniert. In den zwei verschachtelten Schleifen innerhalb des LoadViewState habe ich das i-Inkrement in die erste Schleife verschoben, aber vor die zweite Schleife, und ich habe i auch vor der ersten Schleife auf 0 initialisiert
Uhr

@MPaul Da es hier allgemein als unhöflich angesehen wird, den Code eines anderen zu ändern, möchten Sie die Korrektur vornehmen, auf die rdans hingewiesen hat, oder möchten Sie, dass ich dies für Sie tue?
Andrew Morton

1

@Sujay Sie können dem Wertattribut der Dropdown-Liste (wie im CSV-Stil) einen durch Semikolon getrennten Text hinzufügen und String.Split (';') verwenden, um 2 "Werte" aus dem einen Wert zu erhalten, um dies zu umgehen ohne dass eine neue Benutzersteuerung erstellt werden muss. Vor allem, wenn Sie nur wenige zusätzliche Attribute haben und diese nicht zu lang sind. Sie können auch einen JSON-Wert in das Wertattribut der Dropdown-Liste verwenden und dann von dort aus alles analysieren, was Sie benötigen.


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

Einfache Lösung ohne ViewState, die neue Serversteuerung oder einen neuen Komplex erstellt:

Erstellen:

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

Aktualisierung:

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

Beispiel:

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

Mit dieser Lösung stellen Sie bei jedem Post-Back Anforderungen an die Datenbank. Es ist besser, ViewState zu verwenden.
nZeus

0

Ich habe es geschafft, dies mithilfe von Sitzungsvariablen zu erreichen. In meinem Fall wird meine Liste nicht viele Elemente enthalten, sodass sie ziemlich gut funktioniert. So habe ich es gemacht:

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

Wenn die Seite zum ersten Mal geladen wird, wird die Liste gefüllt und ich füge ein Bildattribut hinzu, das nach dem Postback verloren geht :( Wenn ich also die Elemente mit ihren Attributen hinzufüge, erstelle ich eine Sitzungsvariable "attr" plus die Nummer des aufgenommenen Elements aus dem "für" -Zyklus (es wird wie attr0, attr1, attr2 usw. sein) und in ihnen speichere ich den Wert des Attributs (in meinem Fall einen Pfad zu einem Bild), wenn ein Postback auftritt (innerhalb des " sonst ") Ich schleife einfach die Liste und füge das Attribut aus der Sitzungsvariablen hinzu, indem ich das" int "der" for "-Schleife verwende, das dem beim Laden der Seite entspricht (dies liegt daran, dass ich auf dieser Seite keine Elemente hinzufüge Ich hoffe, dies hilft jemandem in der Zukunft, Grüße!

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.