I've got a custom web part with Accordion panes from the AJAX Control Toolkit as children that are used to render a site map hierarchy. Each pane includes a div with text input and 3 LinkButtons used to edit the sitemap data: "Add Child", "Update", and "Delete".
Currently, all these controls are created in the overridden CreateChildControls method.
When the "Add Child" LinkButton is clicked, the event handler is fired, and a new node is added to the sitemap. When the postback completes, the control should re-render with the new, empty node in the hierarchy, but it doesn't. After a new GET request, the new node appears. After reading for a while, I thought my problem was that I was creating my child controls too early in the process because CreateChildControls is called before Control events are fired, so I moved that bit to the OnPreRender method. But now the Control events don't fire because I'm hooking them up too late in the Page lifecycle (see here: Custom Control Events Not Firing).
My question is this: how do I ensure that the custom control renders the results of its child control event handlers?
As an aside, does it matter that I'm doing this in a web part rather than a custom server control (e.g., is the lifecycle different)?
You could call EnsureChildControls in OnInit of the page. This will ensure that child controls will be recreated before any events are handled.
Related
Let's say we have a pretty standard form with a textbox and a button (for simplicity). You want to handle a Click event and do some stuff based on user's input.
I was wondering, does it matter, when exactly you wire up an event handler for the Click event in a code-behind? If it does, where is the best place to put it? Page load? Page init? I've tried both places, but didn't notice any difference. Or it's just a personal preference of the programmer? I've already searched the internet couple of times, but haven't found any satisfactory answer.
I know when the actual method execute, just not sure about the wiring-up part.
As you know, there are several Page_xxx event handlers, like Init, Load, Prerender... This events exist in Controls, and Pages as well as User controls (in fact they're derived form Control, which holds all these events).
This events are related to the ASP.NET Page Life Cycle
If you read the page pointed to by this link carefully you will understand when the events are triggered. So, if you bind your event handler in any page lifecycle event that happens before the events are triggered, it's guaranteed that your event handlers will be bound in time to be triggered.
These are the main lifecycle steps:
PreInit -> Init -> InitComplete -> PreLoad -> Load -> [Control events] ->
LoadComplete -> PreRender -> SaveStateComplete -> Render -> Unload
Not all of them have associated events, but, if it's necessary you can override the corresponding OnXxx() function, like OnPreInit(). (This is usually only done on custom server controls).
You can bind events in Page_Init or Page_Load, because the control events are triggerd after the loading of all the controls has finished. The Load step happens in top-bottom way, first in the Page, and then recursively in all the children controls.
After Load finishes, the first events which are triggered are the Change Events, like TextChanged or SelectionChanged. Then are triggered all the other events, like Click.
If you bound the events in PreRender or Unload, they wouldn't be triggered. If you did in Init or Load, they would.
So it could look like it's safe to bind in Init or Load, but that's not true:
It could look like there's no special reason to bind them on Init or Load, because they'll be triggered later in the page life cycle. But, as the binding defined in the .aspx happens during Init, a programmer will expect that all events are already bound in the Load event. What would happen if this programmer raised an event of a child control in code behind? The Load event happens first in the root of the control tree, and them on all of the children, recursively. So, by the time the programmer is trying to raise the event of the child control, it won't be already bound. So this won't work as expected. This is more than enough to consider unsafe to bind events in Load event. That's why you should always bind events in Init.
Look at this diagram to see the order of execution of Page & children events:
ASP.NET Page Life Cycle Diagram
I have been wiring mine up in the control tag. If I do it this way it is clear that an event handler is present.
<asp:Button ID="btnRefresh" runat="server" Text="Refresh" OnClick="btnRefresh_Click" />
If I had to wire up an event handler in the codebehind, I would put it in Page_Load as a private function call.
I have an ASP.NET user control with a button, and I want to add it to the page when the user clicks a button from another user control. I have created an event handler to the first user control to handle it from the page and add the second user control to a page. Everything is working normally, but the button on the second user control doesn't respond to the event.
I place the second control on RadAjaxPanel
Note: When I add the second user control at design time its working fine.
All dynamically created controls should be added by the end of Page_Init (though sometimes you can get away with them added by the end of Page_Load).
If you're only adding them based on a button click event then you've done this in the event handers which fire AFTER Page_Init and Page_Load in the lifecycle - This is why your events don't fire and why it works fine when you add at design time.
This is because when a button is clicked on the second user control - the whole page lifecycle starts again. The page goes through Page_Load and Page_Init first and your control doesn't get loaded here. So, when the page lifecycle handles the "handle postback events" part, the control no longer actually exists, so the event doesn't fire.
Conversely, when you add at design time, the control exists in Page_Init and Page_Load so is able to handle the postback events from the user control because it already exists in the control tree - if this makes sense.
You need to think how you can restructure so they're added by the time Page_Load has finished at the very latest or it won't work. Without code samples or more detail it's hard to suggest exactly how you might do this. One possibility would be to set it visible instead of loading it outright - but if the control does some 'heavy lifting' on load like database hits or API calls then this might not be suitable for you.
I did something similar. What I did was to load some controls dynamically based on a selection from a DropDownList. If you have a method which loads the control for you, let's call it LoadControls(), then you can do something like this:
DropDownList_Click {
ViewState("LoadControls") = true;
LoadControls()
}
By setting the ViewState variable, you can then indicate Page_Load to load the controls on future postbacks:
Page_Load {
if (ViewState("LoadControls") == "true")
{
LoadControls();
}
}
This has the effect of then loading the control on-the-fly when the event first happens, and then at future times in the lifecycle.
I have a masterpage and inside that masterage is a user control that has a toolbar with a save button. I then have an aspx page that inherits form t he master page. In that page I have and updatepanel. Is it possible to set the post back trigger to the Save button inside the usercontrol?
You should be able to use Master.FindControl("MySaveButton") from within the content page, and attach it to the scriptmanager's trigger list:
this.MyScriptManager.RegisterAsynchPostBackControl(Master.FindControl("MySaveButton"))
Unless I'm not understanding the question correctly.
Check the fourth post down (marked as answer) here, it ought to help.
In short, create an PostBackTrigger instance, set fields appropriately and then add to the UpdatePanel's Triggers collection.
For example (from linked site):
//Creates a new async trigger
AsyncPostBackTrigger trigger = new AsyncPostBackTrigger();
//Sets the control that will trigger a post-back on the UpdatePanel
trigger.ControlID = "btnCommit";
//Sets the event name of the control
trigger.EventName = "Click";
//Adds the trigger to the UpdatePanels' triggers collection
pnlMain.Triggers.Add(trigger);
I went with a different approach of find my controls. I used this method. I have used this in the past and not sure why I didn't think about it earlier. In my user control I expose controls as properties. In my master page I created a property that allows me to get the user control instance. In my page I can call this: Master.UserControlName.PropertyInControl
So, if I expose a button or control in the user control, I should be able to add that to the trigger collection.
I have a user control that needs to load a child control when a button is clicked.
The trouble is that it has to request the control from another class.
So in the button click event, I call the function to get me my control, and add it to the page, like this:
UserControl ctrl = ExampleDataProvider.GetControl(some params...);
myDetailPane.Controls.Add(ctrl);
The GetControl method looks like:
public static UserControl GetControl(some params...)
{
ExampleDetailPane ctrl = new ExampleDetailPane();
ctrl.Value = "12";
ctrl.Comment = string.Empty;
return ctrl;
}
This isn't working due to the page's lifecycle - the Page_Load of the child control gets fired and its controls are null.
I kind-of know that my approach is wrong and why, but don't know the best way to go about fixing it! Could anyone help?
Dynamic controls must be re-created on every postback, this Article is a good link about how to persist dynamic controls and their state.
If you want to access your control in PostBack or you want to bind Event, you have to create them in CreateChildControls() method.
private UserControl _uc = null;
/// <summary>
/// Creates all controls.
/// </summary>
/// <remarks>All the controls must be created in this method for the event handler</remarks>
protected override void CreateChildControls()
{
_uc = new UserControl ();
this.Controls.Add(_uc);
base.CreateChildControls();
}
Create your control in Page_Init. Then make it visible on your Button_Click event.
(CTRL+C/CTRL+V from some other question I answered last week):
Everything that has to be maintained between page cycles should be declared in Page_Init, not Page_Load.
All the initialization, like adding event handlers, and adding controls should be added during initialization, as the state is saved between page cycles. Handling with the content of controls and the viewstate, should be done in Load.
Check also http://msdn.microsoft.com/en-us/library/ms178472.aspx.
Init
Raised after all controls have been initialized and any skin
settings have been applied. Use this
event to read or initialize control
properties.
.
Load
The Page calls the OnLoad event method
on the Page, then recursively does the
same for each child control, which
does the same for each of its child
controls until the page and all
controls are loaded.
Use the OnLoad event method to set
properties in controls and establish
database connections.
Two possible approaches:
Have the control in the page but not loaded with anything until they click their button. Then you can populate the values in the control. This has the benefit of being within the page life cycle. Note, you can always use the "display: none" style setting for the that your user control is in. Then, as part of the OnClick for the button, you can reveal the div making your control visible.
You could pop up another window, though obviously this has the potential for being blocked by popup blockers.
Ok this is a really annoying bug that I have been having issues with all morning!.
I have a custom control that we have used on many project that has properties that are set and stored in Viewstate by the calling pages onload. the control sets up childcontrols with propertes on the CreateChildControls() method of the custom control.
Normally as usual on a postback the Page_Load event is fired then the CreateChildControls method of the control on the page is fired.
The strange thin though is we have a login system (custom membership provider) on the site and when a user is logged in the opposite happens first the CreateChildControls() method fires then the Page_Load so the control properties are wrong (set from the previous postback)
How could the events be firing in a different order? I thought all page events happened in the same order no matter what and I don't see how being logged in would change that order.
UPDATE: It seems the issue is I'm not calling EnsureChildControls() but I'm not sure where it should be called? If several properies are set on the control which are used in setting up the child controls when should I call EnsureChildControls(), I guess I don't fully understand what EnsureChildControls() does?
CreateChildControls is called whenever the ASP.NET page needs them. There is no specific point in the page cycle for that. It can happen in the Init event, it can happen in the Load event. If you want to make sure your child controls are available, then call EnsureChildControls() method of your control. You can do that in the control's Init event to make sure you have child controls through the whole lifecycle or you can do it whenever you need a reference to one of the child controls - e.g. in the getter/setter of a property of your control.
When creating properties of a server/user control that need access to contained child controls I use the following:
public Whatever SomeProperty
{
get
{
EnsureChildControls();
<more code here>
}
set
{
EnsureChildControls();
<more code here>
}
}
This ensures your control consumers are free to work with your control at various stages of the page lifecycle.