UpdatePanel : Button inside UserControl not firing - c#

I want the user to be able to delete a UserControl by clicking on a Delete button located inside that UserControl.
The btnAddChoice is working fine but the btnRemove is inside the UserControl and btnRemove_Click is not triggered.
Here is my ShowChoices.aspx code :
<div>
<strong>Choices</strong>
<asp:UpdatePanel ID="UpdatePanel1" runat="server" ChildrenAsTriggers="true">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="btnAddChoice" EventName="Click" />
</Triggers>
<ContentTemplate>
<ul class="list-unstyled">
<asp:PlaceHolder runat="server" ID="phChoices">
</asp:PlaceHolder>
</ul>
</ContentTemplate>
</asp:UpdatePanel>
</div>
Here is my ShowChoices.aspx.cs code :
protected void btnAddChoice_Click(object sender, EventArgs e)
{
Choice ctl = (Choice)LoadControl("~/Controls/Choice.ascx");
ctl.ID = "choice" + PersistedControls.Count;
int j = PersistedControls.Count + 1;
ctl.SetSummary("Choice #" + j);
phChoices.Controls.Add(ctl); // the UserControl is added here
PersistedControls.Add(ctl);
AsyncPostBackTrigger trigger = new AsyncPostBackTrigger();
trigger.ControlID = ctl.BtnRemoveUniqueID; // problem : BtnRemoveUniqueID = always null
trigger.EventName = "Click";
UpdatePanel1.Triggers.Add(trigger);
}
In Choice.ascx :
<asp:Button ID="btnRemove" CssClass="btn-default" runat="server" Text="Remove this choice" CausesValidation="false" OnClick="btnRemove_Click"/>
In Choice.ascx.cs
protected void btnRemove_Click(object sender, EventArgs e)
{
this.Parent.Controls.Remove(this);
List<Control> _persistedControls = (List<Control>) Session[Step2.PersistedControlsSessionKey];
_persistedControls.Remove(this);
Session[Step2.PersistedControlsSessionKey] = _persistedControls;
UpdatePanel ctl = (UpdatePanel) this.Parent.FindControl("UpdatePanel1");
if (ctl != null)
{
ctl.Update();
}
}

A user control creates a separate naming container, which means additional work is required for your update panel (the SO answer here does a good job explaining it: Trigger an update of the UpdatePanel by a control that is in different ContentPlaceHolder)
This is a fully working example of exposing a button inside a user control so it can act as a trigger and fire events in the parent page. The button is exposed using a public property on the child control, and then all of the event handlers are attached in the parent.
Default.aspx
<%# Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%# Register Src="~/TestChildControl.ascx" TagName="Custom" TagPrefix="TestChildControl" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager runat="server" />
<div>
<asp:UpdatePanel runat="server" ID="updTest">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="btnAddControl" EventName="Click" />
</Triggers>
<ContentTemplate>
<asp:Placeholder runat="server" ID="phControlContainer"></asp:Placeholder>
</ContentTemplate>
</asp:UpdatePanel>
<asp:Button runat="server" ID="btnAddControl" Text="Add Control" OnClick="btnAddControl_OnClick" />
</div>
</form>
</body>
</html>
Default.aspx.cs
Note that the child controls have to be recreated on Page Load in the same order with the same IDs in order for events to fire properly.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (ControlIDs != null)
{
foreach (string controlID in ControlIDs)
{
AddChildControl(controlID);
}
}
}
protected void btnAddControl_OnClick(object sender, EventArgs e)
{
var rand = new Random();
var controlID = string.Format("TestChildControl_{0}", rand.Next());
AddChildControl(controlID);
}
protected void AddChildControl(string controlID)
{
TestChildControl childControl = (TestChildControl)LoadControl("~/TestChildControl.ascx");
childControl.ID = controlID;
phControlContainer.Controls.Add(childControl);
childControl.RemoveControlButton.Click += btnRemoveControl_OnClick;
AsyncPostBackTrigger updateTrigger = new AsyncPostBackTrigger() { ControlID = childControl.RemoveControlButton.UniqueID, EventName = "click" };
updTest.Triggers.Add(updateTrigger);
SaveControlIDs();
}
private void SaveControlIDs()
{
ControlIDs = phControlContainer.Controls.Cast<Control>().Select(c => c.ID).ToList();
}
protected void btnRemoveControl_OnClick(object sender, EventArgs e)
{
var removeButton = sender as Button;
if (removeButton == null)
{
return;
}
var controlID = removeButton.CommandArgument;
var parentControl =
phControlContainer.Controls.Cast<TestChildControl>().FirstOrDefault(c => c.ID.Equals(controlID));
if (parentControl != null)
{
phControlContainer.Controls.Remove(parentControl);
}
SaveControlIDs();
}
protected IEnumerable<string> ControlIDs
{
get
{
var ids = ViewState["ControlIDs"] ?? new List<string>();
return (IEnumerable<string>) ids;
}
set { ViewState["ControlIDs"] = value; }
}
}
TestChildControl.ascx
<%# Control Language="C#" AutoEventWireup="true" CodeFile="TestChildControl.ascx.cs" Inherits="TestChildControl" %>
<div>
This is a test control
</div>
<div>
<asp:Button runat="server" ID="btnRemoveControl" Text="Remove Control" />
</div>
TestChildControl.ascx.cs
Note that here we expose the button as a read-only property so we can assign event handlers to it and access its members. I assigned the parent control ID as the CommandArgument as a matter of convenience.
using System;
using System.Web.UI.WebControls;
public partial class TestChildControl : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
btnRemoveControl.CommandArgument = this.ID;
}
public Button RemoveControlButton
{
get { return btnRemoveControl; }
}
}

