Controls and ViewState out of sync (?)

P

Peter Zolja

Hi,

I'm building a webcontrol that contains a dynamic list of other controls. My
problem is that when I add or remove an item the synchronization between the
ViewState and the Controls collection seems to break -- or at least that's
my theory for now.

Here's what I do; to add an item I do that following on the PostBack of a
button:
1. Create a Button object and set its properties
2. Add the object to the Controls collection
3. Increase a counter (saved in ViewState) that will allow me to rebuild the
Controls collection in CreateChildControls in future PostBacks

When I add a button and set its Text property to "something" that text is
rendered to the browser, but it's not saved in the ViewState; as a
consequence that "something" is lost if I do another PostBack (from a
different control). Removing a control that was added dynamically has
similar issues. If I have, let's say 4 controls, and I try to remove the
second, the second control is removed from the Controls collection, but the
ViewState shifts / gets corrupted(?). For example, let's say the 4 controls
look like this:

[ 1 ] [ 2 ] [ 3 ] [ 4 ]

We want to delete the second control; after removing the second control the
output is:

[ 1 ] [ 3 ] [ 4 ]

which is what we want. Unfortunately, on a subsequent PostBack, the list
becomes:

[ 1] [ ] [ 3 ]

which basically tells me that the ViewState got out of sync with the number
of control in the Controls collection.

Is my theory correct, is there a way to fix this problem? I've attached a
simple test project to exemplify my problem.


Any help / suggestion is welcome, thanks!
Peter.


----- WebForms1.aspx ---- (the relevant part)

<form id="Form1" method="post" runat="server">
<asp:panel Runat=server ID="pnlTest"></asp:panel>
<br>
<asp:Button Runat=server id="btPostBackTest" Text="Test"></asp:Button>
</form>

----- WebForms1.aspx.cs ---- (the relavant pieces)

private void WebForm1_Init(object sender, System.EventArgs e)
{
ControlList cl = new ControlList();
pnlTest.Controls.Add(cl);
}

private void btPostBackTest_Click(object sender, System.EventArgs e)
{
// do nothing... just a postback test
}

----- ControlList.cs ----- (the file that holds the ControlList class)

using System;
using System.Web.UI;
using System.Web.UI.WebControls;

[ ... ]

public class ControlList : WebControl, INamingContainer
{
public ControlList()
{
//
// TODO: Add constructor logic here
//
}

private Button _btAdd;
private Button _btDelete;

public int ItemCount
{
get
{
object count = ViewState["count"];
return ((count == null) ? 0 : Convert.ToInt32(count));
}
set
{
ViewState["count"] = value;
}
}

private TextBox NewButton(int index)
{
TextBox t = new TextBox();
Controls.Add(t);
t.ID = "tb" + index.ToString();
return t;
}

protected void OnAddClick(object sender, System.EventArgs e)
{
ItemCount++;
TextBox t = NewButton(ItemCount);
// at this stage t is already in the Controls collection, but
// the modified text value below doesn't save to the ViewState (why?)
t.Text = "something #" + ItemCount.ToString();
}

protected void OnDeleteClick(object sender, System.EventArgs e)
{
// to make it simple we always try to delete the second textbox
(id=tb1)
Control c = FindControl("tb1");
if (c != null)
{
ItemCount--;
// this seems to messup the sync btw Controls and ViewState
Controls.Remove(c);
}
}

public override ControlCollection Controls
{
get
{
EnsureChildControls();
return base.Controls;
}
}

protected override void CreateChildControls()
{
Controls.Clear();

_btAdd = new Button();
Controls.Add(_btAdd);
_btAdd.ID = "btAdd";
_btAdd.Text = "Add";
_btAdd.Click += new EventHandler(OnAddClick);

_btDelete = new Button();
Controls.Add(_btDelete);
_btDelete.ID = "btDel";
_btDelete.Text = "Delete";
_btDelete.Click += new EventHandler(OnDeleteClick);

for (int i = 0; i < ItemCount; i++)
{
NewButton(i);
}
}
}
 
S

sam

Yeah.

Viewstate is saved by control index *only* (ie. 1, 2, 3), not ID.
Microsoft did this to save space in the viewstate string. Please
google viewstate for more information.

What most ppl do is always build 4 controls and then on PreRender() set
the visibility of the ones they don't want to false. That way the
viewstate is always restored correctly.

If you want to talk more about this i'll answer.

-Sam
 
P

Peter Zolja

Viewstate is saved by control index *only* (ie. 1, 2, 3), not ID.
Microsoft did this to save space in the viewstate string. Please
google viewstate for more information.

I've read about how the ViewState and the Controls collection sync that's
what made me think that they were getting out of synch somehow. Just to be
sure: are you saying that it can't be done? (creating a dynamic number of
controls in a postback event i.e.). I guess I am hoping that there may be a
trick to do this; I mean, the PostBack occurs before the ViewState is saved,
so why not be able to alter the ViewState to be in sync with the Controls
collection, or am I missing something?
What most ppl do is always build 4 controls and then on PreRender() set
the visibility of the ones they don't want to false. That way the
viewstate is always restored correctly.

That will work for some cases, but not for everything -- cool trick
though...
Any idea if the 2.0 framework has the same issue?
If you want to talk more about this i'll answer.

Thanks for your time!
 
S

sam

You could get the viewstate of the child controls (by getting ViewState
property - it's type StateBag) and save *that* in the viewstate of the
ControlList under some unique key (child control index?). The
ControlList viewstate shouldn't get messed up becuase you are always
adding it to the page in the same place on postbacks (hopefully).

I don't think ASP.NET 2.0 solves your problem. The viewstate is twice
as small and they have a new ControlState(?) which works even if
ViewState of parent is turned off. Maybe ControlState works without
indexing the control tree. Can you find out and tell me? LOL.

-Sam
 
B

billa1972

Peter said:

The way we handled it was:

1. Always re-load the controls on postback, so viewstate doesn't break
(load in order according to step 4).

2. Conditionally handle any events that get fired once the controls
are re-loaded

3. With some logic, conditionally remove the controlls we didn't want.

4. Load in the controls as required

5. Remember (usually with viewstate), the order and type of controls.
1. we use a hashtable with Key = position and string = control
type

Does this make sense?

thanks,

bill
 
S

sam

It makes sense, if you don't want to do the visibility trick.

Sorry, but I forgot to mention. Maybe you already know this. If you
set viewstate to false in the parent control it will be turned off in
all child controls so you won't get that error message again, no matter
what you do with the child controls. I thought this was what you were
going to do.

Of course, you do have to load all the controls if you want to catch
their events. Then you either remove them or set visibility to false.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top