I am puzzling over an event plumbing problem on my page. I have a single ASP.NET UserControl that monitors some browser-side events and raises them as UpdatePanel async-post-backs to the server. Let's call it EventPump. It includes some JavaScript and server-side controls to manage the events. It works great.
I have several other controls on the same page that don't know about each other but would like to subscribe to some of the events raised by the EventPump. Normally one control would subscribe to another's events by including that control in its markup and wiring the events. But in this case that would give me multiple instances of EventPump, which I don't want.
How can I have several UserControls subscribe to events of the common EventPump control?
A few additional options I can think of:
Code to interfaces, so the EventPump control implements IEventPump which has a various public events. The containing page implements IEventPumpContainer with one property called EventPump which allows all other user controls to register like so:
((IEventPumpContainer)Page).EventPump.MyEvent += MyEventHandler;
If the page is aware of the controls that need to subscribe you could have it call appropriate methods on the controls when events fire. For example:
EventPump.MyEvent += (s, e) => SomeControl.SomeMethod();
Alternatively and arguably better is to make the events first class citizens and have an event dispatcher that can be used to subscribe to and raise events. For example:
Events.Register<MyEvent>(e => TextBox.Text = "MyEvent Happened");
...
Events.Raise(new MyEvent());
A couple of options:
Put the EventPump on a master page and create a property that references it (by making the master page "strongly typed" through the #MasterType directive)
Add a static helper method that takes a Page instance as a parameter, and finds the EventPump instance on the page. Instead of using FindControl for this, which will be slow on large pages, you can simply register the control in Page.Items. This approach is best combined with code in the EventPump control that ensures that only one EventPump instance exists on the page.
Make an EventPumpProxy control in the style of ScriptManagerProxy. This is somewhat kludgy, but nice if you prefer to have declarative markup locally instead of a lot of code-behind. I don't know the exact implementation details, but you should be able to see them by disassembling AJAX framework sources.
Create event handlers in the user controls, and subscribe all of the events to the same handler on the page.
UserControl1:
public event EventHandler SomethingChanged;
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
//do OnSelectedIndexChanged logic
if (this.SomethingChanged != null)
this.SomethingChanged(this, e);
}
protected void CheckBox1_CheckedChanged(object sender, EventArgs e)
{
//do OnCheckedChanged logic
if (this.SomethingChanged != null)
this.SomethingChanged(this, e);
}
UserControl2:
public event EventHandler SomethingElseChanged;
protected void TextBox1_TextChanged(object sender, EventArgs e)
{
//do OnTextChanged logic
if (this.SomethingElseChanged != null)
this.SomethingElseChanged(this, e);
}
protected void Button1_Click(object sender, EventArgs e)
{
//do OnClick logic
if (this.SomethingElseChanged != null)
this.SomethingElseChanged(this, e);
}
Page:
<uc:UserControl1 ID="UserControl1" runat="server" SomethingChanged="UserControl_Changed" ... />
<uc:UserControl2 ID="UserControl2" runat="server" SomethingElseChanged="UserControl_Changed" ... />
Code-behind:
protected void UserControl_Changed(object sender, EventArgs e)
{
Label1.Text = "Stuff has changed...";
}
EDIT
See this article for invoking events across user controls:
http://www.codeproject.com/KB/aspnet/EventsAcrossUCs.aspx
Related
I have a user control (Navigation) nested within another user control (Header) that is dynamically loaded from a Control class (Standard).
The user controls, Navigation and Header have AutoEventWireup = false.
The control class Standard calls loads the Header user control from a configuration item.
private void layoutAndRender(HtmlTextWriter output, string UserControlKey, NameValueCollection UserControlsConfiguration)
{
if(UserControlsConfiguration[UserControlKey] != null && UserControlsConfiguration[UserControlKey].ToString() != "")
{
string suc = System.Web.HttpContext.Current.Request.ApplicationPath + UserControlsConfiguration[UserControlKey].ToString();
UserControl ucToRender = (UserControl)this.Page.LoadControl(suc);
ucToRender.RenderControl(output);
}
}
My problem is that I want to initialize an object in the Navigation user control that can accept Page.Request and Page.Response, but events don't seem to be firing in the Navigation code behind.
The code I'm using to initialize my object is:
this.browser = new Browser(this.Request, this.Response);
I tried doing this during the Navigation constructor but this.Request and this.Response are not set at that time.
I tried using the statement in a void Page_Load(object sender, System.EventArgs e) method, but this doesn't seem to be firing, even if I have this.Load += new System.EventHandler(this.Page_Load); in the Navigation constructor.
I've also tried similar statements for Page_Init and Page_PreRender, but none of these seem to be firing.
Is it that a control loaded with LoadControl does not fire Load or Init events, if loaded the way I have loaded them, and the same goes for any user controls that it may include?
If AutoEventWireup is set to false for the controls that you want to load, then you should override the OnInit method to wire up the Load event handler for the controls. The Request and Response properties should be available from within Page_Load.
For example:
public class Header : Control
{
private void Page_Load(object sender, EventArgs e)
{
}
override protected void OnInit(EventArgs e)
{
this.Load += new System.EventHandler(this.Page_Load);
}
}
See MSDN for more info on AutoEventWireup:
http://support.microsoft.com/kb/324151
Unfortunately, this is legacy code, and not always done the right way.
In particularly, because the user control is loaded and rendered manually, it probably skips most of the event model that I wanted to take advantage of. Ideally, it should have done an AddControl() to the page, rather than rendering the control to a HtmlTextWriter.
My work around is to override the RenderControl method, and initialize the browser property before passing RenderControl up the chain.
I have a dropdown list in User Control
How Can I get selected value of dropdown list of user control in page when user select another item (auto postback is true)?
I tried to store selected value of ddl in a public member from Selected Index Changed event handler. But this handler executes after page load of container page. I need to load data in page based on selected value in ddl of user control.
Thanks
User Control's code
protected void ddlPageSize_SelectedIndexChanged(object sender, EventArgs e)
{
_SelectedPageSize = Convert.ToInt32(ddlPageSize.SelectedValue);
}
int GetSelectedPageSize()
{
return _SelectedPageSize;
}
There are a number of ways to accomplish what you're looking for. The first is simply to re-order your events in the containing page. If you use the PreRender event rather than the PageLoad event, your drop down selection action will be complete and the information will be readily available.
A second method, which probably more extensible, would be to raise a custom event from your usercontrol that your page listens for and handles. Then the action would be taken directly at the point where the information is immediately available. This allows any containing structure (whether it's a page, usercontrol or something similar) to subscribe to the event and handle whatever is needed.
A third method, a little more rigid, would be to have a function in the containing page that is called by the usercontrol once the data is complete. This requires the usercontrol to have knowledge of the specific page type that it will be included in (making it less extensible) so I wouldn't recommend it.
Edit: Here's an idea for implement option #2 with a custom event:
public partial class MyUserControl: UserControl
{
//All of your existing code goes in here somewhere
//Declare an event that describes what happened. This is a delegate
public event EventHandler PageSizeSelected;
//Provide a method that properly raises the event
protected virtual void OnPageSizeSelected(EventArgs e)
{
// Here, you use the "this" so it's your own control. You can also
// customize the EventArgs to pass something you'd like.
if (PageSizeSelected!= null)
PageSizeSelected(this, e);
}
private void ddlPageSize_SelectedIndexChanged(object sender, EventArgs e)
{
_SelectedPageSize = Convert.ToInt32(ddlPageSize.SelectedValue);
OnPageSizeSelected(EventArgs.Empty);
}
}
Then in your page code you would listen for the event. Somewhere in the page load you would add:
myUserControlInstance.PageSizeSelected += MyHandinglingMethod;
And then provide the method that handles the event:
protected void MyHandlingMethod(object sender, EventArgs e)
{
// Do what you need to do here
}
It is difficult to explain but here it goes
I have a custom control in my asp.net page, i have two files which i pass page by page to the control, the user does some editing on that file's page (loaded in the control) and when it reaches the end i want the control to some how let the page know that the end of the page has been reached, now load a new page in the control,
How should i achieve this and which is the best practice?
You can bubble up the event from user control to the parent.
ParentAddUser.aspx
<uc1:AddUser ID="AddUser1" runat="Server" OnUserCreated="AddUser1_UserCreated"></uc1:AddUser>
ParentAddUser.aspx.cs
protected void AddUser1_UserCreated(object sender, CommandEventArgs e)
{
// User was created successfully. Do Something here.
}
AddUser.ascx.cs
public event CommandEventHandler UserCreated;
protected void Button_Click(object sender, EventArgs e)
{
// User was created successfully. Bubble up the event to parent.
UserCreated(this, new CommandEventArgs("UserId", userId.ToString()));
}
I have problem passing a variable from a main page containing a user control to the user control itself. Although the passed variable is available generally in the code-behind of the user control the page_load event can't seem to read it.
My code -
In the main page code-behind:
protected void FindCCFsButton_Click(object sender, EventArgs e)
{
if (CustomerDropDown.SelectedIndex != 0)
{ SearchUcCCFList.SetCustID(CustomerDropDown.SelectedValue); }
}
(SearchUcCCFList is the instance of the user control in the main aspx page).
In the user control code behind:
public partial class ucCCFList : System.Web.UI.UserControl
{
public string srchCust { get; set; }
public void SetCustID(string custID)
{
srchCust = custID;
testCustLabel.Text = GetCustID(); //this works
}
public string GetCustID()
{
return srchCust;
}
protected void Page_Load(object sender, EventArgs e)
{
CCFGridView.DataSource = DAL.SearchCCFs(custID : GetCustID()); //doesn't work
CCFGridView.DataBind();
test2CustLabel.Text = GetCustID(); //doesn't work
}
In the Page_Load event GetCustId() doesn't return anything (so the records aren't filtered and all get returned) although it can be read in the methods outside the Page_Load.
I'm probably making a beginners error but any help would be appreciated.
Edit - following Alan's suggestion in the comments I stepped through the page loading sequence & it appears that the user control's Page_Load event is running BEFORE the code in the main page's button click so the variable is not yet available. The sequence after clicking the button is:
User control Page_Load runs
Code in button event on main page
Other code (outside Page_Load) in user control runs hence variable is available here.
This seems a bit weird, is there another way to pass the variable into the user controls Page_Load?
In this case, your click handling even on the main page is called after the user control page load call. Your variable is being set, but not until after your data binding in the user control.
Either switch the user control to declarative binding which will handle calling methods in the correct order for you. Or the easier fix in this case is to change the user control data binding from Page_Load to Page_PreRender, which is called later in the life cycle, after the main page click handling call.
protected void Page_PreRender(object sender, EventArgs e)
{
CCFGridView.DataSource = DAL.SearchCCFs(custID : GetCustID()); // will work now
CCFGridView.DataBind();
test2CustLabel.Text = GetCustID(); // will work now
}
For a more thorough answer, read up on the ASP.NET page life cycle including the interaction with user controls' life cycle.
I have a user control, which is added to another user control. The nested user control is built up of a GridView, an image button and a link button. The nested user control is added to the outer control as a collection object based upon the results bound to the GridView.
The problem that I have is that my link button doesn't work. I click on it and the event doesn't fire. Even adding a break point was not reached. As the nested user control is added a number of times, I have set image button to have unique ids and also the link button. Whilst image button works correctly with its JavaScript. The link button needs to fire an event in the code behind, but despite all my efforts, I can't make it work. I am adding the link button to the control dynamically. Below is the relevant code that I am using:
public partial class ucCustomerDetails : System.Web.UI.UserControl
{
public event EventHandler ViewAllClicked;
protected override void CreateChildControls( )
{
base.CreateChildControls( );
string strUniqueID = lnkShowAllCust.UniqueID;
strUniqueID = strUniqueID.Replace('$','_');
this.lnkShowAllCust.ID = strUniqueID;
this.lnkShowAllCust.Click += new EventHandler(this.lnkShowAllCust_Click);
this.Controls.Add(lnkShowAllCust);
}
protected override void OnInit (EventArgs e)
{
CreateChildControls( );
base.OnInit(e);
}
protected override void OnLoad(EventArgs e)
{
base.EnsureChildControls( );
}
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
CreateChildControls( );
}
}
protected void lnkShowAllCust_Click(object sender, EventArgs e)
{
this.OnCustShowAllClicked(new EventArgs ( ));
}
protected virtual void OnCustShowAllClicked(EventArgs args)
{
if (this.ViewAllClicked != null)
{
this.ViewAllClicked(this, args);
}
}
}
I have been stuggling with this problem for the last 3 days and have had no success with it, and I really do need some help.
Can anyone please help me?
My LinkButton wasn't firing it's Click event, and the reason was I had its CausesValidation property set to True. If you don't want the link to validate the form, be sure to set this to False.
Try adding your click event to the linkbutton tag:
<asp:LinkButton runat="server" OnClick="linkShowAllCust_Click" />
Or adding it to your Page_Load:
Page_Load(object sender, EventArgs e)
{
this.lnkShowAllCust.Click += new EventHandler(this.lnkShowAllCust_Click);
}
Is the usercontrol within the gridview? If so register the event handler on the gridview's onrowcreated event.
It appears that you have a viewstate issue. Because the control isn't there when the viewstate is loaded the application doesn't know how to hook up the event to be fired. Here is how to work around this.
You can actually make your app work like normal by loading the control tree right after the loadviewstateevent is fired. if you override the loadviewstate event, call mybase.loadviewstate and then put your own code to regenerate the controls right after it, the values for those controls will be available on page load. In one of my apps I use a viewstate field to hold the ID or the array info that can be used to recreate those controls.
Protected Overrides Sub LoadViewState(ByVal savedState As Object)
MyBase.LoadViewState(savedState)
If IsPostBack Then
CreateMyControls()
End If
End Sub
I had the same issue. I had viewstate="false" on the page I was adding the control to. (on the aspx page)