Reputation: 600
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();
}
}
Upvotes: 0
Views: 1434
Reputation: 2792
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; }
}
}
Upvotes: 1