views:

5269

answers:

4

I did follow the article TRULLY Understanding ViewState (great article btw) and populating my drop down list is working great. I've even setup a OnSelectedIndexChange event which fires almost as great.

The problem I've found is the SelectedIndexChanged event won't fire when selecting the 0th index. It does all other times however.

Here's some code:

<asp:DropDownList runat="server" ID="DropDownList1" EnableViewState="false" 
AutoPostBack="True" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" />


protected override void OnInit(EventArgs e)
{
    this.DropDownList1.DataTextField = "Text";
    this.DropDownList1.DataValueField = "Value";
    this.DropDownList1.DataSource = fillQueueDropDown();
    this.DropDownList1.DataBind();

    base.OnInit(e);
}

protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
    OnSelectedQueueChanged(e);
}

public void OnSelectedQueueChanged(EventArgs e)
    {
        // Do stuff.
    }

public event EventHandler queueNamesChangedEvent;
public void OnSelectedQueueChanged(EventArgs e)
    {
        if (queueNamesChangedEvent != null)
            queueNamesChangedEvent(this, e);
    }

I suppose I can do some type of check in the Page_Load method:

  if(ViewState["selectedIndexChangedFlag"] != 1)
      // raise OnSelectedChange event

Or is there something I can setup in the OnInit() method where I'm rebinding this data everytime that i can do?

See, my custom EventHander raises an event which is caught by a the parent page in which this control resides, so that the parent could take some action using the newly selected value. And this is currently working for all cases where the selected index > 0.

I create a property in this control which contains the most recently selected index, in which case my parent page can action on this property value on every Page_Load... dunno.

Open to suggestions. Or how to force this SelectedIndexChanged event to fire for that 0th index selection.

A: 

Just a random question - do you have a value assigned to the 0th index item or is it an empty string (e.g. a -Select One- option)?

Mike Robinson
Yeah, I usually have a value of some type in the 0th index.
Dave
+5  A: 

The problem is that you are loading the data each time and this is resetting the selected index. Imagine this is your dropdown:

zero [selected]
one
two

Then in the client you change the selected index:

zero
one [selected]
two

This populates the hidden input __EVENTARGUMENT with your new index (1) and the hidden input __EVENTTARGET with the id of your dropdown. Now the server-side code kicks in and reloads your data:

zero [selected]
one
two

"zero" is the selected value because that is the default when the data is loaded. Then ASP.NET looks for __EVENTTARGET and __EVENTARGUMENT in the Request and finds your dropdown's id and finds the new index (1). Now your dropdown looks like this:

zero 
one [selected]
two

Since the index has changed, the dropdown raises its SelectedIndexChanged event indicating that the index has changed. Obviously this is the part that is working, now lets see why selecting the first item in the list does not raise the event.

Now lets say that we still have the dropdown in the state it was just in (with "one" being selected and the selected index of 1). What happens when we select the first item in the list on the client?

__EVENTTARGET and __EVENTARGUMENT are populated with the id of the dropdown and the new index (0). Then the server loads the data into the dropdown and the dropdown now looks like this again:

zero [selected]
one
two

Notice that since you reloaded the data before the events fired the index is already set to 0 because that is the default. Now when your event fires and the dropdown's selected index is set to 0, the dropdown does not see this as a change since the selected index (as far as it knows) has not changed.

Here is how to fix the problem:

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);

    if (!Page.IsPostBack)
    {
     this.DropDownList1.DataTextField = "Text";
     this.DropDownList1.DataValueField = "Value";
     this.DropDownList1.DataSource = fillQueueDropDown();
     this.DropDownList1.DataBind();
    }    
}

What this will do is only load the data into the dropdown if the page is not a postback. This means that ViewState will maintain the data for you as well as the selected index so that when you post back the dropdown will compare the new index to the index you saw in the client.

Andrew Hare
A very good explanation!
Dave
+3  A: 

My goal with disabling the ViewState on this drop down list is to minimize the size of the ViewState for the page.

The problem I had with only doing the if(!Page.IsPostBack){...DataBind()...}, is that when you select an item for the first time, and the page reloads, my drop down list becomes empty.

What I ended up doing was creating another Property on this control, LastIndex. When the OnSelectedIndexChanged event fires, I update the LastIndex value. In the Page_Load, I compare the Current and Last index values, if they're different, then fire a Index changed event.

    public int SelectedValue{
        get { return this.DropDownList1.SelectedItem.Value; }
    }

    public int LastIndex{
        get { return this.ViewState["lastIndex"] == null ? -1 : (int)this.ViewState["lastIndex"]; }
        set { this.ViewState["lastIndex"] = value; }
    }

    protected override void OnInit(EventArgs e){
        base.OnInit(e);
        this.DropDownList1.DataTextField = "Text";
        this.DropDownList1.DataValueField = "Value";
        this.DropDownList1.DataSource = fillQueueDropDown();
        this.DropDownList1.DataBind();
    }

    protected void Page_Load(object sender, EventArgs e){
        if (this.LastIndex != this.SelectedValue)
            this.OnSelectedQueueChanged(new EventArgs());
    }

    private ListItemCollection fillQueueDropDown(){...}

    protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e){
        OnSelectedQueueChanged(e);
        this.LastIndex = this.SelectedValue;
    }

    public event EventHandler queueNamesChangedEvent;
    public void OnSelectedQueueChanged(EventArgs e){
        if (queueNamesChangedEvent != null)
            queueNamesChangedEvent(this, e);
    }

You are right though. The data is re-loaded and re-bound in the OnInit phase. Then the ViewState is restored (and when the 0th index is restored), when we finally get to the Events phase, the control doesn't detect the change.

Not sure this is the most elegant route, but it's working good so far.

Then i found this in the msdn docs for IPostBackDataHandler:

  public virtual bool LoadPostData(string postDataKey, 
     NameValueCollection postCollection) {

     String presentValue = Text;
     String postedValue = postCollection[postDataKey];

     if (presentValue == null || !presentValue.Equals(postedValue)) {
        Text = postedValue;
        return true;
     }

     return false;
  }

Since the present value is the same as the changed-to value, the event isn't fired.

Dave
+1 Very nice - this is an excellent way to do it without ViewState! Sorry I didn't notice you didn't want ViewState - I will read the question more carefully next time.
Andrew Hare
Thank you for your initial solution, it really helped shed some light onto the order of things. I guess i don't know the asp.net page life cycle as well as i thought.
Dave
A: 

Thank! Very thorough, and solved my problem! :) It's always nice to know WHY the ASP.NET solution works the way it does. ;)

Helgi Hrafn Gunnarsson