web-dev-qa-db-fra.com

Les attributs ListItems dans une DropDownList sont perdus à la publication?

Un collègue m'a montré ceci:

Il a une liste déroulante et un bouton sur une page Web. Voici le code derrière:

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

Lors du chargement de la page, les info-bulles des éléments sont affichées, mais les attributs sont perdus lors du premier postback. Pourquoi est-ce le cas et existe-t-il des solutions de contournement?

64
David Hodgson

J'ai eu le même problème et je voulais contribuer this resource où l'auteur a créé un consommateur ListItem hérité pour conserver les attributs dans ViewState. J'espère que cela sauvera à quelqu'un le temps que j'ai perdu jusqu'à ce que je tombe par hasard dessus.

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

Merci Laramie. Exactement ce que je cherchais. Il conserve parfaitement les attributs. 

Pour développer, vous trouverez ci-dessous un fichier de classe que j'ai créé à l'aide du code de Laramie pour créer une liste déroulante dans VS2008. Créez la classe dans le dossier App_Code. Après avoir créé la classe, utilisez cette ligne sur la page aspx pour l’inscrire:

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

Vous pouvez ensuite mettre le contrôle sur votre formulaire Web avec cette

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

Ok, voici la classe ...

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

La solution simple consiste à ajouter les attributs d'info-bulle dans l'événement pre-render du menu déroulant. Toute modification de l'état doit être effectuée à l'événement pre-render.

exemple de 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");
        }
12
ram

Si vous souhaitez uniquement charger les éléments de liste lors du premier chargement de la page, vous devez activer ViewState pour que le contrôle puisse y sérialiser son état et le recharger lors de la publication de la page.

ViewState peut être activé à plusieurs endroits - vérifiez le noeud <pages/> dans le fichier web.config et dans la directive <%@ page %> en haut du fichier aspx lui-même pour la propriété EnableViewState. Ce paramètre devra être true pour que ViewState fonctionne.

Si vous ne souhaitez pas utiliser ViewState, supprimez simplement la if (!IsPostBack) { ... } dans le code qui ajoute la ListItems et les éléments seront recréés à chaque publication.

Edit: Je m'excuse - J'ai mal interprété votre question. Vous avez raison de dire que les attributs ne survivent pas à la publication car ils ne sont pas sérialisés dans ViewState. Vous devez rajouter ces attributs à chaque publication.

8
Andrew Hare

Une solution simple: appelez votre fonction de téléchargement dans l'événement de clic pour lequel vous demandez la publication.

6
Subhash

Les solutions classiques à ce problème consistent à créer de nouveaux contrôles qui ne sont pas tout à fait réalisables dans des circonstances normales. Il existe une solution simple mais triviale à ce problème. 

Le problème est que la ListItem perd ses attributs lors de la publication. Cependant, la liste elle-même ne perd aucun attribut personnalisé. On peut en tirer profit d’une manière simple mais efficace. 

Pas: 

  1. Sérialisez vos attributs en utilisant le code de la réponse ci-dessus ( https://stackoverflow.com/a/3099755/3624833

  2. Stockez-le dans un attribut personnalisé du ListControl (liste déroulante, liste de contrôle, peu importe).

  3. Lors du post-retour, relisez l'attribut personnalisé à partir du ListControl, puis désérialisez-le en tant qu'attributs.

Voici le code que j’utilisais pour (dé) sérialiser les attributs. l'utilisateur sur l'interface utilisateur):

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

(ci-dessus, "Utilisateurs" est un contrôle CheckboxList). 

Sur le post de retour (dans mon cas, un événement de bouton de soumission en cliquant), j'utilise le code ci-dessous pour récupérer le même et les stocker dans un dictionnaire pour le post-traitement:

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: J'ai une bibliothèque de fonctions qui effectue la gestion des erreurs et les conversions de données, en omettant ici la même chose pour des raisons de brièveté).

2
Sujay Sarma

Voici le code VB.Net de la solution proposée par Laramie et affinée par gleapman.

Mise à jour: Le code que j'ai posté ci-dessous est en réalité destiné au contrôle ListBox. Il suffit de changer l'héritage en DropDownList et de renommer la classe.

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
MPaul

@Sujay Vous pouvez ajouter un texte séparé par des points-virgules dans l'attribut de valeur du menu déroulant (comme le style csv) et utiliser String.Split (';') pour obtenir 2 "valeurs" parmi la valeur, comme solution sortir sans avoir à créer un nouveau contrôle utilisateur. Surtout si vous n'avez que peu d'attributs en plus, et si ce n'est pas trop long. Vous pouvez également utiliser une valeur JSON dans l'attribut value du menu déroulant, puis analyser ce dont vous avez besoin à partir de là.

1
APetersen786

J'ai réussi à atteindre cet objectif en utilisant des variables de session. Dans mon cas, ma liste ne contiendra pas beaucoup d'éléments, donc ça marche plutôt bien, voici comment je l'ai fait:

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

Lorsque la page est chargée pour la première fois, la liste est remplie et j'ajoute un attribut Image qui est perdu après publication :( donc au moment où j'ajoute les éléments avec ses attributs, je crée une variable de session "attr" plus le numéro de l'élément pris à partir du cycle "pour" (ce sera comme attr0, attr1, attr2, etc ...) et dans lequel je sauvegarde la valeur de l'attribut (un chemin d'accès à une image dans mon cas), lorsque la publication a lieu (à l'intérieur du " else ") Je boucle juste la liste et ajoute l'attribut pris de la variable Session en utilisant le" int "de la boucle" pour "identique à celle du chargement de la page (c'est parce que dans cette page, je n'ajoute pas d'éléments à la liste en sélectionnant simplement pour qu'ils aient toujours le même index) et que les attributs soient à nouveau définis, j'espère que cela aidera quelqu'un à l'avenir, salutations!

0
JCO9
    //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
Raphael Soliman

Solution simple sans ViewState, créant un nouveau contrôle de serveur ou un complexe:

En créant:

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

Mise à jour:

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

Exemple:

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
Andrei Shostik