smooga
asked on
Using user input to affect page layout on postback
Ok, that was not the most descriptive title, but my question is kind of specific and I couldn't sum it all up in one line. Here's my problem...
I have a page which contains a number of dynamically generated datagrids. Each datagrid contains rows of data for a group of people... each row representing a person in that group. The number of groups, and hence the number of datagrids, depends on data stored in a database. So, on Page_Load, I go to the database, determine the number of groups, create the datagrids, populate them, and then add them to a placeholder object which is declared in my aspx page. This works fine. I'm running into trouble when I try to alter this data based on user input. One of the columns in the datagrid is a DropDownList which contains the group numbers of the groups on the page. So, if I have 3 group datagrids on the page, the DropDowns contain the numbers 1, 2, and 3. Each of these defaults to its group number... so, the "group 1" dropdowns have '1' selected, the "group 2" dropdowns have '2' selected, etc. Now, I want to have a user be able to move people to different groups by changing their dropdown value to a different group number and the clicking a submit button. When the page is submitted, I want to update the database for the people with altered dropdowns and then reload the page off the new database data. This, however, seems to be easier said than done. When I click submit, the original data is first loaded and THEN the button click method is called. In the button click method I am determining which dropdowns were changed and updating the database accordingly, but this doesn’t really help since the page has already rendered the old data. Furthermore, even if I could get the buttonclick method to execute first, it wouldn’t work because I need to access the datagrids to get at the dropdowns… and the datagrids aren’t created until after Page_Load is called. So I guess my questions are these:
Can I collect the changed dropdown data on the client-side, pass it to the server on submit, alter the database BEFORE Page_Load, and then render the page?
Or, is there some other preferred way of handling this type of situation?
Please help shed some light on this for me!
I have a page which contains a number of dynamically generated datagrids. Each datagrid contains rows of data for a group of people... each row representing a person in that group. The number of groups, and hence the number of datagrids, depends on data stored in a database. So, on Page_Load, I go to the database, determine the number of groups, create the datagrids, populate them, and then add them to a placeholder object which is declared in my aspx page. This works fine. I'm running into trouble when I try to alter this data based on user input. One of the columns in the datagrid is a DropDownList which contains the group numbers of the groups on the page. So, if I have 3 group datagrids on the page, the DropDowns contain the numbers 1, 2, and 3. Each of these defaults to its group number... so, the "group 1" dropdowns have '1' selected, the "group 2" dropdowns have '2' selected, etc. Now, I want to have a user be able to move people to different groups by changing their dropdown value to a different group number and the clicking a submit button. When the page is submitted, I want to update the database for the people with altered dropdowns and then reload the page off the new database data. This, however, seems to be easier said than done. When I click submit, the original data is first loaded and THEN the button click method is called. In the button click method I am determining which dropdowns were changed and updating the database accordingly, but this doesn’t really help since the page has already rendered the old data. Furthermore, even if I could get the buttonclick method to execute first, it wouldn’t work because I need to access the datagrids to get at the dropdowns… and the datagrids aren’t created until after Page_Load is called. So I guess my questions are these:
Can I collect the changed dropdown data on the client-side, pass it to the server on submit, alter the database BEFORE Page_Load, and then render the page?
Or, is there some other preferred way of handling this type of situation?
Please help shed some light on this for me!
ASKER
Well, I had put in the !IsPostBack condition (using C#), but since I'm creating all of the grids dynamically in the Page_Load method and then adding them to a placeholder, if I surround that code with "if (!IsPostBack ) {...}", the page just comes back blank after the submit. None of the grids are created and I cannot access the dropdowns (which are also not created) to determine what the user changed.
hmmm...I still think it's definitely a postback problem, did you try the code if it is postback??
could you post your code?? I can read both vb.net and C#.
Kittrick
could you post your code?? I can read both vb.net and C#.
Kittrick
ASKER
"did you try the code if it is postback"
I'm not sure what you mean. Like I said, when I have the !IsPostBack condition, the datagrids are not created on postback. Am I msunderstanding what you're asking?
Unfortunately, I cannot post my code at the moment. I'm no loger in the office, and the code that I zipped and emailed home was somehow corrupted. I can post some simple pseudocode for now though...
Page_Load ()
{
CreateGrids();
}
CreateGrids()
{
// Connect to the database and get the data
// Create the datagrids and bind to the data
// Add the datagrids to the PlaceHolder object in the .aspx page
}
OnDataBind()
{
// Create dropdownlists and add them to the datagrid
}
OnButtonClick()
{
// Access the dropdownlists in the datagrids and determine if the selectedindex has changed
// If so, update the database
}
The OnDataBind method is called for each datagrid and this is how I'm adding my dropdownlists. This works correctly.
When I surround the call to CreateGrids() with the !IsPostBack condition, the grids are only created on the initial page load. When the page loads from a submit button click, CreateGrids is never called and they are never created. This is the behavior I would expect. Am I missing something here? Should my code be laid out differently?
I'm not sure what you mean. Like I said, when I have the !IsPostBack condition, the datagrids are not created on postback. Am I msunderstanding what you're asking?
Unfortunately, I cannot post my code at the moment. I'm no loger in the office, and the code that I zipped and emailed home was somehow corrupted. I can post some simple pseudocode for now though...
Page_Load ()
{
CreateGrids();
}
CreateGrids()
{
// Connect to the database and get the data
// Create the datagrids and bind to the data
// Add the datagrids to the PlaceHolder object in the .aspx page
}
OnDataBind()
{
// Create dropdownlists and add them to the datagrid
}
OnButtonClick()
{
// Access the dropdownlists in the datagrids and determine if the selectedindex has changed
// If so, update the database
}
The OnDataBind method is called for each datagrid and this is how I'm adding my dropdownlists. This works correctly.
When I surround the call to CreateGrids() with the !IsPostBack condition, the grids are only created on the initial page load. When the page loads from a submit button click, CreateGrids is never called and they are never created. This is the behavior I would expect. Am I missing something here? Should my code be laid out differently?
My suggestion:
Move calling CreateGrids() to Page_Prerender event handler
And in OnButtonClick you can access your datagrids from PlaceHolder1.Controls.
Iterate through them and compare their values to those in the database,
if something changed, modify the database.
After OnButtonClick executes, Page_Prerender will execute
CreateGrids and recreate those datagrids.
Move calling CreateGrids() to Page_Prerender event handler
And in OnButtonClick you can access your datagrids from PlaceHolder1.Controls.
Iterate through them and compare their values to those in the database,
if something changed, modify the database.
After OnButtonClick executes, Page_Prerender will execute
CreateGrids and recreate those datagrids.
ASKER
So let me make sure I understad how ASP.NET works in this scenario...
When the page initially loads, the grids will be created for the first time. When a user clicks the button to submit, a request is made back to the server. Page_Load is called and then OnButtonClick is called. When OnButtonClick is called, it has access to the controls that were originally placed in the PlaceHolder control? How is it possible that those controls still exist on the return to the server? Are they persisted through the viewstate?
I'm headed to the office now. Will try this out first thing. Thanks
When the page initially loads, the grids will be created for the first time. When a user clicks the button to submit, a request is made back to the server. Page_Load is called and then OnButtonClick is called. When OnButtonClick is called, it has access to the controls that were originally placed in the PlaceHolder control? How is it possible that those controls still exist on the return to the server? Are they persisted through the viewstate?
I'm headed to the office now. Will try this out first thing. Thanks
yes, even dynamically created controls will be kept in viewstate.
You can try it out by creating a simple control and add it to the page,
then you can do a postback(such as click a button), you will see the control
will still be there.
You can try it out by creating a simple control and add it to the page,
then you can do a postback(such as click a button), you will see the control
will still be there.
ASKER
Ok, I've changed the code as you suggested, but it's still not working. The initial page load works fine, but when I click submit, the request is made to the server, OnButtonClick is called, it looks in the PlaceHolder control for the datagrids, but the PlaceHolder is empty. It doesn't seem to be retaining the PlaceHolder/Datagrid data in the viewstate... even though I explicitly set EnableViewState=true for the PlaceHolder and DataGrids. This is how my code is laid out now:
Page_Prerender()
{
CreateGrids();
}
CreateGrids()
{
// Connect to the database and get the data
// Create the datagrids and bind to the data
// Add the datagrids to the PlaceHolder object in the .aspx page
}
OnDataBind()
{
// Create dropdownlists and add them to the datagrid
}
OnButtonClick()
{
// Access the dropdownlists in the datagrids via the PlaceHolder control and determine if the selectedindex has changed
// If so, update the database
}
Page_Prerender()
{
CreateGrids();
}
CreateGrids()
{
// Connect to the database and get the data
// Create the datagrids and bind to the data
// Add the datagrids to the PlaceHolder object in the .aspx page
}
OnDataBind()
{
// Create dropdownlists and add them to the datagrid
}
OnButtonClick()
{
// Access the dropdownlists in the datagrids via the PlaceHolder control and determine if the selectedindex has changed
// If so, update the database
}
My fault, I made a mistake while testing.
I think dynamically generated controls are not preserved in postback.
I think dynamically generated controls are not preserved in postback.
ASKER
I must not understand viewstate and postback fully then... here's a test page:
private void Page_Load(object sender, System.EventArgs e)
{
this.EnableViewState = true;
if ( !IsPostBack ) {
myLabel = new Label();
myLabel .Text = "Testing postback!";
myPlaceholder.Controls.Add ( myLabel );
}
}
On postback the label will not be created... but my understanding of what laotzi2000 said above is that the label should still be loaded and displayed from the viewstate.
private void Page_Load(object sender, System.EventArgs e)
{
this.EnableViewState = true;
if ( !IsPostBack ) {
myLabel = new Label();
myLabel .Text = "Testing postback!";
myPlaceholder.Controls.Add
}
}
On postback the label will not be created... but my understanding of what laotzi2000 said above is that the label should still be loaded and displayed from the viewstate.
ASKER
Ahhh... I didn't see your last post before I posted mine.
Ok, so if dynamically created controls are not preserved in postback, how do we handle this situation??
Ok, so if dynamically created controls are not preserved in postback, how do we handle this situation??
I think you can write some client side script to record what changes have
been made and store it in a hidden field on the page.
On postback, read from this hidden field and compare it with the database.
been made and store it in a hidden field on the page.
On postback, read from this hidden field and compare it with the database.
smooga,
you have to understand about dynamic controls.
1. They have to be created and added to the page tree when the Page's Init method fires
#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.Load += new System.EventHandler(this.P age_Load);
this.Init += new System.EventHandler(this.P age_Init);
}
#endregion
private void Page_Init(object sender,EventArgs e)
{
/// add your controls to the page hierarchy
/// view state cannot be accessed now
}
private void Page_Load(object sender,EventArgs e)
{
/// you can access viewstate
}
you have to understand about dynamic controls.
1. They have to be created and added to the page tree when the Page's Init method fires
#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.Load += new System.EventHandler(this.P
this.Init += new System.EventHandler(this.P
}
#endregion
private void Page_Init(object sender,EventArgs e)
{
/// add your controls to the page hierarchy
/// view state cannot be accessed now
}
private void Page_Load(object sender,EventArgs e)
{
/// you can access viewstate
}
ASKER
So I'm thinking along the lines of my original question again... collect the changed dropdown values on the client-side using javascript, stash them in a hidden field, post to the server, pick up that data in OnButtonClick and update the database. Maybe not the most elegant/pure-.NET way, but it should work. Stay tuned
ASKER
I keep posting before reading the other posts...
laotzi2000, exactly what I was thinking :)
b1xml2, can you clarify something for me? What does moving that code to Page_Init achieve? Does it allow their viewstate to persist on postback?
laotzi2000, exactly what I was thinking :)
b1xml2, can you clarify something for me? What does moving that code to Page_Init achieve? Does it allow their viewstate to persist on postback?
2. the dynamic controls you add must be given fixed IDs upfront, so that there is no mixup during postbacks.
3. Now comes the million dollar question: If we depend on user interaction and viewstate in the first place to determine what controls to add, well you're stuffed.
SITUATION A
=========
1. Need to create controls immediately when the Init event fires.
2. Cannot access ViewState for user input
SITUATION B
=========
1. User input done
2. View State holds partial or all of information
SOLUTION
======
1. Move storage of information which determines the whys and hows of the dynamic control away from ViewState..
2. Alternative storage would be Session (Across pages so if the user presses control+N and the settings for MSIE is to reuse existing windows, you end up having 2 windows sharing the same session..yikes!!)
OR
3. Move storage of information to database or temp xml and set the keys inside hidden input. Request.Form collection is accessible and quite apart from ViewState.
OR
4. Store keys and write out classes that handle the control generation according to the keys..
3. Now comes the million dollar question: If we depend on user interaction and viewstate in the first place to determine what controls to add, well you're stuffed.
SITUATION A
=========
1. Need to create controls immediately when the Init event fires.
2. Cannot access ViewState for user input
SITUATION B
=========
1. User input done
2. View State holds partial or all of information
SOLUTION
======
1. Move storage of information which determines the whys and hows of the dynamic control away from ViewState..
2. Alternative storage would be Session (Across pages so if the user presses control+N and the settings for MSIE is to reuse existing windows, you end up having 2 windows sharing the same session..yikes!!)
OR
3. Move storage of information to database or temp xml and set the keys inside hidden input. Request.Form collection is accessible and quite apart from ViewState.
OR
4. Store keys and write out classes that handle the control generation according to the keys..
smooga... by adding controls inside Page_Init, it is as though they were written manually, so the viewstate will be persisted BEFORE the button's onclick event fires up...
It is a well known method, since the view state does not begin to be reconstituted until after the Init Event. If you write it in the Load Event, it will take some time to catch up whilst other events have already fired off
It is a well known method, since the view state does not begin to be reconstituted until after the Init Event. If you write it in the Load Event, it will take some time to catch up whilst other events have already fired off
it's easy to store the controls created dynamically.
What's difficult is to construct a control from the web page with user
change.
What's difficult is to construct a control from the web page with user
change.
b1xml2 is correct, though he is very clear on some point.
Dynamically created control's viewstate can be preserved if it is created in Page_Init
You can try this one:
Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init
Dim c As New TextBox
PlaceHolder1.Controls.Add( c)
End Sub
On postback, the user input in Textbox is preserved and is available on server side.
Dynamically created control's viewstate can be preserved if it is created in Page_Init
You can try this one:
Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init
Dim c As New TextBox
PlaceHolder1.Controls.Add(
End Sub
On postback, the user input in Textbox is preserved and is available on server side.
I'm still playing around with it and I found b1xml2 not correct.
But I think I have found a solution with it.
But I think I have found a solution with it.
I found out that if you create your controls dynamically in or before Page_Load event handler,
your controls will be automatically populated from data in viewstate right after Page_Load.
So if you create your dropdownlists in or before Page_Load, it will contain user input.
My suggestion:
Create Datagrid in Page_Load
Create empty Dropdownlists for DataGrids in Page_Load too
In OnDataItemBound, set proper values for dropdownlist if it is not postback
Then, in your button_click function, you can get the user selection from the dropdownlists
your controls will be automatically populated from data in viewstate right after Page_Load.
So if you create your dropdownlists in or before Page_Load, it will contain user input.
My suggestion:
Create Datagrid in Page_Load
Create empty Dropdownlists for DataGrids in Page_Load too
In OnDataItemBound, set proper values for dropdownlist if it is not postback
Then, in your button_click function, you can get the user selection from the dropdownlists
ASKER
laotzi, I'm going to try the client-side scripting solution in the meantime. Look forward to hearing other solutions though.
b1xml2, thanks for all the info... your solutions might be a little involved for my needs, but they're definitely informative. Thanks.
Once I finally get this problem licked, I'll split the points between both of you since you both had valuable input. Sound good?
b1xml2, thanks for all the info... your solutions might be a little involved for my needs, but they're definitely informative. Thanks.
Once I finally get this problem licked, I'll split the points between both of you since you both had valuable input. Sound good?
ASKER
laotzi2000, trying... I think the key might be to put the !IsPostBack only in OnDataItemBound. Good call. brb...
ASKER
Ok, no, I don't think that's possible. How can I create the dropdownlists before OnItemDataBound? They are currently being created/inserted dynamically as each row of the datagrid is created. I suppose I could create a custom DropDownList TemplateColumn class, but that seems like a bit too much work for this problem :) Plus, I am creating a number of other objects for other columns in the OnItemDataBound method... don't see how to avoid creating them here.
When do you call the datagrid.databind in your code? In Page_Load?
And is onItemDataBound execute before Page_Load ends?(debug it to find out)
If that's case, it should be OK to put those code in OnItemDataBound.
Another note: I think you should assign a unique id manually for each dropdownlist control
(the primary key of the record must be a perfect choice)
so after Page_Load, it can find proper viewstate data for the dropdownlist control. I think
maybe this is really what the problem is.
And is onItemDataBound execute before Page_Load ends?(debug it to find out)
If that's case, it should be OK to put those code in OnItemDataBound.
Another note: I think you should assign a unique id manually for each dropdownlist control
(the primary key of the record must be a perfect choice)
so after Page_Load, it can find proper viewstate data for the dropdownlist control. I think
maybe this is really what the problem is.
consider this:
<%@ Page language="c#" Codebehind="DynamicControl s.aspx.cs" AutoEventWireup="false" Inherits="b1xml2.ExpertsEx change.CSh arp.April. DynamicCon trols" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
<title>DynamicControls</ti tle>
<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name=vs_defaultClientScrip t content="JavaScript">
<meta name=vs_targetSchema content="http://schemas.microsoft.com/intellisense/ie5">
<script language="javascript">
function persistValue(value)
{
var el = document.forms[0]["Stage"] ;
if (el) el.value = "" + value;
alert(el.value);
}
</script>
</head>
<body MS_POSITIONING="FlowLayout ">
<form id="Form1" method="post" runat="server">
<asp:PlaceHolder ID="holder" Runat="server"></asp:Place Holder>
</form>
</body>
</html>
<%@ Page language="c#" Codebehind="DynamicControl
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
<title>DynamicControls</ti
<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name=vs_defaultClientScrip
<meta name=vs_targetSchema content="http://schemas.microsoft.com/intellisense/ie5">
<script language="javascript">
function persistValue(value)
{
var el = document.forms[0]["Stage"]
if (el) el.value = "" + value;
alert(el.value);
}
</script>
</head>
<body MS_POSITIONING="FlowLayout
<form id="Form1" method="post" runat="server">
<asp:PlaceHolder ID="holder" Runat="server"></asp:Place
</form>
</body>
</html>
and then this:
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls ;
namespace b1xml2.ExpertsExchange.CSh arp.April
{
/// <summary>
/// Summary description for DynamicControls.
/// </summary>
public class DynamicControls : System.Web.UI.Page
{
protected System.Web.UI.WebControls. PlaceHolde r holder;
protected DropDownList country;
protected DropDownList state;
protected LinkButton submit;
private StageList stage = StageList.None;
private void Page_Init(object sender, EventArgs e)
{
if (CompareEnum(Stage,StageLi st.Country ))
{
AddCountry();
}
if (CompareEnum(Stage,StageLi st.State))
{
AddState();
}
if (CompareEnum(Stage,StageLi st.Submit) )
{
AddSubmit();
}
}
private void AddCountry()
{
country = new DropDownList();
country.ID = "country";
country.Attributes["onchan ge"] = string.Format("persistValu e(this.sel ectedIndex == 0 ? {0} : {1});",
(int)StageList.FirstStage,
(int)StageList.SecondStage );
country.DataTextField = "Value";
country.DataValueField = "Key";
holder.Controls.Add(ParseC ontrol("<d iv><b>Coun try</b>")) ;
holder.Controls.Add(countr y);
holder.Controls.Add(ParseC ontrol("</ div>"));
country.SelectedIndexChang ed += new EventHandler(country_Selec tedIndexCh anged);
country.AutoPostBack = true;
}
private void AddState()
{
state = new DropDownList();
state.ID = "state";
state.Attributes["onchange "] = string.Format("persistValu e(this.sel ectedIndex == 0 ? {0} : {1});",
(int)StageList.SecondStage ,
(int)StageList.ThirdStage) ;
state.DataTextField = "Value";
state.DataValueField = "Key";
holder.Controls.Add(ParseC ontrol("<d iv><b>Stat e</b>"));
holder.Controls.Add(state) ;
holder.Controls.Add(ParseC ontrol("</ div>"));
state.SelectedIndexChanged += new EventHandler(state_Selecte dIndexChan ged);
state.AutoPostBack = true;
}
private void AddSubmit()
{
submit = new LinkButton();
submit.ID = "submit";
submit.Text = "Submit Selection";
holder.Controls.Add(submit );
submit.Click += new EventHandler(submit_Click) ;
}
private bool CompareEnum(StageList composite,StageList value)
{
return ((composite & value) == value);
}
private StageList Stage
{
get
{
if (stage == StageList.None)
{
int value = Request.Form["Stage"] == null ? 1 : int.Parse(Request.Form["St age"]);
stage = (StageList)value;
}
return stage;
}
set
{
stage = value;
}
}
private void Page_Load(object sender, System.EventArgs e)
{
if (! Page.IsPostBack)
{
PopulateCountries();
}
SetStage();
}
private void PopulateCountries()
{
SortedList list = new SortedList();
list["US"] = "United States";
list["UK"] = "United Kingdom";
this.country.DataSource = list;
this.country.DataBind();
this.country.Items.Insert( 0,"Please Select Country");
}
private void SetStage()
{
Page.RegisterHiddenField(" Stage",((i nt)Stage). ToString() );
}
#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.Load += new System.EventHandler(this.P age_Load);
this.Init += new EventHandler(Page_Init);
}
#endregion
private void country_SelectedIndexChang ed(object sender, EventArgs e)
{
if (this.country.SelectedInde x > 0)
{
if (this.state == null)
{
AddState();
this.Stage = StageList.SecondStage;
}
SortedList sl = new SortedList();
switch (this.country.SelectedValu e)
{
case "UK":
sl["WL"] = "Wales";
sl["EN"] = "England";
sl["SC"] = "Scotland";
break;
case "US":
sl["NY"] = "New York";
sl["TX"] = "Texas";
break;
}
state.DataSource = sl;
state.DataBind();
state.Items.Insert(0,"Plea se Select State");
}
else
{
this.Stage = StageList.FirstStage;
}
SetStage();
}
private void state_SelectedIndexChanged (object sender, EventArgs e)
{
if (this.state.SelectedIndex > 0)
{
if (this.submit == null)
{
AddSubmit();
this.Stage = StageList.ThirdStage;
}
}
else
{
this.Stage = StageList.SecondStage;
}
SetStage();
}
private void submit_Click(object sender, EventArgs e)
{
Response.Write(string.Form at("<div>C ountry Is {0}</div>\n<div>State Is {1}</div>",country.Selecte dValue,sta te.Selecte dValue));
}
}
[Flags]
public enum StageList : int
{
None = 0,
Country = 1,
State = 2,
Submit = 4,
FirstStage = Country,
SecondStage = Country | State,
ThirdStage = Country | State | Submit
}
}
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls
namespace b1xml2.ExpertsExchange.CSh
{
/// <summary>
/// Summary description for DynamicControls.
/// </summary>
public class DynamicControls : System.Web.UI.Page
{
protected System.Web.UI.WebControls.
protected DropDownList country;
protected DropDownList state;
protected LinkButton submit;
private StageList stage = StageList.None;
private void Page_Init(object sender, EventArgs e)
{
if (CompareEnum(Stage,StageLi
{
AddCountry();
}
if (CompareEnum(Stage,StageLi
{
AddState();
}
if (CompareEnum(Stage,StageLi
{
AddSubmit();
}
}
private void AddCountry()
{
country = new DropDownList();
country.ID = "country";
country.Attributes["onchan
(int)StageList.FirstStage,
(int)StageList.SecondStage
country.DataTextField = "Value";
country.DataValueField = "Key";
holder.Controls.Add(ParseC
holder.Controls.Add(countr
holder.Controls.Add(ParseC
country.SelectedIndexChang
country.AutoPostBack = true;
}
private void AddState()
{
state = new DropDownList();
state.ID = "state";
state.Attributes["onchange
(int)StageList.SecondStage
(int)StageList.ThirdStage)
state.DataTextField = "Value";
state.DataValueField = "Key";
holder.Controls.Add(ParseC
holder.Controls.Add(state)
holder.Controls.Add(ParseC
state.SelectedIndexChanged
state.AutoPostBack = true;
}
private void AddSubmit()
{
submit = new LinkButton();
submit.ID = "submit";
submit.Text = "Submit Selection";
holder.Controls.Add(submit
submit.Click += new EventHandler(submit_Click)
}
private bool CompareEnum(StageList composite,StageList value)
{
return ((composite & value) == value);
}
private StageList Stage
{
get
{
if (stage == StageList.None)
{
int value = Request.Form["Stage"] == null ? 1 : int.Parse(Request.Form["St
stage = (StageList)value;
}
return stage;
}
set
{
stage = value;
}
}
private void Page_Load(object sender, System.EventArgs e)
{
if (! Page.IsPostBack)
{
PopulateCountries();
}
SetStage();
}
private void PopulateCountries()
{
SortedList list = new SortedList();
list["US"] = "United States";
list["UK"] = "United Kingdom";
this.country.DataSource = list;
this.country.DataBind();
this.country.Items.Insert(
}
private void SetStage()
{
Page.RegisterHiddenField("
}
#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.Load += new System.EventHandler(this.P
this.Init += new EventHandler(Page_Init);
}
#endregion
private void country_SelectedIndexChang
{
if (this.country.SelectedInde
{
if (this.state == null)
{
AddState();
this.Stage = StageList.SecondStage;
}
SortedList sl = new SortedList();
switch (this.country.SelectedValu
{
case "UK":
sl["WL"] = "Wales";
sl["EN"] = "England";
sl["SC"] = "Scotland";
break;
case "US":
sl["NY"] = "New York";
sl["TX"] = "Texas";
break;
}
state.DataSource = sl;
state.DataBind();
state.Items.Insert(0,"Plea
}
else
{
this.Stage = StageList.FirstStage;
}
SetStage();
}
private void state_SelectedIndexChanged
{
if (this.state.SelectedIndex > 0)
{
if (this.submit == null)
{
AddSubmit();
this.Stage = StageList.ThirdStage;
}
}
else
{
this.Stage = StageList.SecondStage;
}
SetStage();
}
private void submit_Click(object sender, EventArgs e)
{
Response.Write(string.Form
}
}
[Flags]
public enum StageList : int
{
None = 0,
Country = 1,
State = 2,
Submit = 4,
FirstStage = Country,
SecondStage = Country | State,
ThirdStage = Country | State | Submit
}
}
you will see the dynamic creation of web controls and the application of the viewstate to it before the appropriate web control events are fired off. that;s how you deal with dynamic web controls
comment out the alert if you dont want to see what value is being set...
but only by understanding the viewstate and the nature of the postbacks and adding controls the way shown in the snippet, can you successfully use dynamic controls
HTH
but only by understanding the viewstate and the nature of the postbacks and adding controls the way shown in the snippet, can you successfully use dynamic controls
HTH
ASKER
Well, as far as the ID is concerned, I think that might be a problem. Even though I specify an ID for the dropdowns (using the primary key as suggested), since they are in a datagrid, .NET is prepending "_ctl0__ctl0", "_ctl0__ctl1", "_ctl0__ctl2", etc. I don't know if this is causing a problem, but the code still doesn't work.
Initial load works fine. On submit, the grids are created but the code in OnItemDataBound does not execute because of the !IsPostBack condition. Then the OnButtonClick executes. It looks inside the grids and throws an exception:
foreach ( DataGridItem row in dg.Items ) { // each row of the DataGrid
DropDownList ddl = (DropDownList) row.FindControl( "groupDDL" ).Controls[ 0 ]; <-- fails here because it can't find the dropdown
......
}
Although I'm really curious about getting this to work the "right" way, I think I'm going to have to go with the client-side scripting method. Spending way too much time on this and I have deadlines to worry about.
Initial load works fine. On submit, the grids are created but the code in OnItemDataBound does not execute because of the !IsPostBack condition. Then the OnButtonClick executes. It looks inside the grids and throws an exception:
foreach ( DataGridItem row in dg.Items ) { // each row of the DataGrid
DropDownList ddl = (DropDownList) row.FindControl( "groupDDL" ).Controls[ 0 ]; <-- fails here because it can't find the dropdown
......
}
Although I'm really curious about getting this to work the "right" way, I think I'm going to have to go with the client-side scripting method. Spending way too much time on this and I have deadlines to worry about.
ASKER
Thanks b1xml2. Once again my curiostiy gets the better of me... will try this code out despite having just said I'm spending too much time on this problem :)
On more try:
remember that whether it is postback or not, you should create those dropdownlists,
so you should not put the code inside !Ispostback.
remember that whether it is postback or not, you should create those dropdownlists,
so you should not put the code inside !Ispostback.
I'm pretty positive that it will work.
Cheers up.
Cheers up.
the ids here will not relate to the child controls...so you don;t have to worry about that...you just have to ensure that your datagrid has an id,
show us a snippet (small pls) and we'll get a working code for you.
show us a snippet (small pls) and we'll get a working code for you.
ASKER
Well, I got it working by placing the CreateGrids code in Page_Init and giving a unique ID to the grids, but it only works if I don't use the !IsPostBack condition. This means that on postback, the grids are being created from the database data, then onButtonClick updates the database, then the grids are recreated from the database data. Three database hits. Not really what I'm looking for.
I can't really post a small snippet of my code... it's pretty long. Let me try to edit it down to just the very basics. hold on...
I can't really post a small snippet of my code... it's pretty long. Let me try to edit it down to just the very basics. hold on...
I think even if you recreate the datagrids from the database in Page_Init,
it will still get viewstate data after Page_Load
Your data from the database will be lost
I have an example here:
in Page_Load
Dim c As New TextBox
c.Text = "haha"
PlaceHolder1.Controls.Add( c)
you'll see that even though you set the text of the textbox to haha everytime in Page_Load,
only the user input will preserve.
it will still get viewstate data after Page_Load
Your data from the database will be lost
I have an example here:
in Page_Load
Dim c As New TextBox
c.Text = "haha"
PlaceHolder1.Controls.Add(
you'll see that even though you set the text of the textbox to haha everytime in Page_Load,
only the user input will preserve.
So you can create the grids in Page_Init or Page_Load
It will get Populated from viewstate data after Page_Load
Then you modify the database according to these data in OnButtonClick
Then you can recreate those grids according to the database in Page_Prerender.
Quite complex.
It will get Populated from viewstate data after Page_Load
Then you modify the database according to these data in OnButtonClick
Then you can recreate those grids according to the database in Page_Prerender.
Quite complex.
ASKER
Ok, here's a very edited version... not putting any !IsPostBack condition in.
private void Page_Init(object sender, EventArgs e)
{
LoadGrids();
}
private LoadGrids() { ... } // this method calls CreateGrids for each group in the database
private void CreateGrids( DataView groupView )
{
DataGrid groupGrid = new DataGrid();
int groupNum = (int) groupView[ 0 ][ "Group_Number" ];
groupGrid.ID = "groupGrid_" + groupNum;
// Set grid properties.......
groupGrid.AutoGenerateColu mns = false;
groupGrid.EnableViewState = true;
groupGrid.DataSource = groupView;
groupGrid.ItemDataBound += new DataGridItemEventHandler( OnItemDataBound );
BoundColumn name = new BoundColumn();
name.DataTextField = "Full_Name";
TemplateColumn assignedGroup = new TemplateColumn();
groupGrid.Columns.Add( name );
groupGrid.Columns.Add( assignedGroup );
groupGrid.DataBind();
placeholder.Controls.Add( groupGrid );
}
private void OnItemDataBound( Object sender, DataGridItemEventArgs e )
{
DataView groupView = (DataView) ((DataGrid) sender).DataSource;
if ( e.Item.ItemType != ListItemType.Header && e.Item.ItemType != ListItemType.Footer ) {
DropDownList ddl = new DropDownList();
/*
code to populate dropdownlist from other database data.
*/
e.Item.Cells[ 1 ].ID = "groupDDL";
e.Item.Cells[ 1 ].Controls.Add( ddl );
}
}
private void UpdateButton_Click( object sender, System.Web.UI.ImageClickEv entArgs e )
{
string sqlStr = "";
foreach ( object aControl in placeholder.Controls ) {
if ( aControl.GetType() == typeof( DataGrid )) { // each DataGrid
DataGrid dg = (DataGrid) aControl;
foreach ( DataGridItem row in dg.Items ) { // each row of current DataGrid
DropDownList ddl = (DropDownList) row.FindControl( "groupDDL" ).Controls[ 0 ];
// if selected index was changed, create sqlstr string
}
}
}
if ( sqlStr.Length > 0 ) {
// update database
}
}
private void Page_Init(object sender, EventArgs e)
{
LoadGrids();
}
private LoadGrids() { ... } // this method calls CreateGrids for each group in the database
private void CreateGrids( DataView groupView )
{
DataGrid groupGrid = new DataGrid();
int groupNum = (int) groupView[ 0 ][ "Group_Number" ];
groupGrid.ID = "groupGrid_" + groupNum;
// Set grid properties.......
groupGrid.AutoGenerateColu
groupGrid.EnableViewState = true;
groupGrid.DataSource = groupView;
groupGrid.ItemDataBound += new DataGridItemEventHandler( OnItemDataBound );
BoundColumn name = new BoundColumn();
name.DataTextField = "Full_Name";
TemplateColumn assignedGroup = new TemplateColumn();
groupGrid.Columns.Add( name );
groupGrid.Columns.Add( assignedGroup );
groupGrid.DataBind();
placeholder.Controls.Add( groupGrid );
}
private void OnItemDataBound( Object sender, DataGridItemEventArgs e )
{
DataView groupView = (DataView) ((DataGrid) sender).DataSource;
if ( e.Item.ItemType != ListItemType.Header && e.Item.ItemType != ListItemType.Footer ) {
DropDownList ddl = new DropDownList();
/*
code to populate dropdownlist from other database data.
*/
e.Item.Cells[ 1 ].ID = "groupDDL";
e.Item.Cells[ 1 ].Controls.Add( ddl );
}
}
private void UpdateButton_Click( object sender, System.Web.UI.ImageClickEv
{
string sqlStr = "";
foreach ( object aControl in placeholder.Controls ) {
if ( aControl.GetType() == typeof( DataGrid )) { // each DataGrid
DataGrid dg = (DataGrid) aControl;
foreach ( DataGridItem row in dg.Items ) { // each row of current DataGrid
DropDownList ddl = (DropDownList) row.FindControl( "groupDDL" ).Controls[ 0 ];
// if selected index was changed, create sqlstr string
}
}
}
if ( sqlStr.Length > 0 ) {
// update database
}
}
ASKER
"So you can create the grids in Page_Init or Page_Load
It will get Populated from viewstate data after Page_Load
Then you modify the database according to these data in OnButtonClick
Then you can recreate those grids according to the database in Page_Prerender."
But I would still be hitting the database before the viewstate loaded, right? In order to create the grids without using the viewstate, I need to look up data in the database. I'm trying to avoid hitting the database, overwriting that data with the viewstate, updating the database based on user input, and then hitting it again to recreate the controls based on the newly updated database. Why load data from the database if it's only going to be immediately overwritten by the viewstate data? Unless there's no other way to do it. In which case, it would be more elegant to use client-side scripting to post the dropdown data to the server and then create the grids only once in Prerender.
It will get Populated from viewstate data after Page_Load
Then you modify the database according to these data in OnButtonClick
Then you can recreate those grids according to the database in Page_Prerender."
But I would still be hitting the database before the viewstate loaded, right? In order to create the grids without using the viewstate, I need to look up data in the database. I'm trying to avoid hitting the database, overwriting that data with the viewstate, updating the database based on user input, and then hitting it again to recreate the controls based on the newly updated database. Why load data from the database if it's only going to be immediately overwritten by the viewstate data? Unless there's no other way to do it. In which case, it would be more elegant to use client-side scripting to post the dropdown data to the server and then create the grids only once in Prerender.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
b1xml,
Ahh, I see what you're saying. The Repeater will automatically persist the data unlike the Placeholder, right? And I get the added benefit of having it spit out my multiple datagrids. Terrific! I think this is the right solution.
laotzi,
Thanks for the link. I'm going to use b1xml's solution though since it "kills 2 birds with one stone".
I want to give you both points... you were both more than helpful. Is there a way to give 500 to both of you? If not, are you ok with 250 each?
Ahh, I see what you're saying. The Repeater will automatically persist the data unlike the Placeholder, right? And I get the added benefit of having it spit out my multiple datagrids. Terrific! I think this is the right solution.
laotzi,
Thanks for the link. I'm going to use b1xml's solution though since it "kills 2 birds with one stone".
I want to give you both points... you were both more than helpful. Is there a way to give 500 to both of you? If not, are you ok with 250 each?
It doesn't matter.
I've learned quite a bit too. :)
I've learned quite a bit too. :)
ASKER
Well, I tried to give you both 500. Let me know if it worked.
Thanks guys. Above and beyond :)
Thanks guys. Above and beyond :)
What does your Page_Load() look like??
Do you have something similar to the code:
Sub Page_Load(sender as Object, e as EventArgs)
If Not Page.IsPostBack
myfunction()
End If
End Sub
I'm thinking you have your Sub call that contains the binding of your grids is not in the If Not Page.IsPostBack section. Although, I could be wrong obviously as I'm taking a complete stab in the dark.
Kittrick