Creating a UserControl Programmatically within a repeater? - asp.net

I have a repeater that is bound to some data.
I bind to the ItemDataBound event, and I am attempting to programmatically create a UserControl:
In a nutshell:
void rptrTaskList_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
CCTask task = (CCTask)e.Item.DataItem;
if (task is ExecTask)
{
ExecTaskControl foo = new ExecTaskControl();
e.Item.Controls.Add(foo);
}
}
The problem is that while the binding works, the user control is not rendered to the main page.

Eh, figured out one way to do it:
ExecTaskControl foo = (ExecTaskControl)LoadControl("tasks\\ExecTaskControl.ascx");
It seems silly to have a file depedancy like that, but maybe thats how UserControls must be done.

You could consider inverting the problem. That is add the control to the repeaters definition and the remove it if it is not needed. Not knowing the details of your app this might be a tremendous waste of time but it might just work out in the end.

If you are going to do it from a place where you don't have an instance of a page then you need to go one step further (e.g. from a webservice to return html or from a task rendering emails)
var myPage = new System.Web.UI.Page();
var myControl = (Controls.MemberRating)myPage.LoadControl("~/Controls/MemberRating.ascx");
I found this technique on Scott Guithrie's site so I assume it's the legit way to do it in .NET

I think that #Craig is on the right track depending on the details of the problem you are solving. Add it to the repeater and remove it or set Visible="false" to hide it where needed. Viewstate gets tricky with dynamically created controls/user controls, so google that or check here if you must add dynamically. The article referenced also shows an alternative way to load dynamically:
Control ctrl=this.LoadControl(Request.ApplicationPath +"/Controls/" +ControlName);

Related

(New to ViewStates) What is the best way(s) to save a dynamically generated control into the viewstate?

I am creating a web application in Asp.net and I'm still fairly new. I'm just starting to wrap my head around the basics of the ViewState. In my application, people are searching through a database and I give them ways to narrow their search. When they have entered a valid search constraint (ex: date past 10/1/11) I dynamically add another set of controls allowing them to add another constraint. I want to save the contents of the previous constraint (a set of Controls) so that I can still have it on the webpage when they enter the next constraint.
If it makes any difference, one constraint set consists of a drop-down list of attributes, a few literal control, and one or two text fields depending on what attribute was chosen from the drop down list.
How would I go about this?
Thanks so much guys.
The easiest way to track viewstate for dynamic controls is to recreate the controls in OnInit and assign the same ID to the controls every time the page is posted back. If the controls are assigned the same ID each time they're created, when the ViewState is loaded, the controls will be repopulated.
protected override void OnInit(EventArgs e)
{
TextBox txt = new TextBox();
txt.ID = "txt1";
this.Controls.Add(txt);
}
EDIT
To make things easier, try using the DynamicControlsPlaceHolder. Just put the control on the page, and it will persist the controls and their values behind the scenes:
http://www.denisbauer.com/ASPNETControls/DynamicControlsPlaceholder.aspx
Check out this link:
https://web.archive.org/web/20210330142645/http://www.4guysfromrolla.com/articles/092904-1.aspx
http://www.codeproject.com/KB/viewstate/retainingstate.aspx
ViewState for dynamic controls is still maintained by the ASP.NET framework. Just make sure you add them during init or preinit, because viewstate is loaded for every control between the init and load stages.

Nesting gridview/formview in webuser control inside a parent gridview

