I'm trying to create a composite ASP.NET control that let's you build an editable control collection.
My problem is that when I press the add or postback button (which does nothing other than to postback the form) any values entered in the text boxes are lost.
I can't get it to work when the number of controls change between postbacks. I need to basically be able to recreate the control tree at two different times in the control life-cycle depending on the view state property ControlCount.
This test can be used to reproduce the issue:
public class AddManyControl : CompositeControl
{
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
EnsureChildControls();
}
protected override void CreateChildControls()
{
var count = ViewState["ControlCount"] as int? ?? 0;
for (int i = 0; i < count; i++)
{
var div = new HtmlGenericControl("div");
var textBox = new TextBox();
textBox.ID = "tb" + i;
div.Controls.Add(textBox);
Controls.Add(div);
}
ViewState["ControlCount"] = count;
var btnAdd = new Button();
btnAdd.ID = "Add";
btnAdd.Text = "Add text box";
btnAdd.Click += new EventHandler(btnAdd_Click);
Controls.Add(btnAdd);
var btnPostBack = new Button();
btnPostBack.ID = "PostBack";
btnPostBack.Text = "Do PostBack";
Controls.Add(btnPostBack);
}
void btnAdd_Click(object sender, EventArgs e)
{
ViewState["ControlCount"] = (int)ViewState["ControlCount"] + 1;
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
// If I remove this RecreateChildControls call
// the collection lags behind each postback
// because the count is incremented in the btnAdd_Click event handler
// however, the values are not lost between postbacks
RecreateChildControls();
}
}
If you want to play with ASP.NET's custom controls, you have to play by its rule and its picky! When you start to play with the OnPreRender in a custom control, you know you're on the wrong track 90% of the time.
Generally, the best way to use the ViewState is to declare a property backed up by it, just like the standard ASP.NET controls do (.NET Reflector has been my teacher for years!). This way, it will be read and saved naturally during the event's lifecycle.
Here is a code that seems to do what you want, quite naturally, without any trick:
public class AddManyControl : CompositeControl
{
private void AddControl(int index)
{
var div = new HtmlGenericControl("div");
var textBox = new TextBox();
textBox.ID = "tb" + index;
div.Controls.Add(textBox);
Controls.AddAt(index, div);
}
protected override void CreateChildControls()
{
for (int i = 0; i < ControlsCount; i++)
{
AddControl(i);
}
var btnAdd = new Button();
btnAdd.ID = "Add";
btnAdd.Text = "Add text box";
btnAdd.Click += new EventHandler(btnAdd_Click);
Controls.Add(btnAdd);
var btnPostBack = new Button();
btnPostBack.ID = "PostBack";
btnPostBack.Text = "Do PostBack";
Controls.Add(btnPostBack);
}
private int ControlsCount
{
get
{
object o = ViewState["ControlCount"];
if (o != null)
return (int)o;
return 0;
}
set
{
ViewState["ControlCount"] = value;
}
}
void btnAdd_Click(object sender, EventArgs e)
{
int count = ControlsCount;
AddControl(count);
ControlsCount = count + 1;
}
}
I believe you have to add the control into the view state.
Related
I am trying to figure out how to use a click event handler for my 4 buttons that I have generated dynamically without putting any code in page init or oninit. I have one button that once clicked it generates 4 more buttons. The click event handler for these 4 buttons is not working. Here is the code. Has anybody figured out a way to use the click events in asp.net c# without first putting it in page_load? If I can solve this problem, I can solve my real problem in a bigger scenario.:
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 4; i++)
{
Button b = new Button();
b.ID = i.ToString();
b.Text = "ClickMe";
b.Visible = true;
b.Click += new EventHandler(b_click);
PlaceHolder1.Controls.Add(b);
}
}
void b_click(object sender,EventArgs e)
{
Label1.Text = "ok";
}
Make sure the ID of your dynamic controls include a distinct keyword. In my example below I prepended "DYNAMIC_" to their ID. Then override OnPreRender() like this:
protected override void OnPreRender(EventArgs e)
{
if (Page.IsPostBack && !IsPostBackEventControlRegistered)
{
var controlName = this.Request.Form.AllKeys.SingleOrDefault(key => key.Contains("DYNAMIC_"));
processEventForDynamicControl(controlName);
}
base.OnPreRender(e);
}
private void processEventForDynamicControl(string controlName)
{
//Do your dynamic button click processing here
}
Of course, if your dynamic controls use doPost() (which sadly Button doesn't) you can retrieve the control directly from __EVENTTARGET like this:
var controlName = Request.Params.Get("__EVENTTARGET")
You want to load controls inside Page_Load. Otherwise, they are not in control tree, and they won't be able to trigger b_click event.
public int Counter
{
get { return (int?) ViewState["Counter"] ?? 0; }
set { ViewState["Counter"] = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
var counter = Counter;
for (int i = 0; i < counter; i++)
{
Button b = new Button();
b.ID = i.ToString();
b.Text = "ClickMe";
b.Visible = true;
b.Click += b_click;
PlaceHolder1.Controls.Add(b);
}
}
protected void Button1_Click(object sender, EventArgs e)
{
Counter = 4;
for (int i = 0; i < 4; i++)
{
Button b = new Button();
b.ID = i.ToString();
b.Text = "ClickMe";
b.Visible = true;
b.Click += b_click;
PlaceHolder1.Controls.Add(b);
}
}
void b_click(object sender, EventArgs e)
{
Label1.Text = "ok";
}
Note: If you plan to load inside Page_Init, you want to use Session instead of ViewState.
Basically I got it working.There is no way around it. You have to use Oninit or Page_load and put your b.Click += new EventHandler(b_click) code there in addition to the PlaceHolder1.Controls.Add(b); there as well,for the event handler to register properly with the button. The problem with this method is that it places the button on top of the PlaceHolder portion of the web page which is not what I want. I want to beable to place the button at a particular position in the web page. So how do you go around doing this? Basically after the PlaceHolder1.Controls.Add(b) simply make the button invisible. Then in the when you are ready to place the button in a particular part of your html call PlaceHolder1.Controls.Add(b) again and make it visible. That works for me. If anyone needs help with this I can post some sample code and you can test it for yourself. Thanks all.
as suggested in the title i have in which i can insert how many textboxes i want to add to a placeholder. i can add the textboxes just fine the problem is i cant get the values inserted on those dynamically added textboxes. here's my code
the purpose of this piece of code is to whenever the textbox in which i can introduce the number of textboxes i want. it creates and adds them to the placeholder in my page.
public void txtExtra_TextChanged(object sender, EventArgs e)
{
for (a = 1; a <= int.Parse(txtExtra.Text); a++)
{
TextBox txt = new TextBox();
txt.ID = "txtquestion" + a;
pholder.Controls.Add(txt);
}
}
this is the code of the button that will submit and response.write the values inserted in all those textboxes.
protected void btnConfirm_Click(object sender, EventArgs e)
{
foreach (Control ctr in pholder.Controls)
{
if (ctr is TextBox)
{
string value = ((TextBox)ctr).Text;
Response.Write(value);
}
}
}
i've been searching online and i've been getting answers that this code is fine and it should work but it doesnt. if you guys see anything wrong or have any suggestion that can solve my problem i'd really appreciate it
You are almost there.
Problem
You need to reload those dynamically created textboxes on post back. Otherwise, they will become null, and you won't be able to find it.
In order to do that, you need to save those dynamically TextBoxes Ids in persistent location such as View State or Session State.
Screen Shot
ASPX
Number of TextBoxes: <asp:TextBox runat="server" ID="CounterTextBox"
OnTextChanged="CounterTextBox_TextChanged" AutoPostBack="True" /><br/>
<asp:PlaceHolder runat="server" ID="TextBoxPlaceHolder" /><br/>
<asp:Button runat="server" ID="ConfirmButton" Text="Confirm"
OnClick="ConfirmButton_Click" /><br/>
Result: <asp:Literal runat="server" ID="ResultLiteral"/>
Code Behind
private List<string> TextBoxIdCollection
{
get
{
var collection = ViewState["TextBoxIdCollection"] as List<string>;
return collection ?? new List<string>();
}
set { ViewState["TextBoxIdCollection"] = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
foreach (string textboxId in TextBoxIdCollection)
{
var textbox = new TextBox {ID = textboxId};
TextBoxPlaceHolder.Controls.Add(textbox);
}
}
protected void CounterTextBox_TextChanged(object sender, EventArgs e)
{
var collection = new List<string>();
int total;
if (Int32.TryParse(CounterTextBox.Text, out total))
{
for (int i = 1; i <= total; i++)
{
var textbox = new TextBox { ID = "QuestionTextBox" + i };
// Collect this textbox id
collection.Add(textbox.ID);
TextBoxPlaceHolder.Controls.Add(textbox);
}
TextBoxIdCollection= collection;
}
}
protected void ConfirmButton_Click(object sender, EventArgs e)
{
foreach (Control ctr in TextBoxPlaceHolder.Controls)
{
if (ctr is TextBox)
{
string value = ((TextBox)ctr).Text;
ResultLiteral.Text += value;
}
}
}
You are actually creating textboxes with property Text set to default = ""; So you need set txt.Text property for example:
public void txtExtra_TextChanged(object sender, EventArgs e)
{
for (int a = 1; a <= int.Parse(txtExtra.Text); a++)
{
TextBox txt = new TextBox();
txt.ID = "txtquestion" + a;
txt.Text = "Some text"; // Set some text here
pholder.Controls.Add(txt);
}
}
EDIT:
After that you can store your values into the list:
private static List<string> values = new List<string>();
protected void btnConfirm_Click(object sender, EventArgs e)
{
foreach (Control ctr in pholder.Controls)
{
if (ctr is TextBox)
{
string value = ((TextBox)ctr).Text;
values.Add(value); // add values here
}
}
}
EDIT:
Here is your values:
EDIT:
For super mega better understanding:
Create one more textbox txtOutput then add button GetDataFromTextBoxesAndPutItBelow and create an event for that button `Click'. Event code:
protected void btnGetData_Click(object sender, EventArgs e)
{
for (int i = 0; i < values.Count; i++)
txtOutput.Text += "Value from txtquestion1: " + values[i] + " ";
}
Screenshot looks:
for (int i = 0; i < dataTable.Rows.Count; i++)
{
int comment_id = Convert.ToInt32(dataTable.Rows[i]["comment_id"]);
string created_by_name = dataTable.Rows[i]["created_by_name"].ToString();
string created_at = dataTable.Rows[i]["created_at"].ToString();
string comment = dataTable.Rows[i]["comment"].ToString();
HtmlGenericControl divComment = new HtmlGenericControl("div"); //This is root object of comment.Other objects like textbox,button,etc added into this object.
//divComment.Attributes.Add("class", "div_post_display");
divComment.Attributes.Add("id", comment_id.ToString());
/* Comment by */
HtmlGenericControl lblCommentBy = new HtmlGenericControl("label");
//lblCommentBy.Attributes.Add("class", "divauthor");
lblCommentBy.InnerText = "" + created_by_name + " (" + created_at + ")";
/* Comment body */
HtmlGenericControl pComment = new HtmlGenericControl("p");
//lblCommentBy.Attributes.Add("class", "divauthor");
pComment.InnerText = comment;
divComment.Controls.Add(lblCommentBy);
divComment.Controls.Add(pComment);
if (Session["user_id"] != null)
{
if (Session["user_level"].ToString() == "1") //Admin can reply for comment
{
/* Reply Form */
TextBox txtReply = new TextBox(); //Create object dynamacaly
txtReply.ID = "txtReply_"+comment_id;
txtReply.Attributes.Add("class", "form-control"); //Add css class
txtReply.Width = 400;
divComment.Controls.Add(txtReply); //Add obj to root object(div)
Button btnReply = new Button(); //Create object dynamacaly
btnReply.Text = "Reply"; //Set button text
btnReply.Attributes.Add("class", "btn btn-sm btn-success"); //Add css class
btnReply.Click += btnReply_Click;
btnReply.CommandArgument = comment_id.ToString();
divComment.Controls.Add(btnReply); //Add obj to root object(div)
HtmlGenericControl br = new HtmlGenericControl("br"); //Create object dynamacaly
divComment.Controls.Add(br); //new line
}
}
pnlShowComments.Controls.Add(divComment);
}
guys i am creating dynamic TextBoxes everytime a button is clicked. but once i have as many text boxes as i want.. i want to save these value Database Table.. Please guide how to save it into DB
public void addmoreCustom_Click(object sender, EventArgs e)
{
if (ViewState["addmoreEdu"] != null)
{
myCount = (int)ViewState["addmoreEdu"];
}
myCount++;
ViewState["addmoreEdu"] = myCount;
//dynamicTextBoxes = new TextBox[myCount];
for (int i = 0; i < myCount; i++)
{
TextBox txtboxcustom = new TextBox();
Literal newlit = new Literal();
newlit.Text = "<br /><br />";
txtboxcustom.ID = "txtBoxcustom" + i.ToString();
myPlaceHolder.Controls.Add(txtboxcustom);
myPlaceHolder.Controls.Add(newlit);
dynamicTextBoxes = new TextBox[i];
}
}
You have to recreate the dynamical controls in Page_Load at the latest, otherwise the ViewState is not loaded correctly. You can however add a new dynamical control in an event handler(which happens after page_load in the page's lifefycle).
So addmoreCustom_Click is too late for the recreation of all already created controls, but it's not tool late to add a new control or to read the Text.
So something like this should work(untested):
public void Page_Load(object sender, EventArgs e)
{
if (ViewState["addmoreEdu"] != null)
{
myCount = (int)ViewState["addmoreEdu"];
}
addControls(myCount);
}
public void addmoreCustom_Click(object sender, EventArgs e)
{
if (ViewState["addmoreEdu"] != null)
{
myCount = (int)ViewState["addmoreEdu"];
}
myCount++;
ViewState["addmoreEdu"] = myCount;
addControls(1);
}
private void addControls(int count)
{
int txtCount = myPlaceHolder.Controls.OfType<TextBox>().Count();
for (int i = 0; i < count; i++)
{
TextBox txtboxcustom = new TextBox();
Literal newlit = new Literal();
newlit.Text = "<br /><br />";
txtboxcustom.ID = "txtBoxcustom" + txtCount.ToString();
myPlaceHolder.Controls.Add(txtboxcustom);
myPlaceHolder.Controls.Add(newlit);
}
}
Just enumerate the PlaceHolder-Controls to find your TextBoxes or use Linq:
private void saveData()
{
foreach (TextBox txt in myPlaceHolder.Controls.OfType<TextBox>())
{
string text = txt.Text;
// ...
}
}
Quick and dirty way would be to just iterate the Form collection looking for proper values:
if (Page.IsPostBack)
{
string name = "txtBoxcustom";
foreach (string key in Request.Form.Keys)
{
int index = key.IndexOf(name);
if (index >= 0)
{
int num = Int32.Parse(key.Substring(index + name.Length));
string value = Request.Form[key];
//store value of txtBoxcustom with that number to database...
}
}
}
To get values of dynamically created controls on postback you need to recreate those controls on Page_Init event
Then view state of those controls will be loaded and you will get controls and there values.
public void Page_Init(object sender, EventArgs e)
{
addControls(myCount);
}
I hope this will resolve your problem
Happy coding
I need paging on my web page. I use PagedDataSource for it. but I need pages to enumerated on the .aspx , So I add LinkButtons dynamically to PlaceHolder and Write one Eventhandler for all of them. It shows and works fine. on the first click Eventhandler is fired. on the second it doesn't fire. on the third it is. and so on... Any Idea?
protected void Page_Load(object sender, EventArgs e)
{
bindrepeater();
}
private void bindrepeater()
{
var service = new Service();
var coll = service.GetPeople();
PagedDataSource Pds1 = new PagedDataSource();
Pds1.DataSource = coll;
Pds1.AllowPaging = true;
Pds1.PageSize = 10;
Pds1.CurrentPageIndex = CurrentPage;
Repeater1.DataSource = Pds1;
Repeater1.DataBind();
var count = (coll.Count / 10) + 1;
pages.Controls.Clear();
for (int i = 1; i < count; i++)
{
var lb = new LinkButton() { Text = i.ToString(), CssClass = "hrefia" };
lb.Click += new EventHandler(lb_Click);
pages.Controls.Add(lb);
}
}
protected void lb_Click(object sender, EventArgs e)
{
var lb = (LinkButton)sender;
CurrentPage = int.Parse(lb.Text);
bindrepeater();
}
public int CurrentPage
{
get
{
object s1 = this.ViewState["CurrentPage"];
if (s1 == null)
{
return 0;
}
else
{
return Convert.ToInt32(s1);
}
}
set { this.ViewState["CurrentPage"] = value; }
}
It was needed to give ID to all of the linkbuttons.
#Hassan Boutougha ansered me in comments...
var lb = new LinkButton() { Text = i.ToString(), CssClass = "hrefia" };
lb = "btnId" + i.ToString();
One quick thing, on first load as per your code your current page index will be returned as 0.
Then on subsiquent page clicks like if you click on Page 2, the current page index returned will be 2, however if the page index starts with 0 then 2 means 3rd page rather than second page.
So i guess while setting the current page you should do as follows:
protected void lb_Click(object sender, EventArgs e)
{
var lb = (LinkButton)sender;
CurrentPage = int.Parse(lb.Text) **- 1**;
bindrepeater();
}
the dynamic controls went missing right after i click it, why is this happening, and how do i fix it.
protected void Page_Load(object sender, EventArgs e)
{
/*DropDownList1_SelectedIndexChanged(sender, e);
Label1.Text += "<br/>huh?";
Label1.Text = MapPath("dawd");*/
}
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
//PlaceHolder1.Controls.Clear();
for (int i = 0; i < DropDownList1.SelectedIndex + 1; i++)
{
CheckBox cb = new CheckBox();
cb.AutoPostBack = true;
cb.CheckedChanged += new EventHandler(cb_CheckedChanged);
PlaceHolder1.Controls.Add(cb);
PlaceHolder1.Controls.Add(new LiteralControl("<br/>"));
}
}
void cb_CheckedChanged(object sender, EventArgs e)
{
//DropDownList1_SelectedIndexChanged(sender, e);
Label1.Text += "<br/>adsd";
//throw new NotImplementedException();
}
cheers, Jaf
Dynamically created controls have to be recreated in every postback, or they will not be available and non of their events will fire.
You are only ever adding the checkboxes when the dropdownlist changes, so any other postback will not add them.
It is best to create your dynamic controls on the page OnInit event.
Read about the page life cycle here.
Create a panel
Do not create on page_load
Add this code
protected override void CreateChildControls()
{
base.CreateChildControls();
loadCheckbox();
}
public void loadCheckbox()
{
int checkCount = 10;
CheckBox[] chk = new CheckBox[checkCount];
for(int i == 0; i<=10; i++)
{
chk[i] = new CheckBox();
chk[i].ID = rCmt.cmtkey;
chk[i].Text = rCmt.rootcommitteename;
Panel1.Controls.Add(chk[i]);
}
}