Related

Text property of a TextBox is empty when an async PostBack occurs

I've had a look at lots of posts and I feel like I'm going crazy as nothing I've tried seems to work. I simply want to click a button and retrieve the value of a textbox.
I'm using web forms with a site.master so not sure if this could be affecting the problem, but most of the solutions I've seen don't appear to be using a site.master.
I'm not binding the textbox, initially I just wanted to create a contact form.
<%# Page Title="About" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="About.aspx.cs" Inherits="Blackburn_Pulse.About" %>
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">
<script>$("#menuAbout").addClass("navi-active");</script>
<div class="contentBody">
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:TextBox ID="tb_Test" EnableViewState="true" CausesValidation="false" runat="server"></asp:TextBox>
<asp:LinkButton ID="btn_Test" runat="server" OnClick="btn_Test_Click" Text="send test" />
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="btn_Test" EventName="click" />
</Triggers>
</asp:UpdatePanel>
</div>
</asp:Content>
public partial class About : Page
{
protected void Page_Load(object sender, EventArgs e)
{
this.Page.Master.EnableViewState = true;
if (IsPostBack)
{
var test_var = tb_Test.Text; //returning empty
}
}
protected void btn_Test_Click(object sender, EventArgs e)
{
var test_var = tb_Test.Text; //returning empty
}
}
UPDATE:
site.master.cs
public partial class SiteMaster : MasterPage
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
BindCategories();
}
}
protected void BindCategories()
{
Categories Categories = new Categories();
rpt_categories.DataSource = Categories.Get_Categories();
rpt_categories.DataBind();
}
protected void lb_newsCat_Command1(object sender, CommandEventArgs e)
{
switch (e.CommandName)
{
case "naviTo":
//Redirect user to selected category
Response.Redirect("~/" + e.CommandArgument.ToString());
break;
}
}
}
I'm converting a PHP website to an ASP.net website. Turns out I had another HTML form tag on a search box that I haven't started working on yet.
Unfortunately the debugger doesn't throw an error if you have another form tag so anybody else who's just spent hours of their lives searching, double check you don't have more than 1 form tag!
In my site.master.aspx file, I had a search box as follows:
<form method="post" action="?">
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-primary my-2 my-sm-0" type="submit">Search</button>
</form>
Once I took the extra form tag out, everything worked fine.

Re-Binding Event-Handlers in ASP.NET 4.0 "Templated Control"?