I'm developing an ASP.net 2 website for our HR department, where the front page has a matrix of all our departments against pay grades, with links in each cell to all the jobs for that department for that grade.
These links take you a page with a gridview populated dynamically, as each department has a different number of teams, e.g. Finance has one team, IT has four. Each cell has a webuser control inserted into it.
The user control has a sql datasource, pulling out all the job titles and the primary key, popuating a formview, with a linkbutton whose text value is bound to the job title.
(I'm using a usercontrol as this page will also be used to show the results of a search of all roles in a range of grades for a department, and will have a varying number of rows).
I've got everything to display nicely, but when I click on the linkbutton, instead of running the code I've put in the Click event, the page posts back without firing any events.
Having looked around, it looks like I have to put an addhandler line in somewhere, but I'm not sure where, could anyone give me some pointers please? (fairly numpty please, I'm not too experience in ASP yet and am winging it. I'm also using VB but C# isn't a problem)
This is how I'm inserting the controls into the parent grid, have I missed anything obvious?
For row As Int16 = 0 To dgvRoleGrid.Rows.Count - 1
tempwuc = New UserControl
tempwuc = LoadControl("wucRoleList.ascx")
tempwuc.ID = "wucRoleList" & col.ToString
tempwuc.EnableViewState = True
dgvRoleGrid.Rows(row).Cells(col).Controls.Add(tempwuc)
CType(dgvRoleGrid.Rows(row).FindControl(tempwuc.ID), wucRoleList).specialtyid = specid
CType(dgvRoleGrid.Rows(row).FindControl(tempwuc.ID), wucRoleList).bandid = dgvRoleGrid.DataKeys(row)(0)
CType(dgvRoleGrid.Rows(row).FindControl(tempwuc.ID), wucRoleList).familyid = Session("familyid")
Next
Hard to say without having a look at more of your code, but the most common reason for not receiving an event from a control in a repeater is that you are re-binding the data instead of relying on ViewState. Does your code look something like this:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if(!Page.IsPostBack) // <--- YOU REALLY NEED THIS!
{
DoDataBinding();
}
}
}
Are you missing the IsPostBack check? Also... why the heck are you using FindControl if you already have a handle to your control? Cast to the appropriate type when you declare/load the control.
The reason I'm doing it this way is I'm fairly new to all this, and winging it with whatever seems to work; with the deadlines I'm facing, I don't really have the time to find the most elegant solution, as long as it's stable and works, it'll do.
There is an ispostback check which doesn't rebind the datasource on postback.
I've got round the issue by using treeviews, using the databound event to amend the postback URL to include the value as parameter, but I'd still like to work out how to get this working properly.

How To: Use AJAX in an ASP.NET Custom Server Control

Does anyone know of a good tutorial that demonstrates using an existing AJAX control extender in a Custom ASP.NET Server Control?
I do not want to build a "Custom AJAX Server Control". I want to build a Custom Server Control that uses an existing AJAX control extender.
I would like to combine an asp:TextBox, asp:ImageButton, asp:CustomValidator (with client side javascript from an embedded resource), and an ajax:CalendarExtender into one custom server control. Or has this already been created?
Any help would be greatly appreciated. Thanks.
UPDATE: Basically, I would like to create a CompositeControl that has an ajax:CalendarExtender as a child control.
Sounds like what you're after is a composite control. They are pretty much exactly like a user control only instead of using the ascx file to create the controls you create them all programmatically. The big advantage of doing this over using a user control is you end up with something you can put in an assembly and use in different projects.
A composite control can inherit from either Control or WebControl. I personally usually find Control more useful to inherit from because I usually don't need a lot of the extra stuff you get from WebControl such as the styling properties since I usually just style through a single CssClass property.
You'll also need to make sure your class implements the INamingContainer interface. This will make sure that each child control will automatically get a unique name if the control is used multiple times in the same parent container.
The most important thing to do when creating a composite control is to override Control's CreateChildControls method. All the logic for actually creating the controls should go in here. The framework will automatically make sure that this gets called at the right time in the page lifecycle.
Here's a little example:
public class MyCompositeControl : Control, INamingContainer
{
protected override void CreateChildControls()
{
Controls.Clear();
var textbox = new TextBox();
textbox.ID = "TextBox1";
Controls.Add(textbox);
if (!Page.IsPostBack || !IsTrackingViewState)
{
// make sure you set the properties after
// you add it to the parent or the viewstate
// won't save properly
textbox.MaxLength = 30;
}
var button = new Button();
button.ID = "Button1";
Controls.Add(button);
if (!Page.IsPostBack || !IsTrackingViewState)
{
button.Text = "Save";
}
}
}
I don't think ASP.NET AJAX should complicate this much. The only thing I can think of ist you'll need to make sure that you create a ScriptManager on whatever page the composite control will be added to.
There's a full example of this on the MSDN site. There's another nice example on this blog.
What you want is to build a user control and not a custom control most probably. A user control is a composite control whereas a custom control is a control built either from the ground up either derived from a basic control.
I would suggest you search on MSDN. I have seen several good articles about that topic in their magazines over the last year or two, that have been fairly thorough. But I don't have links to them and I'm too lazy to Google for you. :\

