I am working on a page that accepts scanned input into a textbox. Once input is entered into the textbox, a user control that contains additional fields for the input item is dynamically generated via autopostback. The user control also contains a link button that is intended to delete the item (itself). Clicking this button generates a "delete" event that is supposed to bubble up to the parent.
Unfortunately this does not seem to happen - the deletion handling code is never reached. My understanding is that it is because the controls have not been generated yet at page load, so the event handlers have not been created yet. But since I don't know what controls need to be generated at page load (since the user input hasn't been processed yet), I can't move the code to generate the user controls to PageLoad.
How should this be handled? Am I going about this the wrong way? Most of my relevant code is below.
Thanks for any help!
page.aspx:
Enter SKU (tab to enter):
<asp:TextBox ID="EntryTextBox" CssClass="textbox" AutoPostBack="true" OnTextChanged="newItem" runat="server"></asp:TextBox>
<asp:Panel ID="itempanel" runat="server">
</asp:Panel>
page.aspx.cs:
protected void newItem(object sender, EventArgs e)
{
// we need to store item entries in ViewState so they comeback on postback;
// they can't be stored in the controls themselves as the controls will
// disappear
ViewState["skus"] += "\t" + EntryTextBox.Text;
ViewState["descs"] += "\t" + itemLookup(EntryTextBox.Text);
// ...more item descriptors...
updateItemPanel();
}
protected void updateItemPanel()
{
// generate a control for each item entered in ViewState
itempanel.Controls.Clear();
List<string> skus = new List<string>(ViewState["items"].ToString().Substring(1).Split('\t'));
List<string> descs = new List<string>(ViewState["descs"].ToString().Substring(1).Split('\t'));
// ...more item descriptors...
int i = 0;
foreach (string sku in skus)
{
item newitemctrl = (item)Page.LoadControl("~/item.ascx");
newitemctrl.line = (i + 1).ToString();
newitemctrl.sku = skus[i];
newitemctrl.description = descs[i];
// ...more item descriptors...
newitemctrl.deleteLinkClicked += new EventHandler(deleteClicked);
itempanel.Controls.Add(newitemctrl);
i++;
}
}
protected void deleteClicked(object sender, EventArgs e)
{
List<string> skus = new List<string>(ViewState["skus"].ToString().Substring(1).Split('\t'));
List<string> descs = new List<string>(ViewState["descs"].ToString().Substring(1).Split('\t'));
// ...more item descriptors...
item olditemctrl = (item)sender;
skus.RemoveAt(Convert.ToInt32(olditemctrl.number) - 1);
descs.RemoveAt(Convert.ToInt32(olditemctrl.number) - 1);
ViewState["skus"] = skus.ToString();
ViewState["descs"] = descs.ToString();
updateItemPanel();
}
item.ascx:
<asp:LinkButton ID="DeleteLinkButton" runat="server" onclick="DeleteLinkButton_Click">Delete</asp:LinkButton>
item.ascx.cs:
public event EventHandler deleteLinkClicked;
protected void DeleteLinkButton_Click(object sender, EventArgs e)
{
if (this.deleteLinkClicked != null)
{
this.deleteLinkClicked(new object(), new EventArgs());
}
}
You can dispatch a postback event in javascript by adding this as onclick event:
__doPostBack("<%= button.ClientID %>", "");
DoPostBack has two arguments, the first is the ID, the second is the event name.
I used this solution successfully.
You'll find more information here.
Note:
This itself does not fire the event automatically, but you can see what you want to happen in the Load Event of your page.
You can get the arguments like this: : Request.Form["__EVENTTARGET"]
You have full access to the form data, so you can also get the values from the dynamically created controls
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!
I working with ASP.NET WebForms with C#. I have a button when clicking on it creates a dropdownlist. This list is created in the "createlist" method with parameters such as id and items. I also add a SelectedIndexChanged event handler.
The list is created successfully, but when I switch between the 3 options the handler is not firing, because the console never prints the message "CHANGE".
Here's my code:
namespace web
{
public partial class Default : System.Web.UI.Page
{
List<string> lstDivs = new List<string>();
protected void btn_Click(object sender, EventArgs e)
{
Control results = FindControl("results");
lstDivs.Add("One");
lstDivs.Add("Two");
lstDivs.Add("Three");
DropDownList lstTipo = createList("lstTipos", lstDivs);
results.Controls.Add(lstTipo);
}
public DropDownList createList(string id, List<string> lstStr)
{
DropDownList lst = new DropDownList();
lst.Attributes.Add("runat", "server");
lst.ID = id + "" + Variables.numDiv;
lst.SelectedIndexChanged += new EventHandler(change);
lst.AutoPostBack = true;
lst.Items.Add(new ListItem("Select...", "Select..."));
for (int i = 0; i < lstStr.Count; i++)
{
lst.Items.Add(new ListItem(lstStr[i], lstStr[i]));
lst.Items[i].Attributes.Add("id", lst.ID + "" + i.ToString());
}
return lst;
}
protected void change(object sender, EventArgs e)
{
Debug.Write("CHANGE\r\n");
}
}
}
Once a dynamic control is created and displayed to the user, it has to be recreated on a postback for its events to fire. Execution of events is triggered by the control itself, hence, if there is no control - there is nobody to detect the change and call your change method.
You have to persist the fact that the dropdown has been created (hidden field, ViewState, Session, database, ...) and create it again in Page_Load.
I'm not sure your control exists on the postback since it is dynamically created. I found this post which might be helpful:
Dynamically Added DropDownlists Are Not Firing SelectedIndexChanged Event
Also, I don't think you need to add the runat as an attribute. This should be done automatically.
I have an .aspx page and I want to dynamically add textboxes to the page on the click of a button. For this purpose I've added a placeholder on the page and add the controls to the server side when the button is clicked.
<asp:PlaceHolder runat="server" ID="NotificationArea"></asp:PlaceHolder>
<asp:Button ID="AddNotification" runat="server" Text="Add" OnClick="AddNotification_Click" />
<asp:Button ID="RemoveNotification" runat="server" Text="Remove" OnClick="RemoveNotification_Click" />
I store the textboxes in a session variable so that I can continue adding and removing textboxes indefinitely. Below I put the on_click methods for the add and remove buttons:
protected void AddNotification_Click(object sender, EventArgs e)
{
List<TextBox> notifications = (List<TextBox>)(Session["Notifications"]);
notifications.Add(new TextBox());
notifications[notifications.Count - 1].Width = 450;
notifications[notifications.Count - 1].ID = "txtNotification" + notifications.Count;
foreach (TextBox textBox in notifications)
{
NotificationArea.Controls.Add(textBox);
}
NotificationArea.Controls.Add(notifications[notifications.Count - 1]);
Session["Notifications"] = notifications;
}
protected void RemoveNotification_Click(object sender, EventArgs e)
{
List<TextBox> notifications = (List<TextBox>)(Session["Notifications"]);
if (notifications.Count > 0)
{
NotificationArea.Controls.Remove(notifications[notifications.Count - 1]);
notifications.RemoveAt(notifications.Count - 1);
}
foreach (TextBox textBox in notifications)
{
NotificationArea.Controls.Add(textBox);
}
Session["Notifications"] = notifications;
}
This works nicely. It continually adds new textboxes and removes the last one if the remove button is clicked. Then, when I tried to get the text from the textboxes I ran into a problem. I never actually store the text that are typed into the textboces in the session variable. Just the empty textboxes that are initially created. Also, see below:
int count = NotificationArea.Controls.Count;
Debugging this shows that the count of the controls in the NotificationArea is 0. How do I access the text for these dynamically added textbox controls? Do I somehow add an ontext_change event to the textboxes that saves the particular textbox's Text into its equivalent in the session variable? How do I go about doing that?
Found the solution here. Turns out you need to re-create all controls that were added dynamically on each post back
public void Page_Init(object sender, EventArgs e)
{
CreateDynamicControls();
}
private void CreateDynamicControls()
{
notifications = (List<TextBox>)(Session["Notifications"]);
if (notifications != null)
{
foreach (TextBox textBox in notifications)
{
NotificationArea.Controls.Add(textBox);
}
}
}
Doing this allows me to access the content of these controls at any time.
I am dynamically adding a custom user control to an update panel. My user control contains two dropdownlists and a textbox. When a control outside of the update panel triggers a postsback, I am re-adding the user control to the update panel.
The problem is...on postback when I re-add the user controls, it's firing the "SelectedIndexChanged" event of the dropdownlists inside the user control. Even if the selectedindex did not change since the last postback.
Any ideas?
I can post the code if necessary, but there's quite a bit in this particular scenario.
Thanks in advance!
EDIT...CODE ADDED BELOW
*.ASCX
<asp:DropDownList ID="ddlColumns" OnSelectedIndexChanged="ddlColumns_SelectedChanged" AppendDataBoundItems="true" AutoPostBack="true" runat="server">
*.ASCX.CS
List<dataColumnSpecs> dataColumns = new List<dataColumnSpecs>();
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
fillDDLColumns();
}
}
public void fillDataColumnsList()
{
dataColumns.Clear();
//COMMON GETDATATABLE RETURNS A DATA TABLE POPULATED WITH THE RESULTS FROM THE STORED PROC COMMAND
DataTable dt = common.getDataTable(storedProcs.SELECT_COLUMNS, new List<SqlParameter>());
foreach (DataRow dr in dt.Rows)
{
dataColumns.Add(new dataColumnSpecs(dr["columnName"].ToString(), dr["friendlyName"].ToString(), dr["dataType"].ToString(), (int)dr["dataSize"]));
}
}
public void fillDDLColumns()
{
fillDataColumnsList();
ddlColumns.Items.Clear();
foreach (dataColumnSpecs dcs in dataColumns)
{
ListItem li = new ListItem();
li.Text = dcs.friendlyName;
li.Value = dcs.columnName;
ddlColumns.Items.Add(li);
}
ddlColumns.Items.Insert(0, new ListItem(" -SELECT A COLUMN- ", ""));
ddlColumns.DataBind();
}
protected void ddlColumns_SelectedChanged(object sender, EventArgs e)
{
//THIS CODE IS BEING FIRED WHEN A BUTTON ON THE PARENT *.ASPX IS CLICKED
}
*.ASPX
<asp:UpdatePanel ID="upControls" runat="server">
<ContentTemplate>
<asp:Button ID="btnAddControl" runat="server" Text="+" OnClick="btnAddControl_Click" />
</ContentTemplate>
</asp:UpdatePanel>
<asp:Button ID="btnGo" runat="server" Text="Go" OnClick="btnGo_Click" ValidationGroup="vgGo" />
<asp:GridView...
*.ASPX.CS
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
uc_Counter = 0;
addControl();
gridview_DataBind();
}
else
{
reloadControls();
}
}
protected void btnGo_Click(object sender, EventArgs e)
{
if (Page.IsValid)
{
//THIS BUTTON CLICK IS WHAT'S TRIGGERING THE
//SELECTEDINDEXCHANGED EVENT TO FIRE ON MY *.ASCX
gridview_DataBind();
}
}
private void reloadControls()
{
int count = this.uc_Counter;
for (int i = 0; i < count; i++)
{
Control myUserControl = Page.LoadControl("~/Controls/myUserControl.ascx");
myUserControl.ID = "scID_" + i;
upControls.ContentTemplateContainer.Controls.AddAt(i, myUserControl);
((customUserControl)myUserControl).fillDDLColumns();
}
}
private void addControl()
{
Control myUserControl = Page.LoadControl("~/Controls/myUserControl.ascx");
myUserControl.ID = "scID_" + uc_Counter.ToString();
upControls.ContentTemplateContainer.Controls.AddAt(upControls.ContentTemplateContainer.Controls.IndexOf(btnAddControl), myUserControl);
//((customUserControl)myUserControl).fillDDLColumns();
this.uc_Counter++;
}
protected int uc_Counter
{
get { return (int)ViewState["uc_Counter"]; }
set { ViewState["uc_Counter"] = value; }
}
Even though this is already answered I want to put an answer here since I've recently tangled with this problem and I couldn't find an answer anywhere that helped me but I did find a solution after a lot of digging into the code.
For me, the reason why this was happening was due to someone overwriting PageStatePersister to change how the viewstate hidden field is rendered. Why do that? I found my answer here.
One of the greatest problems when trying to optimize an ASP.NET page to be more search engine friendly is the view state hidden field. Most search engines give more score to the content of the firsts[sic] thousands of bytes of the document so if your first 2 KB are view state junk your pages are penalized. So the goal here is to move the view state data as down as possible.
What the code I encountered did was blank out the __VIEWSTATE hidden fields and create a view_state hidden field towards the bottom of the page. The problem with this is that it totally mucked up the viewstate and I was getting dropdownlists reported as being changed when they weren't, as well as having all dropdownlists going through the same handler on submit. It was a mess. My solution was to turn off this custom persister on this page only so I wouldn't have to compensate for all this weirdness.
protected override PageStatePersister PageStatePersister
{
get
{
if (LoginRedirectUrl == "/the_page_in_question.aspx")
{
return new HiddenFieldPageStatePersister(Page);
}
return new CustomPageStatePersister(this);
}
}
This allowed me to have my proper viewstate for the page I needed it on but kept the SEO code for the rest of the site. Hope this helps someone.
I found my answer in this post .net DropDownList gets cleared after postback
I changed my counter that I was storing in the viewstate to a session variable.
Then I moved my reloadControls() function from the Page_Load of the *.ASPX to the Page_Init.
The key was dynamically adding my user control in the Page_Init so it would be a member of the page before the Viewstate was applied to controls on the page.
I am trying to create a UI that creates rows that can be dynamically populated.
I am at the point where I can add a Panel to my list that contains a DropDownList and has an associated Remove Button.
The Remove Button has an OnClick event bound that allows me to remove that specific panel.
I am having an issue when trying to bind a SelectedIndexChanged EventHandler to my DropDownList it does not.
I suspect this had something to do with how I am recreating my controls after every Postback.
I am not asking for or expecting a straightforward "Add this or Change this in the code." I really want to have a understand where I am going wrong, sorry for asking so much! :s
protected void Page_Load(object sender, EventArgs e)
{
//Showing this for the the poster that asked.
if (!IsPostBack)
{
GetClustersFromDB(user);
BindGrid();
BindState();
}
if (Session["persistControls"] != null)
{
persistControls = (List<Panel>)Session["persistControls"];
int count = 0;
foreach (Panel dynamicControl in persistControls)
{
DropDownList list = new DropDownList();
list.ID = "list" + count;
list.SelectedIndexChanged += new EventHandler(list_SelectedIndexChanged);
list.AutoPostBack = true;
list.Items.Add(new ListItem("", "0"));
list.Items.Add(new ListItem("Title", "1"));
dynamicControl.ID = "panel" + count;
Button btnRemove = new Button();
btnRemove.Click += new EventHandler(btnDelete_Click);
btnRemove.Text = "Remove";
btnRemove.CommandArgument = count.ToString();
myPlaceholder.Controls.Add(dynamicControl);
myPlaceholder.Controls.Add(btnRemove);
count++;
}
}
}
protected void btnAdd_Click(object sender, EventArgs e)
{
try
{
DropDownList list = new DropDownList();
list.SelectedIndexChanged += new EventHandler(list_SelectedIndexChanged);
list.AutoPostBack = true;
list.Items.Add(new ListItem("", "0"));
list.Items.Add(new ListItem("Title", "1"));
Panel panelContainer = new Panel();
panelContainer.ID = "panel" + persistControls.Count;
panelContainer.Controls.Add(list);
Button btnRemove = new Button();
btnRemove.Click += new EventHandler(btnDelete_Click);
btnRemove.Text = "Remove";
btnRemove.CommandArgument = persistControls.Count.ToString();
myPlaceholder.Controls.Add(panelContainer); // Pushes the Panel to the page.
persistControls.Add(panelContainer);// Adds our Panel to the Control list
myPlaceholder.Controls.Add(btnRemove); // Pushes our Button to the page.
Session["persistControls"] = persistControls; // put it in the session
}
catch
{
throw;
}
}
protected void btnDelete_Click(object sender, EventArgs e)
{
try
{
int deleteThisOne = int.Parse(((Button)sender).CommandArgument);
persistControls.Remove(persistControls[deleteThisOne]);
Session["persistControls"] = persistControls;
Response.Redirect(Request.Url.ToString());
}
catch
{
throw;
}
}
protected void list_SelectedIndexChanged(object sender, EventArgs e)
{
throw new NotImplementedException();
}
//aspx File Snippets
<asp:Button ID="btnAdd" runat="server" Text="Add Control" onclick="btnAdd_Click" />
<asp:Button ID="btnClear" runat="server" Text="Reset" onclick="btnClear_Click"/>
<asp:PlaceHolder ID="myPlaceholder" runat="server"></asp:PlaceHolder>
Maintaining controls like this is a real pain. You could try an alternative approach:
Create a (small) class that encapsulates the data you want to display in each Panel.
Maintain a List of these objects in Session.
Use an <asp:Repeater> to display the items by data-binding it to your list.
In the repeater's ItemTemplate, you can write out your <asp:Panel> that can contain a <asp:DropDownList>. Use the ItemCommand property of the drop-down list to bind it to a server-side event handler. Similarly you can put an <asp:Button> in the template and bind the ItemCommand property of this to another server-side event handler.
When you click Add, you'll postback to the server, add to your list, save it in session, re-bind the repeater.
When you click a Remove, you'll postback to the server, remove the item from your list, save it in session, re-bind the repeater.
This way lets ASP.Net handle all the control creation for you. However, if you really want to do all that yourself, then this Infinities Loop blog post is required reading.
I don't think this will work. If you create the dropdownlists in the PageLoad event, the viewstate will never get bound to it, and you will never see the option selected by the user.
You should create the controls OnInit, then when the PostBack occurs the ViewState data will get bound to the control and you will be able to access it.