Edit: Sadly, nobody seems to know. Maybe this will help clarify my dilemma: I'm trying to implement my own DataList type of control that supports switching from ItemTemplate to EditItemTemplate. The problem occurs when clicking on a button inside the EditItemTemplate -- it doesn't trigger the handler unless you click a second time!
Sorry about the lengthy post. The code is complete, but hopefully with nothing distracting.
I'm trying to create my own User Control that accepts multiple templates. I'm partly following techniques 39 and 40 from "ASP.NET 4.0 in Practice" by Manning. It seems to be working, except the button inside the template isn't bound to the handler until the second click (after one extra postback).
There are four files involved. Default.aspx:
<%# Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%# Register Src="~/TheTemplate.ascx" TagPrefix="TT" TagName="TheTemplate" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<TT:TheTemplate ID="tt" runat="server">
<ATemplate>
<p>This is template A</p>
<asp:Button ID="TemplateAButton" OnClick="TemplateAButton_Click" runat="server" Text="Template A Button" />
</ATemplate>
<BTemplate>
<p>This is template B</p>
<asp:Button ID="TemplateBButton" OnClick="TemplateBButton_Click" runat="server" Text="Template B Button" />
</BTemplate>
</TT:TheTemplate>
<br />
<asp:Button ID="ToggleTemplate" Text="Toggle Template" OnClick="ToggleTemplate_Click" runat="server" />
</div>
</form>
</body>
</html>
Default.aspx.cs:
using System;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Trace.IsEnabled = true;
tt.DataBind();
}
protected void ToggleTemplate_Click(object sender, EventArgs e)
{
tt.TemplateName = (tt.TemplateName == "A") ? "B" : "A";
tt.DataBind();
}
public void TemplateAButton_Click(object sender, EventArgs e)
{
Trace.Write("TemplateAButton_Click");
}
public void TemplateBButton_Click(object sender, EventArgs e)
{
Trace.Write("TemplateBButton_Click");
}
}
And the user control, TheTemplate.ascx:
<%# Control Language="C#" AutoEventWireup="true" CodeFile="TheTemplate.ascx.cs" Inherits="TheTemplate" %>
<p>Using template <asp:Literal Text="<%# TemplateName %>" ID="Literal1" runat="server"></asp:Literal></p>
<asp:Placeholder runat="server" ID="PlaceHolder1" />
And finally, TheTemplate.ascx.cs:
using System;
using System.Web.UI;
[ParseChildren(true)]
public class TheTemplateContainer : Control, INamingContainer
{
private TheTemplate parent;
public TheTemplateContainer(TheTemplate parent)
{
this.parent = parent;
}
}
public partial class TheTemplate : System.Web.UI.UserControl, INamingContainer
{
public string TemplateName
{
get { return (string)(ViewState["TemplateName"] ?? "A"); }
set { ViewState["TemplateName"] = value; }
}
[TemplateContainer(typeof(TheTemplateContainer))]
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate ATemplate { get; set; }
[TemplateContainer(typeof(TheTemplateContainer))]
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate BTemplate { get; set; }
protected override void OnDataBinding(EventArgs e)
{
TheTemplateContainer container = new TheTemplateContainer(this);
if (TemplateName == "A")
ATemplate.InstantiateIn(container);
else if (TemplateName == "B")
BTemplate.InstantiateIn(container);
PlaceHolder1.Controls.Clear();
PlaceHolder1.Controls.Add(container);
EnsureChildControls();
base.OnDataBinding(e);
}
}
When you first run it, you will see ATemplate being used:
If you click on the Toggle Template button, all the text is correctly rendered:
But clicking on either "Template A Button" or "Template B Button" will not trigger the OnClick handler on the first try:
It will work on the second click:
Does the problem have to do with where .DataBind() is being called?
I'm not quite sure I understand it, but the problem has to do with how newly-added controls go through the "catch-up" events. Removing PlaceHolder1 and adding it programmatically solves the issue. TheTemplate.ascx becomes:
<%# Control Language="C#" AutoEventWireup="true" CodeFile="TheTemplate.ascx.cs" Inherits="TheTemplate" %>
<p>Using template
<asp:Literal Text="<%# TemplateName %>" ID="Literal1" runat="server"></asp:Literal></p>
... and in TheTemplate.ascx.cs, replace OnDataBinding like this:
protected override void OnDataBinding(EventArgs e)
{
TheTemplateContainer container = new TheTemplateContainer(this);
if (TemplateName == "A")
ATemplate.InstantiateIn(container);
else if (TemplateName == "B")
BTemplate.InstantiateIn(container);
System.Web.UI.WebControls.PlaceHolder PlaceHolder1 = new System.Web.UI.WebControls.PlaceHolder();
//PlaceHolder1.Controls.Clear();
PlaceHolder1.Controls.Add(container);
this.Controls.Clear();
this.Controls.Add(PlaceHolder1);
EnsureChildControls();
base.OnDataBinding(e);
}
In the future, if I ever feel like I need to add controls dynamically, I will also create a PlaceHolder dynamically and use that as the root. When PlaceHolder is populated, I will then add it to the page.