What causes the 'Cannot unregister UpdatePanel' error?

I've got a UserControl that contains an UpdatePanel. When I put that on a page, it throws the following error:
Cannot unregister UpdatePanel with ID
'ReviewContentUpdatePanel' since it
was not registered with the
ScriptManager. This might occur if the
UpdatePanel was removed from the
control tree and later added again,
which is not supported. Parameter
name: updatePanel
ReviewContentUpdatePanel is the name of the update panel & it's not being removed or added in code, it exists in the aspx page and isn't removed. Has anyone come across this before?
This error occurs when the Controls collection in which the UpdatePanel is resided is cleared using the Clear method, or when the specific UpdatePanel is removed using the Remove method.
A trigger for these methods could be the implementation of the CreateChildControls method for the control contains the UpdatePanel. Usually, you call Controls.Clear() in the top of this method, to start with a clean slate if this method is called repeatedly.
Are you moving controls about in code? If so take a look here and see if this solves your problem.
I had this happen once before. To fix it, I just deleted it and then re-created it and the problem went away.
I hope this helps someone else as it drove me nuts. After finding various tidbits of info here and there on here and elsewhere, I finally came up with the following fix. Note, I am not dynamically creating this update panel here or anywhere else and most info out there was related to creating this control dynamically, which I was not.
I was using an update panel inside a web user control used on a page inherited by a master page with the script manager. I don't know if this combo was what was causing it, but this is how I fixed it (inside the web user control where the update panel is utilized):
protected override void OnInit(EventArgs e)
{
ScriptManager sm = ScriptManager.GetCurrent(this.Page);
MethodInfo m = (
from methods in typeof(ScriptManager).GetMethods(
BindingFlags.NonPublic | BindingFlags.Instance
)
where methods.Name.Equals("System.Web.UI.IScriptManagerInternal.RegisterUpdatePanel")
select methods).First<MethodInfo>();
m.Invoke(sm, new object[] { updatePanel });
base.OnInit(e);
}
In your markup, make sure you've specified an ID for both UpdatePanels and for every runat="server" control in their parent hierarchies.
Have you tried including a ScriptManagerProxy in the user control?
Try to remove the scriptproxy of UserControl. In this case you only have a ScriptManager on your page.
This is a bit of a long shot, but I've had experiences with the AJAX extensions, specifically with the update panel, in which errors thrown by child controls were manifesting themselves as a different error thrown by the update panel. I did see a reference to this specific error being thrown due to an error in a child control:
http://msmvps.com/blogs/shareblog/archive/2009/03/11/cannot-unregister-updatepanel-with-id-since-it-was-not-registered-with-the-scriptmanager-and-moss.aspx
Not sure if this is the case for you or not, but I've spent many hours chasing down the wrong errors because of this.

Child Control Initialization in Custom Composite in ASP.NET

