I have an application where I need to add multiple (and nested) controls to a PlaceHolder. The user enters the number of 'weeks', and my application adds a RadSplitter (using the Telerik control set), along with the relevant panes/grids for the weeks. I add these controls using code behind.
This works fine when first binding (when entering the number of weeks, and clicking Submit). But I also need to enable drag and drop functionality between the controls, which causes a postback.
It seems that the number of controls in my placeholder is always '0' on postback, so I'm guessing they are not being stored in the ViewState. Rather than have to readd these on every postback, how can I ensure my controls are stored in the ViewState?
Here's some example code:
protected void btnSubmit_Click(object sender, EventArgs e)
{
if (plcSplitter.Controls.Count > 0)
plcSplitter.Controls.Remove(plcSplitter.Controls[0]);
var splitter = new Telerik.Web.UI.RadSplitter();
splitter.Width = Unit.Percentage(100);
int noOfWeeks = int.Parse(txtNoOfWeeks.Text);
DateTime dt = new DateTime(2012, 05, 13);
for (int i = 0; i < noOfWeeks; i++)
{
var range = new Common.DateRange(dt.AddDays(-6),dt);
var pane = new Telerik.Web.UI.RadPane();
Label lbl = new Label();
lbl.ID = "lblText";
lbl.Text = range.To.ToShortDateString();
pane.Controls.Add(lbl);
var gv = AddGrid(i);
pane.Controls.Add(gv);
splitter.Items.Add(pane);
var splitLine = new Telerik.Web.UI.RadSplitBar();
splitter.Items.Add(splitLine);
dt = dt.AddDays(-7);
}
plcSplitter.Controls.Add(splitter);
splitter.DataBind();
}
Controls are not stored in the viewstate, only some of control properties can be stored in viewstate. So, on postback you must create these labels again.
Move that logic to create labels from btnSubmit_Click to separate method, call that method on button click and store data needed to recreate labels somewhere (maybe session), then on postback in OnInit method check for that stored data and if there is some labels create it at that event.
Be sure to read this blog post about viewstate :
http://weblogs.asp.net/infinitiesloop/archive/2006/08/03/Truly-Understanding-Viewstate.aspx
and this about creating controls in runtime
http://weblogs.asp.net/infinitiesloop/archive/2006/08/25/TRULY-Understanding-Dynamic-Controls-_2800_Part-1_2900_.aspx
You can store the data necessary to build your controls in the ViewState, but the really important part is that you make sure your controls are built before you try to access them.
Here's a super basic example.
protected void Page_Load(object sender, EventArgs e)
{
BuildControl(GetLabelData());
}
private Tuple<string, string> GetLabelData()
{
if (Page.IsPostBack)
return (Tuple<string, string>)ViewState["MyLabelData"];
else
return new Tuple<string, string>("lblTest", "Test");
}
private void BuildControl(Tuple<string, string> t)
{
Label l = new Label();
l.ID = t.Item1;
l.Text = t.Item2;
ViewState["MyLabelData"] = t;
plcSplitter.Controls.Add(l);
}
protected void bDoSomething_Click(object sender, EventArgs e)
{
Response.Write(String.Format("plcSplitter.Controls.Count:{0}", plcSplitter.Controls.Count));
}
It's also very important to recognize that these controls are being built at the server and if they can be altered by the client you'll need to implement some mechanism of communication for the important bits of info so you can rebuild your controls and then apply any modifications from the client.
For example you are implementing a draggable control, on the client side when you drag you'll need to store the coordinates in a hidden control manually so that can be posted back to the server and you can have that info available when you're rebuilding the controls.
I think the reason those controls are not stored in the ViewState is that they are being added to the page "too late", after the page ViewState is generated. Look at this post:
Last event in page that can still affect a page's viewstate
Related
- Primary Info:
In my recent project, I need to have a page with a DropDownList with some items like 'firstName' , 'lastName' , 'Age' and etc. I want to add optional controls to the page when every item selected by user. For example when user select the 'Age' another dropdownlist created dynamically with these values : 'Less than 10'
'Between 10 and 30'
'more than 30'
Here is a button that add this user selection to listBox and let user to choice another options. (I made a query at last according to user choices and send it to db)
- What I do:
I create a dropDownList and set it's AutoPostBack property to true and adds some items in it and user must select one of those item. then I add user SelectedValue of dropDownList in a Cache variable before page post back happens:
protected void DropDownListColumnNameSelectedIndexChanged(object sender, EventArgs e)
{
Cache["SelectedKey"] = dropDownListColumnName.SelectedValue;
}
When user select an item from dropDownList *DropDownList_SelectedIndexChanged* fire, and I must create controls dynamically in a place holder:
var textBoxName = new TextBox
{
ID = "textBoxName",
CssClass = "str-search-textbox-highlight",
ViewStateMode = ViewStateMode.Disabled
};
placeHolderFirstItem.Controls.Add(textBoxName);
- What is the problem?
When I try add new control in current Button_Click event, control added successfully to page but I can't find it by placeHolderFirstItem.Controls.Find("textBoxName") actually placeHolderFirstItem.Controls.Count is always zero. So I can't get textBoxName.Text values.
I try to google that for any solution and I found some solution that I must add controls in Page.OnInit so I add controls in overridden OnInit(e):
protected override void OnInit(EventArgs e)
{
if (!Page.IsPostBack) return;
var textBoxName = new TextBox
{
ID = "textBoxName",
CssClass = "str-search-textbox-highlight",
ViewStateMode = ViewStateMode.Disabled
};
placeHolderFirstItem.Controls.Add(textBoxName);
}
after doing this I can find "textBoxName" in placeHolderFirstItem, but it fire before DropDownList_SelectedIndexChanged !
so how can I add new controls to place holder exactly when user change the dropDownList value and how can I read new controls value?
Thanks in advance,
Mohsen.
- Updated:
Here is the better solution
(http://forums.asp.net/p/1959726/5596531.aspx?p=True&t=635244790943067485&pagenum=1)
When you are dynamically adding controls, you have to reload the controls into the control tree everytime thereafter for it to appear. With the help of viewstate, you could change your code sample to have:
ViewState("ShowTextbox") = true
And then in your init routine:
protected override void OnInit(EventArgs e)
{
if (!Page.IsPostBack) return;
if (ViewState("ShowTextBox") == true) {
var textBoxName = new TextBox
{
ID = "textBoxName",
CssClass = "str-search-textbox-highlight",
ViewStateMode = ViewStateMode.Disabled
};
placeHolderFirstItem.Controls.Add(textBoxName);
}
}
Please note it's much easier to have a control on the control tree, and then show/hide by setting Visible to true/false, because of these ASP.NET control tree issues.
I have created a textbox programatically in page load as using the below code:
HtmlTableRow row = new HtmlTableRow();
HtmlTableCell cell1 = new HtmlTableCell();
HtmlTableCell cell2 = new HtmlTableCell();
cell1.Controls.Add(new Label() { ID = LableID1, Text = Name });
cell2.Controls.Add(new TextBox() { ID = TextBoxID1 });
row.Cells.Add(cell1);
row.Cells.Add(cell2);
dynamictable.Rows.Add(row);
And In the button click event i am trying to get the value from the Textbox and assign that value to anohter TextBox which is statically created as below:
string id = TextBoxID1
TextBox tb = (TextBox)dynamictable.FindControl(id);
string valuetext = tb.Text;
TextBox1.Text = valuetext;
Am getting Object Reference Error, I mean, i am not able to Find the control and create the TextBox.
I have tried to create the TextBox as below method also:
TextBox tb = (TextBox)form1.FindControl(id);
TextBox tb = (TextBox)this.form1.FindControl(id);
TextBox tb = (TextBox)page.FindControl(id);
Any help would be highly helpful for me.
I think you might need to find the row and then the cell and then find the textbox.
Means inpite of doing this:
TextBox tb = (TextBox)dynamictable.FindControl(id);
You need to find the specific row first like
// find by it or index etc
HtmlTableRow tb = (HtmlTableRow)dynamictable.FindControl(id);
// Then find the Table cell and then find textbox..
I hope this will help you
In order to work with dynamic controls you need to fully understand the ASP.Net Page Life-cycle
Dynamic controls need to be recreated on each post, there's no magic behind responsible of creating your dynamic controls for you, sadly you have to create them explicitly.
Remember that a page is simply a class that is created when you perform a request and destroyed when the response is sent back to the user. Therefore controls need to be recreated every time. This is done for you when the controls are statically declared on the page. But with dynamic controls, you need to recreate them on each post
The code provided by #BobTodd is a good start point, however the recommendation from Microsoft is that dynamic controls should be created in the Page_Init event in order to sync their events with the rest of the static controls.
So your code would look like:
protected void Page_Init(object sender, EventArgs e)
{
CreateTable();
}
Now, remember this simple rule of dumb, when working with dynamic controls, use always the same ID. This is really important because the page viewstate is loaded back based on the control's ID.
Another thing to consider is that all controls created in the Init event, won't load their viewstate until the LoadViewState method is called on every control on the page. This means that if you subscribe to the Page_PreLoad or Page_Load events, you can safely set the control's properties since their value were already loaded from the viewstate and therefore your new values won't be overridden.
This means that any property assigned to the controls before the PreLoad event, will be replaced by the page viewstate value. Therefore is considered a good practice to set your dynamic controls properties after the viewstate has been loaded.
As a quick view, check the ASP.Net page life-cycle:
You might have a method that creates the table, you need to call it on postback to ensure everything is setup.
protected HtmlTable dynamictable;
protected TextBox tb = new TextBox();
protected override void OnInit(EventArgs args)
{
base.OnInit(args);
CreateTableRows();
}
private void CreateTableRows()
{
HtmlTableRow row = new HtmlTableRow();
HtmlTableCell cell1 = new HtmlTableCell();
HtmlTableCell cell2 = new HtmlTableCell();
cell1.Controls.Add(new Label() { ID = LableID1, Text = Name });
cell2.Controls.Add(tb });
row.Cells.Add(cell1);
row.Cells.Add(cell2);
dynamictable.Rows.Add(row);
}
protected void OnClick(object sender, EventArgs args)
{
return tb.Text;
}
use hidden field to store value of dynamically created text box in java script
also add runat="server" in hidden feild
and you can access your textbox value from hidden feild.
Another way is to store that value in query string using javascript and then reading from it at back end
I have a textbox and a button. On page load I select one column from one row and put its value in the textbox. I have a button click method that updates the same column/row with the value in the textbox.
The problem i'm having is that when I clear the text in the text box, type in new data and hit submit the new text value is not being saved, it uses the old one.
I put a breakpoint at the end of my button click method and it appears that asp.net is sending the old value of the textbox rather than the new one I put in. I'm totally stumped.
This problem persists even if I have viewstate false on the textbox.
Both of my LINQ queries are wrapped in a using statement.
Any Ideas?
Edit: Here is the full code:
protected void Page_Load(object sender, EventArgs e)
{
using (StoreDataContext da = new StoreDataContext())
{
var detail = from a in da.Brands
where a.BrandID == 5
select new
{
brand = a,
};
foreach (var d in detail)
{
txtEditDescription.Text = d.brand.Description;
}
}
}
protected void Button1_Click(object sender, EventArgs e)
{
using (StoreDataContext dk = new StoreDataContext())
{
Brand n = dk.Brands.Single(p => p.BrandID == 5);
n.Description = txtEditDescription.Text;
dk.SubmitChanges();
}
}
As you said, in Page_Load you are retrieving the original value into the text box which overwrites any changes.
Yes, Page_Load DOES execute before Button Click (or any other control events) is executed. I'm going to guess you have done Windows Forms development before this, the ASP.Net web forms event model is quite different. Check out the ASP.NET Page Life Cycle Overview.
I figured it out. I should be be using if(!IsPostBack) on the code that originally fills the textbox with its value.
However this makes no sense to me. If the page is loaded and the textbox text gets a value, then you clear this value and try to insert it into the database, it should insert this value and then when the page post back it will fetch the new value from the database and put the value in the textbox.
The way it's actually working makes it seem like it is executing the page load code before the button click code is executed on post back.
just to trace the error,please try to put a label =( lblDescription.Text ) and leave the rest of code as is,put the new value in the textbox (editdescription.text) try it and tell me what you see
foreach (var d in detail)
{
lblDescription.Text = d.brand.Description;
}
I need to create the following functionality, and looking for the best way to store info on multiple postbacks:
Say I have a TextBox, that is related to a question, i.e. "Places I've visited". Once TextBox will be loaded on first load. Under that, there is a button to "Add another place". This will postback, which will then add another TextBox underneath the first. This can be done x number of times.
The question I have is, how do I store/remember the number of controls that have been added to the page by the user, considering that this load needs to be done in the Init event, and ViewState is not loaded at this point?
EDIT*:
I forgot to mention, that when the user saves, there would be some validation, so if validation fails, need to show all the TextBoxes, with their posted data.
If you were going to only allow a finite number of textboxes on your form, you could create that number of textboxes during Page_Init and set their visibility to false so they would not be rendered in the browser. On the button's click event, you could find the first invisible textbox and change the visibility to true. Something like this
protected void Page_Init(object sender, EventArgs e)
{
for (int i = 0; i < 20; i++)
{
this.Form.Controls.Add(new TextBox() { Visible = false });
}
}
protected void addTextboxButton_Click(object sender, EventArgs e)
{
TextBox tb = this.Form.Controls.OfType<TextBox>().FirstOrDefault(box => box.Visible == false);
if (tb != null) tb.Visible = true;
}
Using this approach, the textboxes become visible one by one on each button click, and the postback values stick.
Obviously, you'd want to put some more work into it, such as perhaps definining some literal controls to create line breaks and prompts for the textboxes, as well as displaying a message when the user hit whatever finite limit you set.
Option 1 : You can use Session to store the number of Textboxes...
Option 2 : You can even add controls in the Load event of the page, wherein you will have the ViewState information.
Here are the links that could help you...
TRULY understanding Dynamic Controls
Truly Understanding ViewState
After some thinking, and considering the answers given, came up with this solution.
In the controls placeholder, have a hidden input field which will store the number of controls added to the page. Then, on Init, I can have the following code (testing only):
protected override void OnInit(EventArgs e)
{
int i = 0;
i = Int32.Parse(hdnTestCount.Value);
if(Request.Params[hdnTestCount.UniqueID] != null)
{
i = Int32.Parse(Request.Params[hdnTestCount.UnitueID]);
}
for (int j = 1; j <= i; j++)
{
TextBox txtBox = new TextBox();
txtBox.ID = "Test" + j.ToString();
plhTest.Controls.Add(txtBox);
}
}
protected void btnAdd_OnClick(object sender, EventArgs e)
{
int i = 0;
i = Int32.Parse(hdnTestCount.Value) + 1;
TextBox txtBox = new TextBox();
txtBox.ID = "Test" + i.ToString();
plhTest.Controls.Add(txtBox);
hdnTestCount.Value = i.ToString();
}
Of course the only issue with this, is that the value could be manipulated by the user in the hidden field. The only other option would be to use Session, which I do not want to use as it sticks around, whereby if the page is refreshed this way, the form will reset itself, which is what should happen.
I am adding some checkboxes dynamically during runtime, and I need to know whether they are checked or not when I reload them next time.
I load the checkbox values from a list stored in ViewState.
The question is: when do I save or check for the value of the the Checked?
I tried the event dispose for the check box and the place holder I am adding the checkboxes in, but it wasn't fired. i.e. when I put a break point it didn't stop. So any suggestions?
This is a sample code, but I don't think it is necessary:
void LoadKeywords()
{
bool add = true;
foreach (string s in (ViewState["keywords"] as List<string>))
if (s == ddlKeywords.SelectedItem.Text)
{
add = false;
continue;
}
if (add)
(ViewState["keywords"] as List<string>).Add(ddlKeywords.SelectedItem.Text);
foreach (string s in (ViewState["keywords"] as List<string>))
{
CheckBox kw = new CheckBox();
kw.Disposed += new EventHandler(kw_Disposed);
kw.Text = s;
PlaceHolderKeywords.Controls.Add(kw);
}
}
If you are dynamically adding controls at run time you have to make sure that those controls are populated to the page's Control collection before ViewState is loaded. This is so that the state of each checkbox can be rehydrated from Viewstate. The Page Load event, for example, is too late.
Typically you would dynamically add your CheckBox controls during the Init Event (before view state is loaded) and then Read the values in your Checkbox controls during the Load event (after view state is loaded).
eg:
protected override void OnInit(EventArgs e)
{
//load the controls before ViewState is loaded
base.OnInit(e);
for (int i = 0; i < 3; i++)
{
CheckBox cb = new CheckBox();
cb = new CheckBox();
cb.ID = "KeyWord" + i.ToString();
cb.Text = "Key Word"
MyPlaceHolder.Controls.Add(new CheckBox());
}
}
//this could also be a button click event perhaps?
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (Page.IsPostBack)
{
//read the checkbox values
foreach(CheckBox control in MyPlaceHolder.Controls)
{
bool isChecked = control.Checked;
string keyword = control.Text;
//do something with these two values
}
}
}
Hope that helps
****EDIT****
Forgot to mention that this is obviously just demo code - you would need to flesh it out.
For more information on dynaic control rendering in ASP.Net check out this article on 4Guys.
For more information on the page life-cycle in ASP.Net check out MSDN.
How to:
try adding a javascript code, that handles checked(),
u can get the checkboxes by using document.findElementById(ID) , then store the checkboxe's value into a hiddenfield that has a runat="server" property.
When to:
either on pageload , check if page is postback(), and check the hiddenfield(s) value(S). or add a submit button (and place its event in the code behind, runat="server" property).
hope this helps u.