|
code
newsgroups
|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
ASP.NET 2.0 Dropdownlist EnableViewState=false SelectedIndexChangebut have run into an issue with the DropDownList. Problem statement SelectedIndexChanged events are getting fired for every dropdown since the page was first rendered. Here's a sequence example. 1. Load page 2. Change value in ddl #1 ... causes postback. SelectedIndexChanged fired for ddl #1. 3. Change value in ddl #2 ... causes postback. SelectedIndexChanged fired for ddl #1 and ddl #2. This is the unexpected behavior. Would only expect SelectedIndexChanged to be fired for ddl #2 Assumptions 1. Multiple drop down lists on page 2. Autopostback = true 3. EnableViewState on page = false 4. Each ddl has it's own selectedindexchanged event 5. Lists in the ddl's are being rebuilt by overriding OnPreInit on page. Also tried populating lists in OnInit and OnInitComplete ... same result 6. Subscribe to SelectedIndexChanged event in OnLoad. Also tried setting onselectedindexchanged properties of ddl in aspx ... same result. I'd like to know what I need to do in order to get only the SelectedIndexChanged event to fire for the control that actually changed. If I set EnableViewState=true, the expected behavior is exhibited. I've attached a sample scenario below. Your help is appreciated. Thanks. Anson <aspx_page> <%@ Page Language="C#" AutoEventWireup="false" CodeFile="TestMultipleEventsOnPostback.aspx.cs" Inherits="TestMultipleEventsOnPostback" enableviewstate="false" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>DropDownList SelectedIndexChanged EnableViewState="False" Example</title> </head> <body> <form id="form1" runat="server"> <div> <asp:label id="summary" runat="server"></asp:label> <br /> <br /> <table width="100%"> <tr> <td>List #1</td> <td>List #2</td> <td>Repeater</td> </tr> <tr> <td><asp:dropdownlist id="list1" runat="server" autopostback="true"></asp:dropdownlist></td> <td><asp:dropdownlist id="list2" runat="server" autopostback="true"></asp:dropdownlist></td> <td> <table> <asp:repeater id="repeat" runat="server"> <itemtemplate> <tr> <td><asp:label id="itemTemplateLabel" runat="server"></asp:label></td> <td><asp:dropdownlist id="itemTemplateList" runat="server" autopostback="true"></asp:dropdownlist></td> </tr> </itemtemplate> </asp:repeater> </table> </td> </tr> </table> </div> </form> </body> </html> </aspx_page> <aspx_cs_codebehind> using System; using System.Collections.Generic; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class TestMultipleEventsOnPostback : System.Web.UI.Page { //items for list #1 private Dictionary<string, string> _list1Items = new Dictionary<string, string>(); //items for list #2 private Dictionary<string, string> _list2Items = new Dictionary<string, string>(); //items for repeater drop down private Dictionary<string, string> _repeaterListItems = new Dictionary<string, string>(); //repeater source just to get 5 items created private object[] _repeaterSource = new object[5]; public TestMultipleEventsOnPostback() { //create the list 1 items for (int i = 0; i <= 10; i++) { this._list1Items.Add(i.ToString(), string.Format("Value = {0}", i)); } //create the list 2 items for (int i = 0; i <= 40; i++) { this._list2Items.Add(i.ToString(), string.Format("Value = {0}", i)); } //create the list repeater items for (int i = 0; i <= 60; i++) { this._repeaterListItems.Add(i.ToString(), string.Format("Value = {0}", i)); } } protected override void OnPreInit(EventArgs e) { //populate the drop down lists and rebuild the repeater this.list1.DataTextField = "Value"; this.list1.DataValueField = "Key"; this.list1.DataSource = this._list1Items; this.list1.DataBind(); this.list2.DataTextField = "Value"; this.list2.DataValueField = "Key"; this.list2.DataSource = this._list2Items; this.list2.DataBind(); //rebuild the repeater control this.repeat.ItemDataBound += new RepeaterItemEventHandler(repeat_ItemDataBound); this.repeat.DataSource = this._repeaterSource; this.repeat.DataBind(); base.OnPreInit(e); } protected override void OnLoad(EventArgs e) { if (!this.IsPostBack) { this.summary.Text = "No postback"; } else { //clear out the summary label this.summary.Text = "<b>Event's that fired for current postback (Note: I would only expect 1 because all the dropdowns have autopostback=\"true\")</b>"; } //set up the event handlers this.list1.SelectedIndexChanged += new EventHandler(list1_SelectedIndexChanged); this.list2.SelectedIndexChanged += new EventHandler(list2_SelectedIndexChanged); //set up the event for the drop down in the repeater foreach (RepeaterItem item in this.repeat.Items) { DropDownList ddl = (DropDownList)item.FindControl("itemTemplateList"); ddl.SelectedIndexChanged += new EventHandler(itemTemplateList_SelectedIndexChanged); } base.OnLoad(e); } protected void itemTemplateList_SelectedIndexChanged(object sender, EventArgs e) { this.summary.Text += "<br/>itemTemplateList_SelectedIndexChanged"; } protected void list2_SelectedIndexChanged(object sender, EventArgs e) { this.summary.Text += "<br/>list2_SelectedIndexChanged"; } protected void list1_SelectedIndexChanged(object sender, EventArgs e) { this.summary.Text += "<br/>list1_SelectedIndexChanged"; } protected void repeat_ItemDataBound(object sender, RepeaterItemEventArgs e) { DropDownList ddl = (DropDownList)e.Item.FindControl("itemTemplateList"); ddl.DataTextField = "Value"; ddl.DataValueField = "Key"; ddl.DataSource = this._repeaterListItems; ddl.DataBind(); } } </aspx_cs_codebehind> Hi Anson,
I put your code on my site to illustrate to you what is happening: http://www.webswapp.com/codesamples/testMultipleEventsOnPostBack.aspx The problem here is that when you set the EnableViewState to false, you have reconstructed the dropdownlists on every postback. After reconstructing them, (at the end of the OnPreInit), the selectedindex is always =0. Therefore if you had changed the first dropdownlist (which fired the first event) then changed the second dropdownlist, the event of the first would still be fired because as far as the page is concerned you have changed both dropdownlist from the time you reconstructed them. Your problem disappeared when you set the EnableViewState to true because after the OnPreInit the state of the first dropdownlist (from previous postback) changed the selectedIndex of your list and therefore the dropdownlist did not sense a change in the selectedindex. Show quoteHide quote "Anson Goldade" wrote: > I'm trying to leverage the viewstate optimization capabilities in ASP.NET 2.0 > but have run into an issue with the DropDownList. > > Problem statement > SelectedIndexChanged events are getting fired for every dropdown since the > page was first rendered. Here's a sequence example. > 1. Load page > 2. Change value in ddl #1 ... causes postback. SelectedIndexChanged fired > for ddl #1. > 3. Change value in ddl #2 ... causes postback. SelectedIndexChanged fired > for ddl #1 and ddl #2. This is the unexpected behavior. Would only expect > SelectedIndexChanged to be fired for ddl #2 > > Assumptions > 1. Multiple drop down lists on page > 2. Autopostback = true > 3. EnableViewState on page = false > 4. Each ddl has it's own selectedindexchanged event > 5. Lists in the ddl's are being rebuilt by overriding OnPreInit on page. > Also tried populating lists in OnInit and OnInitComplete ... same result > 6. Subscribe to SelectedIndexChanged event in OnLoad. Also tried setting > onselectedindexchanged properties of ddl in aspx ... same result. > > I'd like to know what I need to do in order to get only the > SelectedIndexChanged event to fire for the control that actually changed. If > I set EnableViewState=true, the expected behavior is exhibited. I've > attached a sample scenario below. Your help is appreciated. Thanks. > > Anson > > <aspx_page> > > <%@ Page Language="C#" AutoEventWireup="false" > CodeFile="TestMultipleEventsOnPostback.aspx.cs" > Inherits="TestMultipleEventsOnPostback" enableviewstate="false" %> > > <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" > "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> > > <html xmlns="http://www.w3.org/1999/xhtml" > > <head runat="server"> > <title>DropDownList SelectedIndexChanged EnableViewState="False" > Example</title> > </head> > <body> > <form id="form1" runat="server"> > <div> > <asp:label id="summary" runat="server"></asp:label> > <br /> > <br /> > <table width="100%"> > <tr> > <td>List #1</td> > <td>List #2</td> > <td>Repeater</td> > </tr> > <tr> > <td><asp:dropdownlist id="list1" runat="server" > autopostback="true"></asp:dropdownlist></td> > <td><asp:dropdownlist id="list2" runat="server" > autopostback="true"></asp:dropdownlist></td> > <td> > <table> > <asp:repeater id="repeat" runat="server"> > <itemtemplate> > <tr> > <td><asp:label id="itemTemplateLabel" > runat="server"></asp:label></td> > <td><asp:dropdownlist id="itemTemplateList" > runat="server" autopostback="true"></asp:dropdownlist></td> > </tr> > </itemtemplate> > </asp:repeater> > </table> > </td> > </tr> > </table> > > </div> > </form> > </body> > </html> > > </aspx_page> > > <aspx_cs_codebehind> > > using System; > using System.Collections.Generic; > using System.Data; > using System.Configuration; > using System.Collections; > using System.Web; > using System.Web.Security; > using System.Web.UI; > using System.Web.UI.WebControls; > using System.Web.UI.WebControls.WebParts; > using System.Web.UI.HtmlControls; > > public partial class TestMultipleEventsOnPostback : System.Web.UI.Page > { > //items for list #1 > private Dictionary<string, string> _list1Items = new Dictionary<string, > string>(); > //items for list #2 > private Dictionary<string, string> _list2Items = new Dictionary<string, > string>(); > //items for repeater drop down > private Dictionary<string, string> _repeaterListItems = new > Dictionary<string, string>(); > //repeater source just to get 5 items created > private object[] _repeaterSource = new object[5]; > public TestMultipleEventsOnPostback() > { > //create the list 1 items > for (int i = 0; i <= 10; i++) > { > this._list1Items.Add(i.ToString(), string.Format("Value = {0}", > i)); > } > //create the list 2 items > for (int i = 0; i <= 40; i++) > { > this._list2Items.Add(i.ToString(), string.Format("Value = {0}", > i)); > } > //create the list repeater items > for (int i = 0; i <= 60; i++) > { > this._repeaterListItems.Add(i.ToString(), string.Format("Value = > {0}", i)); > } > } > protected override void OnPreInit(EventArgs e) > { > //populate the drop down lists and rebuild the repeater > this.list1.DataTextField = "Value"; > this.list1.DataValueField = "Key"; > this.list1.DataSource = this._list1Items; > this.list1.DataBind(); > > this.list2.DataTextField = "Value"; > this.list2.DataValueField = "Key"; > this.list2.DataSource = this._list2Items; > this.list2.DataBind(); > > //rebuild the repeater control > this.repeat.ItemDataBound += new > RepeaterItemEventHandler(repeat_ItemDataBound); > this.repeat.DataSource = this._repeaterSource; > this.repeat.DataBind(); > > base.OnPreInit(e); > } > protected override void OnLoad(EventArgs e) > { > if (!this.IsPostBack) > { > this.summary.Text = "No postback"; > } > else > { > //clear out the summary label > this.summary.Text = "<b>Event's that fired for current postback > (Note: I would only expect 1 because all the dropdowns have > autopostback=\"true\")</b>"; > } > //set up the event handlers > this.list1.SelectedIndexChanged += new > EventHandler(list1_SelectedIndexChanged); > this.list2.SelectedIndexChanged += new > EventHandler(list2_SelectedIndexChanged); > //set up the event for the drop down in the repeater > foreach (RepeaterItem item in this.repeat.Items) > { > DropDownList ddl = > (DropDownList)item.FindControl("itemTemplateList"); > ddl.SelectedIndexChanged += new > EventHandler(itemTemplateList_SelectedIndexChanged); > } > base.OnLoad(e); > } > > protected void itemTemplateList_SelectedIndexChanged(object sender, > EventArgs e) > { > this.summary.Text += "<br/>itemTemplateList_SelectedIndexChanged"; > } > > protected void list2_SelectedIndexChanged(object sender, EventArgs e) > { > this.summary.Text += "<br/>list2_SelectedIndexChanged"; > } > > protected void list1_SelectedIndexChanged(object sender, EventArgs e) > { > this.summary.Text += "<br/>list1_SelectedIndexChanged"; > } > > protected void repeat_ItemDataBound(object sender, RepeaterItemEventArgs > e) > { > DropDownList ddl = > (DropDownList)e.Item.FindControl("itemTemplateList"); > ddl.DataTextField = "Value"; > ddl.DataValueField = "Key"; > ddl.DataSource = this._repeaterListItems; > ddl.DataBind(); > } > } > > </aspx_cs_codebehind> > Phillip,
I absolutely agree with our assessment. However, with the new ControlState model that was implemented in controls in ASP.NET 2.0, we shouldn't need view state to have the control update it's selectedindex between when the control is populated in PreInit and when the event is subscribed to in OnLoad. According to the MSDN docs, all viewstate (and I'm assuming that includes controlstate) is restored by the time that OnLoad is called. This should mean that the selected index has been reset to the value it was prior to the postback. To illustrate this, disable ViewState and check what the selected index just prior to me subscribing to the selectedindexchanged events in OnLoad. Since I don't subscribe to the events until the index has been reset (and prior to the postback data being processed) only the controls that actually had value changes should fire the selectedindexchanged events. Perhaps I'm missing something with the new ControlState model. I started going down this path based on the following article. http://msdn.microsoft.com/msdnmag/issues/04/10/ViewState/default.aspx The overall goal here is to not have to enable viewstate, but continue to have the controls firing the server side events. I don't mind repopulating the list controls ... in fact I find that much preferrable to serializing 10K of viewstate to a client that just ends up posting that back to me. I'm just trying to figure out if I'm doing something wrong or if I'm missing the point completely. Thanks again for your time and feedback and I look forward to working with you toward a resolution. Anson Goldade Show quoteHide quote "Phillip Williams" wrote: > Hi Anson, > > I put your code on my site to illustrate to you what is happening: > http://www.webswapp.com/codesamples/testMultipleEventsOnPostBack.aspx > > The problem here is that when you set the EnableViewState to false, you have > reconstructed the dropdownlists on every postback. After reconstructing > them, (at the end of the OnPreInit), the selectedindex is always =0. > Therefore if you had changed the first dropdownlist (which fired the first > event) then changed the second dropdownlist, the event of the first would > still be fired because as far as the page is concerned you have changed both > dropdownlist from the time you reconstructed them. > > Your problem disappeared when you set the EnableViewState to true because > after the OnPreInit the state of the first dropdownlist (from previous > postback) changed the selectedIndex of your list and therefore the > dropdownlist did not sense a change in the selectedindex. > > -- > HTH, > Phillip Williams > http://www.societopia.net > http://www.webswapp.com > > > "Anson Goldade" wrote: > > > I'm trying to leverage the viewstate optimization capabilities in ASP.NET 2.0 > > but have run into an issue with the DropDownList. > > > > Problem statement > > SelectedIndexChanged events are getting fired for every dropdown since the > > page was first rendered. Here's a sequence example. > > 1. Load page > > 2. Change value in ddl #1 ... causes postback. SelectedIndexChanged fired > > for ddl #1. > > 3. Change value in ddl #2 ... causes postback. SelectedIndexChanged fired > > for ddl #1 and ddl #2. This is the unexpected behavior. Would only expect > > SelectedIndexChanged to be fired for ddl #2 > > > > Assumptions > > 1. Multiple drop down lists on page > > 2. Autopostback = true > > 3. EnableViewState on page = false > > 4. Each ddl has it's own selectedindexchanged event > > 5. Lists in the ddl's are being rebuilt by overriding OnPreInit on page. > > Also tried populating lists in OnInit and OnInitComplete ... same result > > 6. Subscribe to SelectedIndexChanged event in OnLoad. Also tried setting > > onselectedindexchanged properties of ddl in aspx ... same result. > > > > I'd like to know what I need to do in order to get only the > > SelectedIndexChanged event to fire for the control that actually changed. If > > I set EnableViewState=true, the expected behavior is exhibited. I've > > attached a sample scenario below. Your help is appreciated. Thanks. > > > > Anson > > > > <aspx_page> > > > > <%@ Page Language="C#" AutoEventWireup="false" > > CodeFile="TestMultipleEventsOnPostback.aspx.cs" > > Inherits="TestMultipleEventsOnPostback" enableviewstate="false" %> > > > > <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" > > "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> > > > > <html xmlns="http://www.w3.org/1999/xhtml" > > > <head runat="server"> > > <title>DropDownList SelectedIndexChanged EnableViewState="False" > > Example</title> > > </head> > > <body> > > <form id="form1" runat="server"> > > <div> > > <asp:label id="summary" runat="server"></asp:label> > > <br /> > > <br /> > > <table width="100%"> > > <tr> > > <td>List #1</td> > > <td>List #2</td> > > <td>Repeater</td> > > </tr> > > <tr> > > <td><asp:dropdownlist id="list1" runat="server" > > autopostback="true"></asp:dropdownlist></td> > > <td><asp:dropdownlist id="list2" runat="server" > > autopostback="true"></asp:dropdownlist></td> > > <td> > > <table> > > <asp:repeater id="repeat" runat="server"> > > <itemtemplate> > > <tr> > > <td><asp:label id="itemTemplateLabel" > > runat="server"></asp:label></td> > > <td><asp:dropdownlist id="itemTemplateList" > > runat="server" autopostback="true"></asp:dropdownlist></td> > > </tr> > > </itemtemplate> > > </asp:repeater> > > </table> > > </td> > > </tr> > > </table> > > > > </div> > > </form> > > </body> > > </html> > > > > </aspx_page> > > > > <aspx_cs_codebehind> > > > > using System; > > using System.Collections.Generic; > > using System.Data; > > using System.Configuration; > > using System.Collections; > > using System.Web; > > using System.Web.Security; > > using System.Web.UI; > > using System.Web.UI.WebControls; > > using System.Web.UI.WebControls.WebParts; > > using System.Web.UI.HtmlControls; > > > > public partial class TestMultipleEventsOnPostback : System.Web.UI.Page > > { > > //items for list #1 > > private Dictionary<string, string> _list1Items = new Dictionary<string, > > string>(); > > //items for list #2 > > private Dictionary<string, string> _list2Items = new Dictionary<string, > > string>(); > > //items for repeater drop down > > private Dictionary<string, string> _repeaterListItems = new > > Dictionary<string, string>(); > > //repeater source just to get 5 items created > > private object[] _repeaterSource = new object[5]; > > public TestMultipleEventsOnPostback() > > { > > //create the list 1 items > > for (int i = 0; i <= 10; i++) > > { > > this._list1Items.Add(i.ToString(), string.Format("Value = {0}", > > i)); > > } > > //create the list 2 items > > for (int i = 0; i <= 40; i++) > > { > > this._list2Items.Add(i.ToString(), string.Format("Value = {0}", > > i)); > > } > > //create the list repeater items > > for (int i = 0; i <= 60; i++) > > { > > this._repeaterListItems.Add(i.ToString(), string.Format("Value = > > {0}", i)); > > } > > } > > protected override void OnPreInit(EventArgs e) > > { > > //populate the drop down lists and rebuild the repeater > > this.list1.DataTextField = "Value"; > > this.list1.DataValueField = "Key"; > > this.list1.DataSource = this._list1Items; > > this.list1.DataBind(); > > > > this.list2.DataTextField = "Value"; > > this.list2.DataValueField = "Key"; > > this.list2.DataSource = this._list2Items; > > this.list2.DataBind(); > > > > //rebuild the repeater control > > this.repeat.ItemDataBound += new > > RepeaterItemEventHandler(repeat_ItemDataBound); > > this.repeat.DataSource = this._repeaterSource; > > this.repeat.DataBind(); > > > > base.OnPreInit(e); > > } > > protected override void OnLoad(EventArgs e) > > { > > if (!this.IsPostBack) > > { > > this.summary.Text = "No postback"; > > } > > else > > { > > //clear out the summary label > > this.summary.Text = "<b>Event's that fired for current postback > > (Note: I would only expect 1 because all the dropdowns have > > autopostback=\"true\")</b>"; > > } > > //set up the event handlers > > this.list1.SelectedIndexChanged += new > > EventHandler(list1_SelectedIndexChanged); > > this.list2.SelectedIndexChanged += new > > EventHandler(list2_SelectedIndexChanged); > > //set up the event for the drop down in the repeater > > foreach (RepeaterItem item in this.repeat.Items) > > { > > DropDownList ddl = > > (DropDownList)item.FindControl("itemTemplateList"); > > ddl.SelectedIndexChanged += new > > EventHandler(itemTemplateList_SelectedIndexChanged); > > } > > base.OnLoad(e); > > } > > > > protected void itemTemplateList_SelectedIndexChanged(object sender, > > EventArgs e) > > { > > this.summary.Text += "<br/>itemTemplateList_SelectedIndexChanged"; > > } > > > > protected void list2_SelectedIndexChanged(object sender, EventArgs e) > > { > > this.summary.Text += "<br/>list2_SelectedIndexChanged"; > > } > > > > protected void list1_SelectedIndexChanged(object sender, EventArgs e) > > { > > this.summary.Text += "<br/>list1_SelectedIndexChanged"; > > } > > > > protected void repeat_ItemDataBound(object sender, RepeaterItemEventArgs > > e) > > { > > DropDownList ddl = > > (DropDownList)e.Item.FindControl("itemTemplateList"); > > ddl.DataTextField = "Value"; > > ddl.DataValueField = "Key"; > > ddl.DataSource = this._repeaterListItems; > > ddl.DataBind(); > > } > > } > > > > </aspx_cs_codebehind> > > Hello all,
I've found the solution ... please excuse my sarcasm. Spending this much time on the problem and finding out it's an issue that MS just isn't supporting in their own controls is more than just a litle frustrating. I finally fired up Lutz (.NET Reflector) and said to myself, hmm, let's see what the behavior is for the DropDownList.SaveControlState method. I paste protected internal virtual object SaveControlState() { return null; } Seeing this, it became obvious what the problem was ... MS didn't implement control state in the DropDownList ... but did decide to implement it in other controls like the GridView. I like to refer to this as the InvalidInternException (originally coined when we tried to serialize an Oracle exception from the System.Data.Oracle namespace in the 1.0 framework). So, I created (again, please look past sarcasm for the actual value) ProperDropDownList. Here's the code <properdropdownlist_code> using System; using System.Web.UI.WebControls; namespace TestConcepts { /// <summary> /// Summary description for ProperDropDownList /// </summary> public class ProperDropDownList : DropDownList { public ProperDropDownList() : base() { } //override the control state methods to see if can get the selected index //set prior to the post back happening protected override void OnInit(EventArgs e) { Page.RegisterRequiresControlState(this); base.OnInit(e); } protected override void LoadControlState(object savedState) { object[] controlState = (object[])savedState; base.LoadControlState(controlState[0]); base.SelectedIndex = (int)controlState[1]; } protected override object SaveControlState() { object[] controlState = new object[2]; controlState[0] = base.SaveControlState(); controlState[1] = base.SelectedIndex; return controlState; } } } </propertdropdonlistcode> Then I added the following directive to the page. <%@ register tagprefix="wtf" namespace="TestConcepts" %> Then I did a find and replace in the aspx page on asp:dropdownlist and replaced with wtf:properdropdownlist. Run page and success. To illustrate the value of this, turn on viewstate and look at the page size and then turn off viewstate and compare. Let me know your thoughts and if you see any problems with this. Personally, I believe this behavior should have been implemented in ListControl so that we can derive the benefits of the Control State model without having to subclass the dropdown list. </rant> Anson Goldade Show quoteHide quote "Phillip Williams" wrote: > Hi Anson, > > I put your code on my site to illustrate to you what is happening: > http://www.webswapp.com/codesamples/testMultipleEventsOnPostBack.aspx > > The problem here is that when you set the EnableViewState to false, you have > reconstructed the dropdownlists on every postback. After reconstructing > them, (at the end of the OnPreInit), the selectedindex is always =0. > Therefore if you had changed the first dropdownlist (which fired the first > event) then changed the second dropdownlist, the event of the first would > still be fired because as far as the page is concerned you have changed both > dropdownlist from the time you reconstructed them. > > Your problem disappeared when you set the EnableViewState to true because > after the OnPreInit the state of the first dropdownlist (from previous > postback) changed the selectedIndex of your list and therefore the > dropdownlist did not sense a change in the selectedindex. > > -- > HTH, > Phillip Williams > http://www.societopia.net > http://www.webswapp.com > > > "Anson Goldade" wrote: > > > I'm trying to leverage the viewstate optimization capabilities in ASP.NET 2.0 > > but have run into an issue with the DropDownList. > > > > Problem statement > > SelectedIndexChanged events are getting fired for every dropdown since the > > page was first rendered. Here's a sequence example. > > 1. Load page > > 2. Change value in ddl #1 ... causes postback. SelectedIndexChanged fired > > for ddl #1. > > 3. Change value in ddl #2 ... causes postback. SelectedIndexChanged fired > > for ddl #1 and ddl #2. This is the unexpected behavior. Would only expect > > SelectedIndexChanged to be fired for ddl #2 > > > > Assumptions > > 1. Multiple drop down lists on page > > 2. Autopostback = true > > 3. EnableViewState on page = false > > 4. Each ddl has it's own selectedindexchanged event > > 5. Lists in the ddl's are being rebuilt by overriding OnPreInit on page. > > Also tried populating lists in OnInit and OnInitComplete ... same result > > 6. Subscribe to SelectedIndexChanged event in OnLoad. Also tried setting > > onselectedindexchanged properties of ddl in aspx ... same result. > > > > I'd like to know what I need to do in order to get only the > > SelectedIndexChanged event to fire for the control that actually changed. If > > I set EnableViewState=true, the expected behavior is exhibited. I've > > attached a sample scenario below. Your help is appreciated. Thanks. > > > > Anson > > > > <aspx_page> > > > > <%@ Page Language="C#" AutoEventWireup="false" > > CodeFile="TestMultipleEventsOnPostback.aspx.cs" > > Inherits="TestMultipleEventsOnPostback" enableviewstate="false" %> > > > > <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" > > "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> > > > > <html xmlns="http://www.w3.org/1999/xhtml" > > > <head runat="server"> > > <title>DropDownList SelectedIndexChanged EnableViewState="False" > > Example</title> > > </head> > > <body> > > <form id="form1" runat="server"> > > <div> > > <asp:label id="summary" runat="server"></asp:label> > > <br /> > > <br /> > > <table width="100%"> > > <tr> > > <td>List #1</td> > > <td>List #2</td> > > <td>Repeater</td> > > </tr> > > <tr> > > <td><asp:dropdownlist id="list1" runat="server" > > autopostback="true"></asp:dropdownlist></td> > > <td><asp:dropdownlist id="list2" runat="server" > > autopostback="true"></asp:dropdownlist></td> > > <td> > > <table> > > <asp:repeater id="repeat" runat="server"> > > <itemtemplate> > > <tr> > > <td><asp:label id="itemTemplateLabel" > > runat="server"></asp:label></td> > > <td><asp:dropdownlist id="itemTemplateList" > > runat="server" autopostback="true"></asp:dropdownlist></td> > > </tr> > > </itemtemplate> > > </asp:repeater> > > </table> > > </td> > > </tr> > > </table> > > > > </div> > > </form> > > </body> > > </html> > > > > </aspx_page> > > > > <aspx_cs_codebehind> > > > > using System; > > using System.Collections.Generic; > > using System.Data; > > using System.Configuration; > > using System.Collections; > > using System.Web; > > using System.Web.Security; > > using System.Web.UI; > > using System.Web.UI.WebControls; > > using System.Web.UI.WebControls.WebParts; > > using System.Web.UI.HtmlControls; > > > > public partial class TestMultipleEventsOnPostback : System.Web.UI.Page > > { > > //items for list #1 > > private Dictionary<string, string> _list1Items = new Dictionary<string, > > string>(); > > //items for list #2 > > private Dictionary<string, string> _list2Items = new Dictionary<string, > > string>(); > > //items for repeater drop down > > private Dictionary<string, string> _repeaterListItems = new > > Dictionary<string, string>(); > > //repeater source just to get 5 items created > > private object[] _repeaterSource = new object[5]; > > public TestMultipleEventsOnPostback() > > { > > //create the list 1 items > > for (int i = 0; i <= 10; i++) > > { > > this._list1Items.Add(i.ToString(), string.Format("Value = {0}", > > i)); > > } > > //create the list 2 items > > for (int i = 0; i <= 40; i++) > > { > > this._list2Items.Add(i.ToString(), string.Format("Value = {0}", > > i)); > > } > > //create the list repeater items > > for (int i = 0; i <= 60; i++) > > { > > this._repeaterListItems.Add(i.ToString(), string.Format("Value = > > {0}", i)); > > } > > } > > protected override void OnPreInit(EventArgs e) > > { > > //populate the drop down lists and rebuild the repeater > > this.list1.DataTextField = "Value"; > > this.list1.DataValueField = "Key"; > > this.list1.DataSource = this._list1Items; > > this.list1.DataBind(); > > > > this.list2.DataTextField = "Value"; > > this.list2.DataValueField = "Key"; > > this.list2.DataSource = this._list2Items; > > this.list2.DataBind(); > > > > //rebuild the repeater control > > this.repeat.ItemDataBound += new > > RepeaterItemEventHandler(repeat_ItemDataBound); > > this.repeat.DataSource = this._repeaterSource; > > this.repeat.DataBind(); > > > > base.OnPreInit(e); > > } > > protected override void OnLoad(EventArgs e) > > { > > if (!this.IsPostBack) > > { > > this.summary.Text = "No postback"; > > } > > else > > { > > //clear out the summary label > > this.summary.Text = "<b>Event's that fired for current postback > > (Note: I would only expect 1 because all the dropdowns have > > autopostback=\"true\")</b>"; > > } > > //set up the event handlers > > this.list1.SelectedIndexChanged += new > > EventHandler(list1_SelectedIndexChanged); > > this.list2.SelectedIndexChanged += new > > EventHandler(list2_SelectedIndexChanged); > > //set up the event for the drop down in the repeater > > foreach (RepeaterItem item in this.repeat.Items) > > { > > DropDownList ddl = > > (DropDownList)item.FindControl("itemTemplateList"); > > ddl.SelectedIndexChanged += new > > EventHandler(itemTemplateList_SelectedIndexChanged); > > } > > base.OnLoad(e); > > } > > > > protected void itemTemplateList_SelectedIndexChanged(object sender, > > EventArgs e) > > { > > this.summary.Text += "<br/>itemTemplateList_SelectedIndexChanged"; > > } > > > > protected void list2_SelectedIndexChanged(object sender, EventArgs e) > > { > > this.summary.Text += "<br/>list2_SelectedIndexChanged"; > > } > > > > protected void list1_SelectedIndexChanged(object sender, EventArgs e) > > { > > this.summary.Text += "<br/>list1_SelectedIndexChanged"; > > } > > > > protected void repeat_ItemDataBound(object sender, RepeaterItemEventArgs > > e) > > { > > DropDownList ddl = > > (DropDownList)e.Item.FindControl("itemTemplateList"); > > ddl.DataTextField = "Value"; > > ddl.DataValueField = "Key"; > > ddl.DataSource = this._repeaterListItems; > > ddl.DataBind(); > > } > > } > > > > </aspx_cs_codebehind> > > Anson,
This is excellent. I just started down the road of extending the dropdownlist. Then I went into a meeting before doing much about it. Now I read your solution and I think it is a great analysis and solution. Show quoteHide quote "Anson Goldade" wrote: > Hello all, > > I've found the solution ... please excuse my sarcasm. Spending this much > time on the problem and finding out it's an issue that MS just isn't > supporting in their own controls is more than just a litle frustrating. > > I finally fired up Lutz (.NET Reflector) and said to myself, hmm, let's see > what the behavior is for the DropDownList.SaveControlState method. I paste > > protected internal virtual object SaveControlState() > { > return null; > } > > Seeing this, it became obvious what the problem was ... MS didn't implement > control state in the DropDownList ... but did decide to implement it in other > controls like the GridView. I like to refer to this as the > InvalidInternException (originally coined when we tried to serialize an > Oracle exception from the System.Data.Oracle namespace in the 1.0 framework). > > So, I created (again, please look past sarcasm for the actual value) > ProperDropDownList. Here's the code > > <properdropdownlist_code> > using System; > using System.Web.UI.WebControls; > > namespace TestConcepts > { > /// <summary> > /// Summary description for ProperDropDownList > /// </summary> > public class ProperDropDownList : DropDownList > { > public ProperDropDownList() : base() { } > > //override the control state methods to see if can get the selected > index > //set prior to the post back happening > protected override void OnInit(EventArgs e) > { > Page.RegisterRequiresControlState(this); > base.OnInit(e); > } > protected override void LoadControlState(object savedState) > { > object[] controlState = (object[])savedState; > base.LoadControlState(controlState[0]); > base.SelectedIndex = (int)controlState[1]; > } > protected override object SaveControlState() > { > object[] controlState = new object[2]; > controlState[0] = base.SaveControlState(); > controlState[1] = base.SelectedIndex; > return controlState; > } > } > } > </propertdropdonlistcode> > > Then I added the following directive to the page. > <%@ register tagprefix="wtf" namespace="TestConcepts" %> > > Then I did a find and replace in the aspx page on asp:dropdownlist and > replaced with wtf:properdropdownlist. Run page and success. To illustrate > the value of this, turn on viewstate and look at the page size and then turn > off viewstate and compare. Let me know your thoughts and if you see any > problems with this. Personally, I believe this behavior should have been > implemented in ListControl so that we can derive the benefits of the Control > State model without having to subclass the dropdown list. > > </rant> > > Anson Goldade > > "Phillip Williams" wrote: > > > Hi Anson, > > > > I put your code on my site to illustrate to you what is happening: > > http://www.webswapp.com/codesamples/testMultipleEventsOnPostBack.aspx > > > > The problem here is that when you set the EnableViewState to false, you have > > reconstructed the dropdownlists on every postback. After reconstructing > > them, (at the end of the OnPreInit), the selectedindex is always =0. > > Therefore if you had changed the first dropdownlist (which fired the first > > event) then changed the second dropdownlist, the event of the first would > > still be fired because as far as the page is concerned you have changed both > > dropdownlist from the time you reconstructed them. > > > > Your problem disappeared when you set the EnableViewState to true because > > after the OnPreInit the state of the first dropdownlist (from previous > > postback) changed the selectedIndex of your list and therefore the > > dropdownlist did not sense a change in the selectedindex. > > > > -- > > HTH, > > Phillip Williams > > http://www.societopia.net > > http://www.webswapp.com > > > > > > "Anson Goldade" wrote: > > > > > I'm trying to leverage the viewstate optimization capabilities in ASP.NET 2.0 > > > but have run into an issue with the DropDownList. > > > > > > Problem statement > > > SelectedIndexChanged events are getting fired for every dropdown since the > > > page was first rendered. Here's a sequence example. > > > 1. Load page > > > 2. Change value in ddl #1 ... causes postback. SelectedIndexChanged fired > > > for ddl #1. > > > 3. Change value in ddl #2 ... causes postback. SelectedIndexChanged fired > > > for ddl #1 and ddl #2. This is the unexpected behavior. Would only expect > > > SelectedIndexChanged to be fired for ddl #2 > > > > > > Assumptions > > > 1. Multiple drop down lists on page > > > 2. Autopostback = true > > > 3. EnableViewState on page = false > > > 4. Each ddl has it's own selectedindexchanged event > > > 5. Lists in the ddl's are being rebuilt by overriding OnPreInit on page. > > > Also tried populating lists in OnInit and OnInitComplete ... same result > > > 6. Subscribe to SelectedIndexChanged event in OnLoad. Also tried setting > > > onselectedindexchanged properties of ddl in aspx ... same result. > > > > > > I'd like to know what I need to do in order to get only the > > > SelectedIndexChanged event to fire for the control that actually changed. If > > > I set EnableViewState=true, the expected behavior is exhibited. I've > > > attached a sample scenario below. Your help is appreciated. Thanks. > > > > > > Anson > > > > > > <aspx_page> > > > > > > <%@ Page Language="C#" AutoEventWireup="false" > > > CodeFile="TestMultipleEventsOnPostback.aspx.cs" > > > Inherits="TestMultipleEventsOnPostback" enableviewstate="false" %> > > > > > > <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" > > > "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> > > > > > > <html xmlns="http://www.w3.org/1999/xhtml" > > > > <head runat="server"> > > > <title>DropDownList SelectedIndexChanged EnableViewState="False" > > > Example</title> > > > </head> > > > <body> > > > <form id="form1" runat="server"> > > > <div> > > > <asp:label id="summary" runat="server"></asp:label> > > > <br /> > > > <br /> > > > <table width="100%"> > > > <tr> > > > <td>List #1</td> > > > <td>List #2</td> > > > <td>Repeater</td> > > > </tr> > > > <tr> > > > <td><asp:dropdownlist id="list1" runat="server" > > > autopostback="true"></asp:dropdownlist></td> > > > <td><asp:dropdownlist id="list2" runat="server" > > > autopostback="true"></asp:dropdownlist></td> > > > <td> > > > <table> > > > <asp:repeater id="repeat" runat="server"> > > > <itemtemplate> > > > <tr> > > > <td><asp:label id="itemTemplateLabel" > > > runat="server"></asp:label></td> > > > <td><asp:dropdownlist id="itemTemplateList" > > > runat="server" autopostback="true"></asp:dropdownlist></td> > > > </tr> > > > </itemtemplate> > > > </asp:repeater> > > > </table> > > > </td> > > > </tr> > > > </table> > > > > > > </div> > > > </form> > > > </body> > > > </html> > > > > > > </aspx_page> > > > > > > <aspx_cs_codebehind> > > > > > > using System; > > > using System.Collections.Generic; > > > using System.Data; > > > using System.Configuration; > > > using System.Collections; > > > using System.Web; > > > using System.Web.Security; > > > using System.Web.UI; > > > using System.Web.UI.WebControls; > > > using System.Web.UI.WebControls.WebParts; > > > using System.Web.UI.HtmlControls; > > > > > > public partial class TestMultipleEventsOnPostback : System.Web.UI.Page > > > { > > > //items for list #1 > > > private Dictionary<string, string> _list1Items = new Dictionary<string, > > > string>(); > > > //items for list #2 > > > private Dictionary<string, string> _list2Items = new Dictionary<string, > > > string>(); > > > //items for repeater drop down > > > private Dictionary<string, string> _repeaterListItems = new > > > Dictionary<string, string>(); > > > //repeater source just to get 5 items created > > > private object[] _repeaterSource = new object[5]; > > > public TestMultipleEventsOnPostback() > > > { > > > //create the list 1 items > > > for (int i = 0; i <= 10; i++) > > > { > > > this._list1Items.Add(i.ToString(), string.Format("Value = {0}", > > > i)); > > > } > > > //create the list 2 items > > > for (int i = 0; i <= 40; i++) > > > { > > > this._list2Items.Add(i.ToString(), string.Format("Value = {0}", > > > i)); > > > } > > > //create the list repeater items > > > for (int i = 0; i <= 60; i++) > > > { > > > this._repeaterListItems.Add(i.ToString(), string.Format("Value = > > > {0}", i)); > > > } > > > } > > > protected override void OnPreInit(EventArgs e) > > > { > > > //populate the drop down lists and rebuild the repeater > > > this.list1.DataTextField = "Value"; > > > this.list1.DataValueField = "Key"; > > > this.list1.DataSource = this._list1Items; > > > this.list1.DataBind(); > > > > > > this.list2.DataTextField = "Value"; > > > this.list2.DataValueField = "Key"; > > > this.list2.DataSource = this._list2Items; > > > this.list2.DataBind(); > > > > > > //rebuild the repeater control > > > this.repeat.ItemDataBound += new > > > RepeaterItemEventHandler(repeat_ItemDataBound); > > > this.repeat.DataSource = this._repeaterSource; > > > this.repeat.DataBind(); > > > > > > base.OnPreInit(e); > > > } > > > protected override void OnLoad(EventArgs e) > > > { > > > if (!this.IsPostBack) > > > { > > > this.summary.Text = "No postback"; > > > } > > > else > > > { > > > //clear out the summary label > > > this.summary.Text = "<b>Event's that fired for current postback > > > (Note: I would only expect 1 because all the dropdowns have > > > autopostback=\"true\")</b>"; > > > } > > > //set up the event handlers > > > this.list1.SelectedIndexChanged += new > > > EventHandler(list1_SelectedIndexChanged); > > > this.list2.SelectedIndexChanged += new > > > EventHandler(list2_SelectedIndexChanged); > > > //set up the event for the drop down in the repeater > > > foreach (RepeaterItem item in this.repeat.Items) > > > { > > > DropDownList ddl = > > > (DropDownList)item.FindControl("itemTemplateList"); > > > ddl.SelectedIndexChanged += new > > > EventHandler(itemTemplateList_SelectedIndexChanged); > > > } > > > base.OnLoad(e); > > > } > > > > > > protected void itemTemplateList_SelectedIndexChanged(object sender, > > > EventArgs e) > > > { > > > this.summary.Text += "<br/>itemTemplateList_SelectedIndexChanged"; > > > } > > > > > > protected void list2_SelectedIndexChanged(object sender, EventArgs e) > > > { > > > this.summary.Text += "<br/>list2_SelectedIndexChanged"; > > > } > > > > > > protected void list1_SelectedIndexChanged(object sender, EventArgs e) |
|||||||||||||||||||||||