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.
Related
I'm having some difficulty with my dynamically generated usercontrols retaining their selected values. I have created a usercontrol with several form fields. On the main page I have a button that will place my usercontrol in a placeholder. The user can create as many of these as needed. I also have a button in the control itself that allows the user to delete any given control. This functionality seems to be working fine.
But, weirdness ensues in the following scenario:
Click button to create a usercontrol on page. Fill out the form
fields.
Click button again to create a second UC and fill out form
fields. All is well at this point. Values in UC#1 retained its
values.
Click button to create third UC and all selected values are
wiped out of UC #1 and #2.
If I refill out the fields in all the UC's then click the button to create a 4th UC, then UC#1 and UC#3 retain their values but UC#2 loses it's values.
Any help would be massively appreciated. I'm going bug-eyed trying to figure this one out. This is my first foray into dynamic usercontrols and so far it's kicking my butt. And I still have to figure out how to populate these UC's with values from the db so the user can come back and edit the form, but one thing at a time.
aspx:
<asp:PlaceHolder ID="placeholderOffenseCodes" runat="server"> </asp:PlaceHolder>
<asp:Button ID="btnAddOffense" runat="server" Text="Add an Offense" CausesValidation="false" OnClick="btnAddOffense_Click" />
<!--The text value determines how many items are initially displayed on the page-->
<asp:Literal ID="ltlCount" runat="server" Text="0" Visible="false" />
<asp:Literal ID="ltlRemoved" runat="server" Visible="false" />
aspx.cs:
protected void Page_Load(object sender, EventArgs e)
{
AddAndRemoveDynamicOffenseControls();
}
private void AddAndRemoveDynamicOffenseControls()
{
//Determine which control fired the postback event.
Control c = GetPostBackOffenseControl(Page);
if ((c != null))
{
//If the add button was clicked, increase
//the count to let the page know we want
//to display an additional user control
if (c.ID.ToString() == "btnAddOffense")
{
ltlCount.Text = Convert.ToString(Convert.ToInt16(ltlCount.Text) + 1);
}
}
//Be sure everything in the placeholder control is cleared out
placeholderOffenseCodes.Controls.Clear();
int ControlID = 0;
//Re-add controls every time the page loads.
for (int i = 0; i <= (Convert.ToInt16(ltlCount.Text) - 1); i++)
{
IncidentGroupA_Offenses uc = (IncidentGroupA_Offenses)LoadControl("IncidentGroupA_Offenses.ascx");
//If this particular control id has been deleted
//from the page, DO NOT use it again. If we do, it will
//pick up the viewstate data from the old item that
//had this control id, instead of generating
//a completely new control. Instead, increment
//the control ID so we're guaranteed to get a "new"
//control that doesn't have any lingering information in the viewstate.
while (InDeletedOffenseList("offense" + ControlID) == true)
{
ControlID += 1;
}
//Note that if the item has not been deleted from the page,
//we DO want it to use the same control id
//as it used before, so it will automatically maintain
//the viewstate information of the user control
//for us.
uc.ID = "offense" + ControlID;
//Add an event handler to this control to raise
//an event when the delete button is clicked
//on the user control
uc.RemoveOffenseUC += this.HandleRemoveOffenseUserControl;
//Add the user control to the panel
placeholderOffenseCodes.Controls.Add(uc);
//Add Offense number to label on usercontrol
int OffenseNum = i + 1;
uc.OffenseNumber = "Offense " + OffenseNum;
//Increment the control id for the next round through the loop
ControlID += 1;
}
}
protected void btnAddOffense_Click(object sender, EventArgs e)
{
//handled in page_load
}
private bool InDeletedOffenseList(string ControlID)
{
//Determine if the passed in user control ID
//has been stored in the list of controls that
//were previously deleted off the page
string listvalues = ltlRemoved.Text;
string[] stringSeparators = new string[] { "|" };
string[] DeletedList = listvalues.Split(stringSeparators, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i <= DeletedList.GetLength(0) - 1; i++)
{
if (ControlID == DeletedList[i])
{
return true;
}
}
return false;
}
public void HandleRemoveOffenseUserControl(object sender, EventArgs e)
{
//This handles delete event fired from the user control
//Get the user control that fired this event, and remove it
LinkButton linkBtn = sender as LinkButton;
IncidentGroupA_Offenses uc = (IncidentGroupA_Offenses)linkBtn.Parent;
if (uc != null)
{
placeholderOffenseCodes.Controls.Remove(uc);
}
//Keep a pipe delimited list of which user controls were removed. This will increase the
//viewstate size if the user keeps removing dynamic controls, but under normal use
//this is such a small increase in size that it shouldn't be an issue.
ltlRemoved.Text += uc.ID.ToString() + "|";
//Also, now that we've removed a user control decrement the count of total user controls on the page
ltlCount.Text = Convert.ToString(Convert.ToInt16(ltlCount.Text) - 1);
}
public Control GetPostBackOffenseControl(Page page)
{
Control control = null;
string ctrlname = page.Request.Params.Get("__EVENTTARGET");
if ((ctrlname != null) & ctrlname != string.Empty)
{
control = page.FindControl(ctrlname);
}
else
{
foreach (string ctl in page.Request.Form)
{
Control c = page.FindControl(ctl);
if (c is System.Web.UI.WebControls.Button)
{
control = c;
break;
}
}
}
return control;
}
.ascx.cs:
public event EventHandler RemoveOffenseUC;
protected void btnRemoveOffense_Click(object sender, EventArgs e)
{
//Raise this event so the parent page can handle it
if (RemoveOffenseUC != null)
{
RemoveOffenseUC(sender, e);
}
}
public string OffenseNumber
{
get { return lblOffenseNumber.Text; }
set { lblOffenseNumber.Text = value; }
}
I always thought you had to add dynamic controls at Page_Init to ensure correct loading of ViewState. Maybe that simple change would fix your problem?
Otherwise, I've had luck in the past with avoiding dynamic controls altogether using a repeater. Instead of adding dynamic controls, which webforms is definitely not very good at, add data to a data structure like a List or ADO DataTable, then bind that to an asp:Repeater with the controls you want in it. No messy fussing around with dynamic controls.
Best of luck!
Good afternoon,
I have a DropDownList that I am setting Enabled = false in the code behind OnPageLoad. Later when I press the save button on the page I try to extract the data and I get a weird value from the disabled DropDownList and correct values from the Enabled DropDownList's.
My question is, how can I disable the DropDownList OnPageLoad so the users can't change the data but still modify it's data in the code behind file and extract it when needed? I see that the enabled property sets the read-only flag and I tried enabling the drop down before modifying it's data but it didn't work. Any ideas?
Code behind:
protected void Page_Load(object sender, EventArgs e)
{
for (int i = 1; i <= Convert.ToInt32(txtNumPrizes.Text.Trim()); i++)
{
DropDownList dl = new DropDownList();
bool disableRow = true; //example
dl.ID = "DDPrize" + i.ToString();
dl.DataSourceID = "SqlDataSource1";
dl.DataTextField = "PrizeName";
dl.DataValueField = "PrizeID";
Panel1.Controls.Add(dl);
dl.DataBind();
if (disableRow == true)
{
dl.Enabled = false;
}
}
}
protected void Page_PreRender(object sender, EventArgs e)
{
for (int i = 1; i <= Convert.ToInt32(txtNumPrizes.Text.Trim()); i++)
{
DropDownList dd = (DropDownList)Panel1.FindControl("DDPrize" + i.ToString());
//disable the row if prize was already assigned to a player
int place = 1; //example
int selectedValue = DropDownSelect(i, place, GetTournamentID());
dd.SelectedValue = selectedValue.ToString(); //sets properly here
}
}
protected void btnSave_Click(object sender, EventArgs e)
{
for (int i = 1; i <= numPrizes; i++)
{
DropDownList dd = (DropDownList)Panel1.FindControl("DDPrize" + i.ToString());
string key = dd.SelectedValue;//here is where we can't get the selected value :(
}
}
If you disable it in code, then when it renders they can't change the selection. The SelectedItem will possibly be null/Nothing depending on whether you set any of the items to Selected=true.
It doesn't make much sense [to me, anyway] using a dropdown that can't be used. Unless of course it's waiting on a postback for enabling it based on certain criteria.
You could disable it with jQuery on page load, and .NET wouldn't care. No matter what, it will let you access the value, or lack thereof, which is what you might be missing. Again, you'll still run into the issue that there's no SelectedItem if you haven't set one in the web form or in code.
Posting the code will help us help you further :)
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
i have a alphabetic filter consist of 26 dynamically created link button on selecting any link button it is filtering the name of user's on the basis of alphabet and changing its color to orange to make it different from other linkbuttons it is working fine but if there are more number of user associated with a particular alphabet and on applying filter it is filtering the user on the basis of that alphabet and showing them in a list view on clicking the data pager next page or any other page number the link button changes its color to default color but i want to keep that highlighted until and unless other link button is selected
my code
protected void Page_Init(object sender, EventArgs e)
{
// Adding Dynamically linkbuttons for all alphabets(i.e. A-Z)
for (char asciiValue = 'A'; asciiValue <= 'Z'; asciiValue++)
{
LinkButton lbtnCharacter = new LinkButton();
lbtnCharacter.ID = "lbtnCharacter" + asciiValue;
divAlphabets.Controls.Add(lbtnCharacter);
// Setting the properties of dynamically created Linkbutton.
lbtnCharacter.Text = Convert.ToString(asciiValue);
lbtnCharacter.CssClass = "firstCharacter";
lbtnCharacter.ToolTip = "Show Tags starting with '" + Convert.ToString(asciiValue) + "'";
lbtnCharacter.CommandArgument = Convert.ToString(asciiValue);
lbtnCharacter.Command += new CommandEventHandler(lbtnCharacter_Command);
}
}
// For assigning default color to linkbutton text in page load
foreach (var ctrl in divAlphabets.Controls)
{
if (ctrl is LinkButton)
((LinkButton)ctrl).CssClass = "firstCharacter";
}
void lbtnCharacter_Command(object sender, CommandEventArgs e)
{
// Storing the values of pressed alphabet in viewstate.
ViewState["Selected_Character"] = e.CommandArgument;
LinkButton lbtnSelected = (LinkButton)divAlphabets.FindControl("lbtnCharacter" + e.CommandArgument);
lbtnSelected.CssClass = "firstCharacter highlighted";
txtTagFilter.Text = string.Empty;
BindTagList();
}
I hope I understood your question.
You are setting your Selected_Character item in the command handler and then setting the class of the button to highlight it. This only gets fired when the button is clicked, not when you move to the next page. Why not separate these two operations. Set the class of the link button on prerender if the Selected_Character matches. That way even when you page the link button will stay highlighted.
I would also set your selected character as a query string parameter, if someone copies and pastes a link to your page the button would not highlight and the correct data would not display.
Hope this helps.
Edit: Haven't tested the below but maybe it will get you started.
void lbtnCharacter_Command(object sender, CommandEventArgs e)
{
// redirect to self with tag as qs parameter
Response.Redirect(string.Format("{0}?tag={1}", Request.Url.GetLeftPart(UriPartial.Path), e.CommandArgument));
}
protected void Page_PreRender(object sender, EventArgs e)
{
if (Request.QueryString["tag"] != null) {
LinkButton lbtnSelected = (LinkButton)divAlphabets.FindControl("lbtnCharacter" + Request.QueryString["tag"]);
lbtnSelected.CssClass = "firstCharacter highlighted";
}
}
N.B You will also need to change your BindTagList to use the query string also. I'm assuming you call this in the page load event.
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.