CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Peter's Gekko

public Blog MyNotepad : Imho { }

Adding controls at runtime, the viewstate and the page lifecycle

In yesterday's post I blogged  about problems with the viewstate after adding a control at runtime. A great thing with blogging are the responses, one by Martijn Boland inspired me to a more systematic investigation of the problem.

As a test bed I take a simple web form. It has one label, one textbox, a button, a checkbox and a panel. On creation I will add another textbox and another label from code. These controls are declared as privates in the code behind and they do not appear in the html of the aspx page. This dynamic addition is done twice, once in the OnInit event and once in the Page_Load.

private Label Label2;
private Label Label3;

private TextBox TextBox2;
private TextBox TextBox3;

#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
    Label2 = new Label();
    this.Controls.Add(Label2);
    TextBox2 = new TextBox();
    Panel1.Controls.Add(TextBox2);

    InitializeComponent();
    base.OnInit(e);
}

private void InitializeComponent()
{
....
}
#endregion

private void Page_Load(object sender, System.EventArgs e)
{
    Label3 = new Label();
    this.Controls.Add(Label3);
    TextBox3 = new TextBox();
    Panel1.Controls.Add(TextBox3);
}


Trying to add a textbox to the controls collection of the page itself will result in an error :  Control '_ctl1' of type 'TextBox' must be placed inside a form tag with runat=server. Textboxes need a container control. Adding them to an existing panel will do.
Whether the viewstate of the dynamically created textboxes is working is easy to see. To test the viewstate of the labels I use the checkbox to conditionally update their text. This is done in the PreRender event.

private void WebForm1_PreRender(object sender, System.EventArgs e)
{
    if (CheckBox1.Checked)
    {
        Label1.Text = TextBox1.Text;
        Label2.Text = TextBox2.Text;
        Label3.Text = TextBox3.Text;
    }
}

What the demo will show is that the viewstate of all three control pairs, created in the designer, in the onInit and in the page_load does work. The contents and caption are preserved over roundtrips. This is actually what I did expect from the viewstate. As it has such a nice programming interface I expected the same flexible behaviour when adding controls. Nevertheless I ran into trouble with the viewstate when adding a literal control in the base webform of my app. The behaviour of the app became quite erratic and all debugging pointed to a wrecked viewstate. Martijn pointed me to the fact that it matters at which moment in the page lifecycle you add your control.  I had done it in the page_load, moving the code to the OnInt fixed this. A closer look at the page life cycle made clear why this can make a difference.

The life cycle of a page goes through these steps

  1. Construct page
  2. Initialize (OnInit)
  3. Begin tracking Viewstate
  4. Load (Page_load)
  5. Prerender (OnPreRender)
  6. Save View state
  7. Render
  8. UnLoad (OnUnload)

You can hook in your own code using Onxxx events. To hook into other events you have to override the corresponding method. As you see the tracking of changes starts after the OnInit event. Anything done after that will interfere with maintaining the state. And can (as in my app) but will not always (as in the demo presented) wreck the state.

Peter

 



Comments

Peter van Ooijen said:

When and how you the declare your controls is up to you. My only point is the moment the dynamically created control is inserted into the control tree.
# February 22, 2004 1:23 PM

Jason said:

I have the same question as Mindy.
In your example you already know how many textboxes and labels you wanted to add dynamically and so you could easily declare

private TextBox TextBox2;
private TextBox TextBox3;

I am developing an application in which I do not know how many textboxes, labels and Dropdownlists I need before-hand. I retreive certain records from the database and if i retrieve N records I create N textboxes, labels and Dropdownlists. So far so good,

The problem lies in the fact that I cannot add this information in the OnInit Function (because I simply do not know how many controls I will need).

When the user selects values from N dynamically
dropdownlists and textboxes, my submit_click event needs to get these selected values.

I would appreciate any help in this regard,
-Jason-




# March 26, 2004 5:20 PM

Peter's Gekko said:

Why Page_load ?
# March 28, 2004 7:19 AM

Peter van Ooijen said:

Jason,

I would use the datalist to get your thing done. There is an itemtemplate for every row in your db.