Update Content page Controls on Master Page Timer TIck

I have a master page it has
<asp:Timer ID="masterTimer" runat="server" Interval="1000" OnTick="masterTimer_Tick"/>
<asp:UpdatePanel runat="server" ID="time" UpdateMode="Always" ChildrenAsTriggers="True">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="masterTimer" EventName="Tick"/>
</Triggers>
<ContentTemplate>
<asp:Label runat="server" ID="lblTime"></asp:Label>
</ContentTemplate>
</asp:UpdatePanel>
and in code behind i have simple
protected void masterTimer_Tick(object sender, EventArgs e)
{
this.lblTime.Text = DateTime.Now.ToString("ddd MMM dd yyyy h:mm:ss tt");
}
In content page i have
Dictionary<Guid, string> data = dataClass.DataDictionary();
and then i am creating a dynamic server control of Label type in Default page (content page). Server control has property of Text. Now my problem is, on each tick it does read the correct data means data dictionary contains updated data and it does assign it to label text property but its not displaying the updated text.
I am creating my CustomeLabel like this
CustomLabel newLabel = new CustomLabel
{
Text = "Label",
Width = 200,
Height = 150,
};
this.Controls.Add(newLabel);
And below is the CustomLabel class derived from LinkLabel and it has below properties
public string Text { get; set; }
public int Width { get; set; }
public int Height { get; set; }
and
readonly LinkButton Label = new LinkButton();
and
protected override void OnLoad(EventArgs e)
{
Label.Text = Text;
}
protected override void Render(HtmlTextWriter output)
{
base.Render(output);
}
I will appreciate if somebody tells me what i need to do
This might do it for you:
Child page:
<%# Page Title="" Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="ChildPage.aspx.cs" Inherits="ChildPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
<asp:UpdatePanel ID="MyUpdatePanel" runat="server">
<ContentTemplate>
<asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
</ContentTemplate>
</asp:UpdatePanel>
</asp:Content>
public partial class ChildPage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Label newLabel = new Label
{
Text = "Label",
Width = 200,
Height = 150,
};
newLabel.Text = "Label: " + DateTime.Now.ToString();
PlaceHolder1.Controls.Add(newLabel);
}
}

Loading a dynamically created radio button list

I am trying to generate a dynamic radio button list, but the issue is that the dynamic list does not show on page load. Only the Submit button shows. Here is the code I am using:
<asp:PlaceHolder runat="server" ID="PlaceHolder1"/>
<asp:Button runat="server" ID="Button1" OnClick="Button1_Click" Text="Submit" />
<asp:Label runat="server" ID="Label1"/>
protected void Page_Load(object sender, EventArgs e)
{
LoadControls();
}
protected void Button1_Click(object sender, EventArgs e)
{
var radioButtonList = PlaceHolder1.FindControl("1") as RadioButtonList;
Label1.Text = radioButtonList.SelectedValue;
}
private void LoadControls()
{
var tmpRBL = new RadioButtonList();
tmpRBL.ID = "1";
for (int i = 1; i <= 5; i++)
{
var tmpItem = new ListItem(i.ToString(), i.ToString());
tmpRBL.Items.Add(tmpItem);
}
PlaceHolder1.Controls.Add(tmpRBL);
}
WebForm1.aspx
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:PlaceHolder runat="server" ID="PlaceHolder1"/>
<asp:Button runat="server" ID="Button1" OnClick="Button1_Click" Text="Submit" />
<asp:Label runat="server" ID="Label1"/>
</div>
</form>
</body>
</html>
WebForm1.aspx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebApplication1
{
public partial class WebForm1 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
LoadControls();
}
protected void Button1_Click(object sender, EventArgs e)
{
var radioButtonList = PlaceHolder1.FindControl("1") as RadioButtonList;
Label1.Text = radioButtonList.SelectedValue;
}
private void LoadControls()
{
var tmpRBL = new RadioButtonList();
tmpRBL.ID = "1";
for (int i = 1; i <= 5; i++)
{
var tmpItem = new ListItem(i.ToString(), i.ToString());
tmpRBL.Items.Add(tmpItem);
}
PlaceHolder1.Controls.Add(tmpRBL);
}
}
}
It works correct. I hope it will be helpful.
You could define an empty RadioButtonList control which you then populate with items on Page_Load. Would have the advantage that you don't have to create it manually. Also using the DataBind mechanism the control offers you together with an ObjectDataSource is a better solution (the Select method of the ObjectDataSource should return the items).
But aside this, maybe AutoEventWireup of the #Page directive (first line in the markup of the ASPX page) is set to false. If so the Page_Load event handler is not called. The directive usually looks like this:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="WebApplication1.WebForm1" %>
I copied the rest of your code and everything runs just fine.

