I have several Server Controls, each in a separate assembly and I'd like to load one of them dynamically into a page depending on some choice. There seems to be a problem where the server side events in the control are not firing however.
e.g. Controls are of the form:
[ToolboxData("<{0}:MyPlugin runat=server></{0}:MyPlugin>")]
public class MyPlugin : WebControl, PluginSystem.Interface.IMyPlugins
{
protected override void RenderContents(HtmlTextWriter output)
{
...
_btn = new Button();
_btn.ID = "btnSave";
this.Controls.Add(_btn);
_btn.Click += new EventHandler(btn_Click);
_btn.RenderControl(output);
}
void btn_Click(object sender, EventArgs e)
{
//do something. This doesn't fire
}
}
The controls are loaded from their assemblies:
public static IMyPlugins GetPlugin(string assembly, string type)
{
var t = Type.GetType(type + ", " + assembly);
IMyPlugins rtn = (IMyPlugins)Activator.CreateInstance(t);
rtn.Initialise();
return rtn;
}
How do I inject the loaded assembly into a page so that the events in the control will fire? Is that possible?
Thanks for any help!
You're adding the controls way too late in the page lifecycle. Add the server side controls in the OnInit Page event is your best bet.
Check out this link for an overview of the lifecycle process. The controls need to be created by the time the postback event handling happens. This series is also really good.
Dynamically changing the page based on user choice can be a pain in the ass because of this. There are a few options to go with. The easiest way is to add all your controls in the OnInit and then remove them when you know the selection the user made.
If it's truly completely dynamic, and you have no idea what control you are going to render until after the postbacks have been handled, it can be easier to step away from ASP.NET's viewstate/postback system and check things yourself. You can always get the full range of post values at any time in the page lifecycle by checking the Form.Request collection.
Related
I have composite web server control, which at the moment doesn't perform any actions. My aim is to place inside it child controls beginning with checkbox. I try to do it in the following way:
[DefaultProperty("Text")]
[ToolboxData("<{0}:SubmitImageControl runat=\"server\"></{0}:SubmitImageControl>")]
public class SubmitImageControl : CompositeControl
{
private CheckBox _checkBox;
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
}
protected override void CreateChildControls()
{
_checkBox = new CheckBox();
Controls.Add(_checkBox);
base.CreateChildControls();
}
protected override void RenderContents(HtmlTextWriter output)
{
_checkBox.RenderControl(output);
}
}
Registering and placing on the page:
<%# Register TagPrefix="uc" Namespace="PostBackHandlerApp.Controls" Assembly="PostBackHandlerApp" %>
<uc:SubmitImageControl runat="server" />
Checkbox appears on the page and everything seems fine until we look at the view state. Its value is
/wEPDwULLTExMTg2MzM0NjJkGAEFHl9fQ29udHJvbHNSZXF1aXJlUG9zdEJhY2tLZXlfXxYBBR1jdGwwMCRNYWluQ29udGVudCRjdGwwMCRjdGwwMD+PWeqrbtVyQSNMxvfjcmJkKAwpIuEPWJd+m5W6eJtQ
Then, if we simply remove the code Controls.Add(_checkBox);, view state size reduces greatly:
/wEPDwULLTExMTg2MzM0NjJkZLrri0oSGPS9ZiOTsRtSageoskXzCME4KCdRZxOiJyR9
If I move the code of child initialization and adding to OnInit method of my control (where, as far as I know, view state tracing is still disabled), result stays the same. Also, this MSDN article recommends to perform initialization only in CreateChildControls method:
You should create the child controls in the CreateChildControls method and not in OnInit or another life cycle phase. The server control architecture relies on calls to CreateChildControls whenever the Controls collection is needed, such as during data binding (if applicable).
Could anyone explain me why view state becomes larger? Thanks in advance.
Have you tried disabling viewstate for the checkbox in the control. I preusme the viewstate has to account for this control unless you specify otherwise? If you want to easily use this control within the lifecycle though you would want to leave the viewstate enabled.
The reason why viewstate is populated is implementing IPostBackDataHandler interface by large part of data controls, including checkbox. Interface's method LoadPostData is called automatically after the LoadViewState event and viewstate gets populated from the posted data.
Here is nice article about it.
I am having a rather odd problem with the Gecko Webbrowser control, I have created my own class which inherits off of the Gecko Webcontrol and within the constructor of this I have set an event:
class fooGeckoClass: Gecko.GeckoWebBrowser
{
public fooGeckoClass()
{
this.DomClick += new EventHandler<Gecko.GeckoDomEventArgs>(fooEventFunction);
}
private static void fooEventFunction(Object sender, Gecko.GeckoDomEventArgs e)
{
((Gecko.GeckoWebBrowser)sender).Navigate("www.foo.com");
}
}
I am using three of these controls in a manually created UserControl, the controls are loaded in dynamically at start up from a config file and added the the UserControl controls collection. When clicking on any of the three controls, all three will navigate to "www.foo.com" away from there original site. I had a look at:
e.StopPropagation();
Which specifies that it stops further propagation of events during an event flow, however it does also specify that it will handle all events in the current flow, I believe the events must have already been given to the controls before this has a chance to stop it as the the three controls will still fire the event. I also tried e.Handled = true to no avail. Has anyone encountered this problem before and have any kind of solution to make it only fire on the control that was clicked on?
EDIT:
It may be worth showing how the controls are added to the form seeing as this must be where the problem is occurring (it does not happen if the controls are just placed in a user control in a small test app).
private void fooUserControl_Load(object sender, EventArgs e)
{
if (!this.DesignMode)
{
for (int iControls = 0; iControls < geckObs.Count(); iControls ++)
{
fooGeckoClass geckControl = new fooGeckoClass();
this.Controls.Add(geckControl );
break;
}
}
}
Odd answer but I seem to have resolved the issue, DomClick was being called at first run, changing to DomMouseClick or DomMouseUp has completely resolved the issue. I assume DomClick must be an event unto itself as it also doesn't use the GeckoDomMouseEventArgs but the regular GeckoEventArgs.
EDIT:
To add to this, the site I was going to was actually calling DomClick when it had finished loading hence the reason it was being called at start up across all three browsers.
I'm developing a custom server control in Asp.NET (.NET 3.5) which inherits the CompositeControl class. Inside my control I'm overriding the CreateChildControls() method to generate a mixture of html and Asp.NET server controls. Some of the Asp.NET controls which are added are LinkButtons (which each have their Command event handler set to a method within my control). What I'm finding is that the first time one of these LinkButtons is clicked a postback is triggered and the event handler method is correctly fired. Inside this event handler method CreateChildControls() is explicitly called to regenerate the control in response to the postback. What I then find is that subsequent clicks of the LinkButtons postbacks fail to raise the event handler method.
I assume that the way I'm handling the regeneration of the control on postback must be at fault, but I can't figure out what to do - I am aware of the fact that on that first postback CreateChildControls() is called twice which probably isn't ideal but since CreateChildControls is called before any events are raised, I don't see a way around this.
A simplified version of my control class is shown below:
public class SearchResults : CompositeControl
{
private int PageIndex = 0;
protected override void CreateChildControls()
{
//do stuff here e.g.
LinkButton prevLink = new LinkButton();
prevLink.Text = "< Prev";
prevLink.CommandArgument = (PageIndex - 1).ToString();
prevLink.Command += new CommandEventHandler(PagerLinkCommand);
this.Controls.Add(prevLink);
}
protected void PagerLinkCommand(object sender, CommandEventArgs e)
{
PageIndex = int.Parse(e.CommandArgument.ToString());
CreateChildControls();
}
}
EDIT
The problem here was caused by the fact that the control is used in a Sitecore site and I had forgotten to register the control type in the web.config file with a <typesThatShouldNotBeExpanded> entry. This entry is used to prevent server controls from having their events messed up by Sitecore - this can cause similar problems for standard server controls such as ListView, GridView and Repeater etc. My web.config was modified as shown below:
<typesThatShouldNotBeExpanded>
<type>System.Web.UI.WebControls.Repeater</type>
<type>System.Web.UI.WebControls.DataList</type>
<type>System.Web.UI.WebControls.GridView</type>
<type>MyNamespace.MyCustomControl</type> <!-- This is the bit I added -->
</typesThatShouldNotBeExpanded>
In my experience this sort of problem is usually due to not assigning an ID to dynamically generated controls.
LinkButton prevLink = new LinkButton();
prevLink.ID = "prevLink";
Apologies... this is not a complete answer, but a debugging suggestion that is too long for a comment:
In your browser save an HTML copy of your page for initial load, postback load, and second postback. Then compare the files using your favorite comparison tool. Eliminate obvious differences like search results, etc. This can help you pinpoint any issues with control IDs, missing controls, etc.
The two absolute keys to successful dynamically created controls are
1) Creating them at the correct time during the page lifecycle
2) Re-creating the EXACT SAME control hierarchy (including IDs) on postback
To get the proper control tree override the Controls property and call the EnsureChildControls, and also call the EnsureChildControls and not the CreateChildControls inside the PagerLinkCommand.
/// <summary>
/// Gets controls.
/// </summary>
public override ControlCollection Controls
{
get
{
EnsureChildControls();
return base.Controls;
}
}
/// <summary>
/// Create child controls.
/// </summary>
protected override void CreateChildControls()
{
this.Controls.Clear();
//do stuff here e.g.
LinkButton prevLink = new LinkButton();
prevLink.Text = "< Prev";
prevLink.CommandArgument = (PageIndex - 1).ToString();
prevLink.Command += new CommandEventHandler(PagerLinkCommand);
this.Controls.Add(prevLink);
}
protected void PagerLinkCommand(object sender, CommandEventArgs e)
{
PageIndex = int.Parse(e.CommandArgument.ToString());
EnsureChildControls();
}
The reason for this behaviour was not down to the server control itself, but was Sitecore-related. In order for Sitecore to not interfere with server control postbacks, it is necessary to add an entry under the typesThatShouldNotBeExpanded section in the web.config file as shown below.
<typesThatShouldNotBeExpanded>
<type>System.Web.UI.WebControls.Repeater</type>
<type>System.Web.UI.WebControls.DataList</type>
<type>System.Web.UI.WebControls.GridView</type>
<type>MyNamespace.MyCustomControl</type> <!-- This is the bit I added -->
</typesThatShouldNotBeExpanded>
Ok I'm trying to understand how best to handle ViewState, for the programmatic setting of default values using C#. I understand that the construction of the ViewState hidden field is based on every value that is set after the OnInit event is triggered. What I'm not clear about is if there is a difference between using the control's constructor or the OnInit event to set default values.
public MyControl(){
this.Text = "SomeDefaultValue";
}
versus
protected override void OnInit(EventArgs e){
this.Text = "SomeDefaultValue";
}
I've seen some places that suggest testing the ViewState value for null in the get of the given property, like so:
public string Text {
get {
return this.ViewState["Text"] == null ?
"SomeDefaultValue" :
this.ViewState["Text"] as string;
}
set { this.ViewState["Text"] = value; }
}
I don't like that because it makes clearing the value confusing.
So, Is there any functional difference between using the constructor vs OnInit to set default ViewState values?
In terms of minimizing ViewState, there is no difference, as ViewState starts tracking after the OnInit method is run.
There are some functional differences, however: until the control is initialized, you cannot access other properties like the Page. For this reason, I usually prefer to use either OnInit or some handler tied to the Init event.
Also, be careful about overriding OnInit: you should call base.OnInit() to make sure that other event handlers for the Init event still get called.
I highly recommend that you read this excellent article on the topic: http://weblogs.asp.net/infinitiesloop/archive/2006/08/03/Truly-Understanding-Viewstate.aspx
Edit
To clarify, the ViewState starts tracking for a given control after the OnInit method is run for that control. So in the given example, you are safe to override OnInit like this:
protected override void OnInit(EventArgs e){
this.Text = "SomeDefaultValue"; // Make sure this happens before base.OnInit
base.OnInit();
}
This works because the Text property is saving the value to the ViewState of this control. However, let's say you have another child control (I'll use a Label as an example). That Label's OnInit will already have been run by the time your control's OnInit method is called. So if you want to change the Label's Text value, you'll need to do it during that label's OnInit phase (or sooner).
You could do it in the constructor of the current control:
public MyControl(){
this.Label.Text = "SomeDefaultValue";
}
... but as mentioned earlier you won't have access to the external control structure, which may be necessary in some cases. A good alternative in these cases is to use an Init event handler on the label itself. You can hook up the event handler itself in your constructor:
public MyControl(){
this.Label.Init +=
(sender, e) => this.Label.Text =
((TextBox)Page.FindControl("SomeControl")).Text;
}
... but this will only work if the control is declared directly as a member of your class. If the label is inside a template (like in a Repeater), you'll need to use markup to hook it up:
<asp:Label runat="server" OnInit="Label_Init" />
with the code-behind:
public void Label_Init(object sender, EventArgs e)
{
var label = (Label)sender;
label.Text = ((TextBox)Page.FindControl("SomeControl")).Text;
}
This latter example has the advantage of working in just about every circumstance I can think of, but it requires more boilerplate code, as well as a change in markup. So pick your poison based on your specific situation.
There is quite a detailed document on the ViewState over at MSDN:
...server controls don't begin tracking
view state changes until right at the
end of the initialization stage.
Second, when adding dynamic controls
that need to utilize view state, these
controls will need to be added during
the Page's Init event as opposed to
the Load event.
Just from this alone, I would say, if you're utilising the ViewState, use OnInit.
I have a dialog with loads of control in it. Each and evey control will be populated during the loading sequence and it might take a while for it to get completely filled. Mean while, I don't wanna allow the user to click on any of the controls. In other words, I wanna disable the control from receiving the events and I don't wanna disable the controls (as it might look odd).Also, I don't wanna subscribe and unsubscribe for the events regular intervals. Is there any way to stop the controls from listening to the events for a brief time ??
Sudarsan Srinivasan
The whole point of disabling controls is to communicate to the user that the control cannot be used at a particular time. This is a convention that users have learned and are used to, so I would advice to follow that. Not doing that may confuse the users.
The easiest way is to disable the container in which the controls are located in, rather than disabling each and every control. A better way (or at least the way that I prefer) is to have a method that will control the Visible and Enabled properties of controls based on which state the UI is in.
The easiest way is to move the control population out of the load event (if possible). Then in Load do something like:
private bool _LoadComplete;
void OnFormLoad(Object sender, EventArgs e)
{
_LoadComplete = true;
InitializeControls();
_LoadComplete = false;
}
void InitializeControls()
{
// Populate Controls
}
void OnSomeControlEvent()
{
if (_LoadComplete)
{
// Handle the event
}
}
Edit A Couple other Ideas:
Set the Application.Cursor = WaitCursor (typically will disallow clicking, but not a 100% guarantee)
Create a "Spinner" control to let the user know that the screen is busy. When loading bring it to the front so it sits on top and covers all other controls. Once you're done loading set it to visible = false and show your other controls
Unfortunately the only way i know of is to have a class variable (called something like _loading) and in each control handler do something like:
If (! _loading )
{
...
}
And in your loading code set _loading = true; once you have finished loading.
If you just want to disable user input, then you can set the form's Enabled property to false.
This has the effect of blocking user input to any of the form's controls, without changing the appearance of the controls; it's the technique used internally by the ShowDialog method.