Personnaly I add as many controls as possible at design time. When the control is not needed I can set its visible property to false. Invisible controls are not rendered to the response, they have no overhead for the client. The visible property is bindable, so you can set it from code depending on anything you can imagine.

The items in the list can be read from code. In case you want to read nore check some of my latest articles on the dnj.

http://www.dotnetjunkies.com/Tutorial/4D57511C-653C-406C-B5ED-B364640DAF2F.dcik
Peter
# March 28, 2004 12:18 PM

DLARLICK said:


Hmm,

I liked the example but have a slight variation.

I am attempting a menu, witch works fine (dynamic by role of user) Loaded at OnInit. However a menu selection runs a delegate. This delegate would then attempt to load the appropriate control into the body of the aspx form.

The delegate runs after the page load and attempts to load the appropriate control. At this time the app does not maintain viewstate for the dynamically generated control.

The only option that I have figured to work is to use a querystring and parse in the OnInit or PageLoad. Using a querystring is undesireable due to user ability to request controls through the URL.

I have played with using the ViewState explicitly, but am missing something that is done by default when instantiated in the appropriate startup method.

I would appreciate any comment.

Thanks!


# March 29, 2004 12:24 PM

Peter's Gekko said:

Adding a control a runtime : what is not in the viewstate
# March 29, 2004 7:15 PM

Alicia said:

Hi!

I also have a small variation of the problem - I read from the database how many controls I need, but I have to provide the user with the ability to change the number at runtime. So imagine this: I have a page, with 2 distinct areas (one is the general info, one is the number of levels). Let's say initially I have 2 levels, that's 2 sets of controls (3 textBoxes per level). Now the user say 4 instead of 2 - I need to refresh the page and have 2 more sets of controls added. How do I do this so I won't loose the data in the first area (which might have been already modified by the user)?

I tried to add some functionality in the onTextChanged method, but adding the controls don't appear on the screan... How do I force the page to "repaint" that area?

I would appreciate any ideas - Thanks!
Alicia
# May 7, 2004 1:42 PM

Peter van Ooijen said:

hi Alicia,
the ontextchanged event is far to late to add anything on the same roundtrip.

What I would do is create all controls at designtime, perhaps in a list or repeater, and set the visibility of the controls in a databinding expression.

Does that help ?
Peter
# May 10, 2004 6:40 AM

Alicia said:

Hi Peter,

Yes, I understand that the event is way to late, my problem is I have no idea how many controls to define in advance! Other than set a maximum (which is good enough probbaly in this case), which cannot be changed other than re-deploying the application...