Part of the series of controls I am working on obviously involves me lumping some of them together in to composites. I am rapidly starting to learn that this takes consideration (this is all new to me!) :)
I basically have a StyledWindow control, which is essentially a glorified Panel with ability to do other bits (like add borders etc).
Here is the code that instantiates the child controls within it. Up till this point it seems to have been working correctly with mundane static controls:
protected override void CreateChildControls()
{
_panel = new Panel();
if (_editable != null)
_editable.InstantiateIn(_panel);
_regions = new List<IAttributeAccessor>();
_regions.Add(_panel);
}
The problems came today when I tried nesting a more complex control within it. This control uses a reference to the page since it injects JavaScript in to make it a bit more snappy and responsive (the RegisterClientScriptBlock is the only reason I need the page ref).
Now, this was causing "object null" errors, but I localized this down to the render method, which was of course trying to call the method against the [null] Page object.
What's confusing me is that the control works fine as a standalone, but when placed in the StyledWindow it all goes horribly wrong!
So, it looks like I am missing something in either my StyledWindow or ChildControl. Any ideas?
Update
As Brad Wilson quite rightly pointed out, you do not see the controls being added to the Controls collection. This is what the _panel is for, this was there to handle that for me, basically then override Controls (I got this from a guide somewhere):
Panel _panel; // Sub-Control to store the "Content".
public override ControlCollection Controls
{
get
{
EnsureChildControls();
return _panel.Controls;
}
}
I hope that helps clarify things. Apologies.
Update Following Longhorn213's Answer
Right, I have been doing some playing with the control, placing one within the composite, and one outside. I then got the status of Page at event major event in the control Lifecycle and rendered it to the page.
The standalone is working fine and the page is inited as expected. However, the one nested in the Composite is different. It's OnLoad event is not being fired at all! So I am guessing Brad is probably right in that I am not setting up the control hierarchy correctly, can anyone offer some advice as to what I am missing? Is the Panel method not enough? (well, it obviously isn't is it?!) :D
Thanks for your help guys, appreciated :)
I don't see you adding your controls to the Controls collection anywhere, which would explain why they can't access the Page (since they've never been officially placed on the page).
I have always put the JavaScript calls on the OnLoad Function. Such as below.
protected override void OnLoad(EventArgs e)
{
// Do something to get the script
string script = GetScript();
this.Page.ClientScript.RegisterClientScriptBlock(this.Page.GetType(), "SomeJavaScriptName", script);
// Could also use this function to determine if the script has been register. i.e. more than 1 of the controls exists
this.Page.ClientScript.IsClientScriptBlockRegistered("SomeJavaScriptName");
base.OnLoad(e);
}
If you still want to do the render, then you can just write the script in the response. Which is what the RegisterScriptBlock does, it just puts the script inline on the page.
Solved!
Right, I was determined to get this cracked today! Here were my thoughts:
I thought the use of Panel was a bit of a hack, so I should remove it and find out how it is really done.
I didn't want to have to do something like MyCtl.Controls[0].Controls to access the controls added to the composite.
I wanted the damn thing to work!
So, I got searching and hit MSDN, this artcle was REALLY helpful (i.e. like almost copy 'n' paste, and explained well - something MSDN is traditionally bad at). Nice!
So, I ripped out the use of Panel and pretty much followed the artcle and took it as gospel, making notes as I went.
Here's what I have now:
I learned I was using the wrong term. I should have been calling it a Templated Control. While templated controls are technically composites, there is a distinct difference. Templated controls can define the interface for items that are added to them.
Templated controls are very powerful and actually pretty quick and easy to set up once you get your head round them!
I will play some more with the designer support to ensure I fully understand it all, then get a blog post up :)
A "Template" control is used to specify the interface for templated data.
For example, here is the ASPX markup for a templated control:
<cc1:TemplatedControl ID="MyCtl" runat="server">
<Template>
<!-- Templated Content Goes Here -->
</Template>
</cc1:TemplatedControl>
Heres the Code I Have Now
public class DummyWebControl : WebControl
{
// Acts as the surrogate for the templated controls.
// This is essentially the "interface" for the templated data.
}
In TemplateControl.cs...
ITemplate _template;
// Surrogate to hold the controls instantiated from
// within the template.
DummyWebControl _owner;
protected override void CreateChildControls()
{
// Note we are calling base.Controls here
// (you will see why in a min).
base.Controls.Clear();
_owner = new DummyWebControl();
// Load the Template Content
ITemplate template = _template;
if (template == null)
template = new StyledWindowDefaultTemplate();
template.InstantiateIn(_owner);
base.Controls.Add(_owner);
ChildControlsCreated = true;
}
Then, to provide easy access to the Controls of the [Surrogate] Object:
(this is why we needed to clear/add to the base.Controls)
public override ControlCollection Controls
{
get
{
EnsureChildControls();
return _owner.Controls;
}
}
And that is pretty much it, easy when you know how! :)
Next: Design Time Region Support!
Right, I got playing and I figured that there was something wrong with my control instantiation, since Longhorn was right, I should be able to create script references at OnLoad (and I couldn't), and Brad was right in that I need to ensure my Controls hierarchy was maintained by adding to the Controls collection of the composite.
So, I had two things here:
I had overriden the Controls property accessor for the composite to return this Panel's Controls collection since I dont want to have to go ctl.Controls[0].Controls[0] to get to the actual control I want. I have removed this, but I need to get this sorted.
I had not added the Panel to the Controls collection, I have now done this.
So, it now works, however, how do I get the Controls property for the composite to return the items in the Panel, rather than the Panel itself?

Resources