Repeater and Custom Control - Dynamically adding to the collection and retaining values

It has been so long since I've used Web Forms I find myself not remembering most of the perks.
I have a user control that has a button, a repeater and the ItemTemplate property of the repeater is another user control.
<asp:Button runat="server" ID="btnAdd" CssClass="btn" Text="Add" OnClick="btnAdd_Click"/>
<br/>
<asp:Repeater runat="server" ID="rptrRequests">
<ItemTemplate>
<uc1:ucRequest ID="ucNewRequest" runat="server" />
</ItemTemplate>
</asp:Repeater>
The idea is that when the user clicks on the Add button a new instance of the ucRequest is added to the collection. The code behind is as follows:
public partial class ucRequests : UserControl
{
public List<ucRequest> requests
{
get
{
return (from RepeaterItem item in rptrRequests.Items
select (ucRequest) (item.Controls[1])
).ToList();
}
set
{
rptrRequests.DataSource = value;
rptrRequests.DataBind();
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack) return;
requests = new List<ucRequest>();
}
protected void btnAdd_Click(object sender, EventArgs e)
{
var reqs = requests;
reqs.Add(new ucRequest());
requests = reqs;
}
}
After much googling I now remember that I should be binding the Repeater in the OnInit method in order for the ViewState to put the captured data of the controls within the ucRequest control on them between post backs but when I try to do that I will always have a single instance of the control on the Repeater since its Items collection is always empty.
How could I manage to do this?
Thanks in advance.
You just need control ids in view state stead of entire control collection.
<%# Control Language="C#" AutoEventWireup="true"
CodeBehind="ucRequests.ascx.cs"
Inherits="RepeaterWebApplication.ucRequests" %>
<asp:Button runat="server" ID="btnAdd" CssClass="btn" Text="Add"
OnClick="btnAdd_Click" />
<br /><asp:PlaceHolder runat="server" ID="PlaceHolder1"></asp:PlaceHolder>
<%# Control Language="C#" AutoEventWireup="true"
CodeBehind="ucRequest.ascx.cs"
Inherits="RepeaterWebApplication.ucRequest" %>
<asp:TextBox runat="server" ID="TextBox1"></asp:TextBox>
private List<int> _controlIds;
private List<int> ControlIds
{
get
{
if (_controlIds == null)
{
if (ViewState["ControlIds"] != null)
_controlIds = (List<int>) ViewState["ControlIds"];
else
_controlIds = new List<int>();
}
return _controlIds;
}
set { ViewState["ControlIds"] = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
foreach (int id in ControlIds)
{
Control ctrl = Page.LoadControl("ucRequest.ascx");
ctrl.ID = id.ToString();
PlaceHolder1.Controls.Add(ctrl);
}
}
}
protected void btnAdd_Click(object sender, EventArgs e)
{
var reqs = ControlIds;
int id = ControlIds.Count + 1;
reqs.Add(id);
ControlIds = reqs;
Control ctrl = Page.LoadControl("ucRequest.ascx");
ctrl.ID = id.ToString();
PlaceHolder1.Controls.Add(ctrl);
}
Try to get the ucRequests during the OnItemDatabound event, at that point you can edit the content of itemtemplate of the repeater. You can get there after the postback caused by the click on the add button. Here's a sample with a similar scenario

Categories