My question was if it's possible to somehow re-post only a part of the page - I would not use the ontextchanged event, but a button... but I don't want to re-post the whole page (I don't want to loose the other settings the user might already have changed).

I can workaround with setting some rules for the end user, but I'd like to know if something like that it's possible.

TIA,
Alicia
# May 10, 2004 9:07 AM

Peter van Ooijen said:

Hi,

You cannot postback a part of the page yet, you can in .NET 2. There's a good article on that on the junkies : http://www.dotnetjunkies.com/Tutorial/E80EC96F-1C32-4855-85AE-9E30EECF13D7.dcik

I would go for a datlist or repeater. Never mind the full postback, the viewstate should take care of all settings allready entered.
# May 10, 2004 9:39 AM

Alicia said:

I'll use a repeater then :-)

Thanks,
Alicia
# May 10, 2004 10:22 AM

xxxxxxx said:


Can find users data entry before init if you have registered post back or not.
Just use Page.Request.Form

// Page.Request.Form
// [3] "_ctl0:_ctl0:_ctl0:_ctl1:_ctl6:_ctl0"
// [4] "_ctl0:_ctl0:_ctl1:_ctl1"
// [5] "_ctl0:_ctl0:_ctl2:_ctl1"
//
// ID "_ctl0:_ctl1"
// ClientID "_ctl0__ctl0__ctl1"
// UniqueID "_ctl0:_ctl0:_ctl1"
//
// Looking for "_ctl0:_ctl0:_ctl1:_ctl1" this control plus the control id withing this control

[Conditional("DEBUG")]
public void IterateThroughForm()
{
String m_strlastErr = String.Empty;
ArrayList indices = null;
ArrayList state = null;

try
{
if ((this.Page.Request.Form == null) || (this.Page.Request.Form.Count <= 0))
return;

indices = new ArrayList();
state = new ArrayList();

// Only occurs if control registered as post back will have __EVENTTARGET
string t_strTarget = Page.Request.Form["__EVENTTARGET"];
string t_strValue = Page.Request.Form["__EVENTARGUMENT"];

// [0] "__EVENTTARGET" _ctl0:_ctl0:_ctl1

foreach (string cName in Page.Request.Form.Keys)
{
if ((cName == null) || (cName.Length <= 0))
continue; // cName "_ctl0:_ctl0:_ctl0:_ctl1"

if ( cName.IndexOf("__VIEWSTATE") != -1 )
continue;

if (Page.Request.Form[cName] == null)
continue;

m_strlastErr += String.Format(" Key {0} Values " , cName);

String [] l_astrValues = Page.Request.Form.GetValues(cName);

if ((l_astrValues == null) || (l_astrValues.GetLength(0) <= 0))
continue;

foreach (String cValue in l_astrValues)
{
if ( ( cValue == null ) || (cValue.Length <= 0))
continue;

m_strlastErr += String.Format(" {0} " , cValue);

}
}
}
catch (Exception ex)
{
m_strlastErr = ex.Message;
}

HttpContext.Current.Trace.Write("Info", m_strlastErr);
} // IterateThroughForm


[Conditional("DEBUG")]
public void IterateThroughPostBack(NameValueCollection postCollection)
{
String m_strlastErr = String.Empty;

try
{
if ( (postCollection == null) || (postCollection.Count <= 0))
return;

foreach (String cName in postCollection.Keys)
{
if ( ( cName == null ) || (cName.Length <= 0))
continue;
if ( ( cName.IndexOf("__EVENTTARGET") != -1 ) ||
( cName.IndexOf("__EVENTARGUMENT") != -1 ) ||
( cName.IndexOf("__VIEWSTATE") != -1 ) )
continue;

// [0] "__EVENTTARGET" "" nothing
// [1] "__EVENTARGUMENT" "" nothing
// [2] "__VIEWSTATE" "kdlfjgkldsjgriogjbkjk" lot of encrypted stuff
//
// "_ctl0:_ctl0:_ctl1:_ctl1" "3333" - new value in control
// "_ctl0:_ctl0:_ctl2:_ctl1" "extered text"

m_strlastErr += String.Format(" Key {0} Values " , cName);

String [] l_astrValues = postCollection.GetValues(cName);

if ((l_astrValues == null) || (l_astrValues.GetLength(0) <= 0))
continue;

foreach (String cValue in l_astrValues)
{
if ( ( cValue == null ) || (cValue.Length <= 0))
continue;

m_strlastErr += String.Format(" {0} " , cValue);

}
}
}
catch (Exception ex)
{
m_strlastErr = ex.Message;
}

HttpContext.Current.Trace.Write("Info", m_strlastErr);
} // IterateThroughPostBack

# July 14, 2004 9:12 AM

Peter van Ooijen said:

mmm, could you provide some comments ?
# July 14, 2004 10:14 AM

KaaN said:

Hi xxxxxxxxxxx,

I believe, that you are trying to help us, but I didn't understand what you do there. Can you please give some explanation?

KaaN
# September 30, 2004 10:04 AM

Chuttad said:

Bhenchod
# November 21, 2004 11:53 PM

Chuttad said:

Bhenchod
# November 21, 2004 11:53 PM

Peter van Ooijen said:

I see two problems
- You are adding the controls at a pretty late moment in the page's lifecycle.
- Controls addded to the templates of a datalist will never appear in the main Controls collection of the Page.

In your case I would consider adding the controls at design time and setting its visibility using some (fancy ?) binding expression.
# January 31, 2005 7:27 AM

Peter's Gekko said:

In several earlier post on adding controls at runtime and the potential impact on the viewstate I could...
# July 6, 2006 4:35 PM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add
Check out Devlicio.us!

This Blog

Syndication

News