Dynamically adding controls to a Templated User Control? - c#

A few weeks back I created a Templated User Control, for the most part based on the example here:
http://msdn.microsoft.com/en-us/library/36574bf6(v=VS.90).aspx
The difference is that I did not implement the "MessageContainer" class as I wanted just an empty template that I could add whatever controls I wanted to at design time.
This TUC has been working great, but I ran into a scenario I hadn't anticipated when I created the thing. The need to dynamically add this TUC to a page, which means that I would need to dynamically add the controls within the template of the TUC as well.
I found another example here on how to dynamically create a Template and add it to the Templated Control:
http://msdn.microsoft.com/en-us/library/y0h809ak(v=vs.71).aspx
This second example article discusses only the "DataList, Repeater, and DataGrid controls" but I figure since I am using the ITemplate interface here, it should be the same thing.
However, I am unable to get this to work, I keep getting an "Object reference not set to an instance of an object." error when I attempt to populate the TUC.
Here's what I am doing....
Like the example above I created an ITemplate class:
public class XPCTemplate : ITemplate
{
private readonly Control _control;
public XPCTemplate(Control control)
{
_control = control;
}
public void InstantiateIn(Control container)
{
container.Controls.Add(_control);
}
}
Then, in the test page code-behind I attempt to load up and display the TUC dynamically:
ExpandCollapseRegion xpcRegion; // The Templated User Control
protected void Page_Load(object sender, EventArgs e)
{
PlaceHolder ph;
// .... code here to dynamically create some labels, textboxes, etc. ....
// Create an instance of the TUC
xpcRegion = new ExpandCollapseRegion();
// Pass into the TUC's template the controls dynamically created above
xpcRegion.LayoutTemplate = new XPCTemplate(ph);
// add the dynamic TUC to the page
phDynamicUC.Controls.Add(xpcRegion);
phDynamicUC.Controls.Add(new LiteralControl("<br />"));
}
Test page HTML Source:
<body>
<form id="form1" runat="server">
<div>
Dynamically Loading User Control
<br />
<asp:PlaceHolder ID="phDynamicUC" runat="server" />
</div>
</form>
</body>
When I run the page, I get the "Object reference not set to an instance of an object" error on the "container.Controls.Add(_control);" line of the XPCTemplate class. When I debug the test page and TUC control, the code of the TUC receives the XPCTemplate into its LayoutTemplate during the TUC's Page_Init() method, but when the InstantiateIn() event back in the XPCTemplate fires immediately afterwards, the "container" argument is NULL.
I'm not sure why this is happening, it's like the InstantiateIn() method of the XPCTemplate class is trying to actually set the PlaceHolder control within the TUC rather than just passing the contents. Maybe this is supposed to be the way and I am missing something on the TUC side to allow this behavior?
This is the first TUC I have created and likewise the first time trying to dynamically fill/load it, so I am sure I am missing something needed to accomplish this. Any help is greatly appreciated.
-- Andrew

Found the solution to the problem, which was how I was loading the TUC.
Incorrect:
xpcRegion = new ExpandCollapseRegion();
Correct:
xpcRegion = (ExpandCollapseRegion)LoadControl("ExpandCollapseRegion.ascx");
Making this simple change took care of the problem. Also, found that I could forego the need for a custom ITemplate class by using the CompiledTemplateBuilder() method. Really simplifies the code.
-- Andrew

I believe the problem is that you haven't instantiated your PlaceHolder ph that you are passing into your template. So, the line it is complaining on is because it can't add the control to the PlaceHolder because it is null/nothing.
Also, I believe you're going to need to put your placeholder somewhere. Otherwise, you're adding items to a container that is never added to the page.

Related

How to properly instantiate a Custom User Control from Code Behind

I created a custom user control that has the following table as part of it (and I've simplified slightly to make this easier to read):
<table id="tblWeekCalendar" runat="server">
<!-- .... -->
</table>
I'm trying to create the Custom Control from Code Behind on a different page:
// I'm passing values in on the constructor as an alternative to using attributes
MasterScheduleCalendar sch = new MasterScheduleCalendar(true, 123);
However, when I try to access tblWeekCalendar (the main table from my custom control) from the custom control's code-behind while constructing the control, I find that it is null:
HtmlTableCell tableCell = tblWeekCalendar.FindControl($"tdR{week}Day{day}") as HtmlTableCell;
This code runs in a method that's called from Page_Load.
I tried doing the following instead of directly calling the constructor, as in this question, but still the same problem:
MasterScheduleCalendar ctrl = (MasterScheduleCalendar)Page.LoadControl(typeof(MasterScheduleCalendar), new object[] { true, schedule.MasterScheduleID });
As mentioned previously, this occurs in a method that's being called from the Page_Load event handler, so I'm slightly baffled as to why tblWeekCalendar is null at this point. Does anyone have any suggestions as to how to fix this?
So you are doing it wrong.
Do not try to access "tblWeekCalendar" or any other element inside your control.
Implement methods that recive a values and update your control, or methods that get values when you need.
MyWeekOfTheDay week = tblWeekCalendar.GetWeekOfTheDay();
List<MyDays> days = tblWeekCalendar.GetViewDays();
List<MyMonth> months = tblWeekCalendar.GetCurrentYear().GetMonths();
Something like that.

Accessing Template items of UserControl

Is it possible to implement custom UserControl or Control, which will act like a PlaceHolder, but with my logic? Example of using:
<x:MyControl runat="server">
<Template>
<asp:TextBox runat="server" ID="MyTextBox" />
..... any custom code here ........
</Template>
</x:MyControl>
And then in code-behind:
MyTextBox.Text = "ABC";
I implemented test control, but I am unable to access nested ASP controls on the page level. Error is The name "MyTextBox" does not exist in the current context.
You must call the function "FindControl" in you user control MyControl.
Name you MyControl with and id="mcContainer", then on your code behind call ((TextBox)mcContainer.FindControl("MyTextBox")).Text = "ABC";.
Wiki about this here How to: Create Templated ASP.NET User Controls
Why can I write MyTextBox.Text="ABC" when it is in the Page and why isn't that possible when the text box is inside of naming container implemented by a user control?
Well, when you drag N dropped your text box the ASP.NET Page designer declared a variable for you in the partial class of your page reserved for the designer itself, is in that designer part of your class that your controls in you page are declared.
So when you drag and drop your user control, and create a text box inside of it's naming container you can't access it directly because your page doesn't hold a reference to that control, the reason for that is because that control will only be available at runtime during the instanciation of the user control itself, the motive is related in part with the way that ASP.NET page parser renders controls.
Quoting this resource:
When working with composite controls it is important to be familiar with a number of properties and methods, as well as with the INamingContainer interface. All composite controls should implement the INamingContainer interface. Controls that implement this interface do not need to add any methods or properties; rather, the implemented interface merely indicates that the control is being used as a composite control. The effect is that child controls—that is, controls in the composite control's Controls collection—are rendered so that their ID is prefixed with the ID of the control's naming container. This ensures that all the child controls will have unique ID values, even if there are multiple instances of the parent control on a Form. The WebControl class has a NamingContainer property that returns the control's parent.
So, when you write MyTextBox, your text box name should at least something like $mcContainer$MyTextBox. So because of this there is no way to transparently do what you intend.
Solution.
Create class like this:
public class ToolTip : PlaceHolder
{
protected override void CreateChildControls()
{
HtmlGenericControl div1 = new HtmlGenericControl("div");
div1.Attributes.Add("id", ClientID);
div1.Attributes.Add("class", "tooltip");
while (Controls.Count != 0)
div1.Controls.Add(Controls[0]);
Controls.Add(div1);
}
}
Then register it in ASPX file like this:
<%# Register Assembly="YOUR_ASSEMBLY" TagPrefix="x" Namespace="YOUR_NAMESPACE" %>
Then use it like this:
<x:ToolTip runat="server">
... any your content here ......
</x:ToolTip>
Now you are able to nest any controls inside of x:ToolTip and add any composition logic to CreateChildControls().

Loading User Controls (ascx) from other User Controls

I have a UserControl (ascx) that, depending on the user's credentials, will load another UserControl (ascx). Currently the control to be loaded, contains a special navigation menu.
I am using this code:
UserControl jmNav =
(UserControl)Page.LoadControl("~/controls/client/jmNavigation.ascx");
Then, after some more code, I'm telling it to load, like this:
SBarTopWelcome.Controls.Add(jmNav);
The problem is, that I'm getting an "object reference not set to instance of an object" error.
Yes, the path is correct - as I tried it like this, as well (in all variations):
UserControl jmNav = (UserControl)Page.LoadControl("/client/jmNavigation.ascx");
This one (and its variants) tells me it doesn't exist.
So! Any thoughts?
One helpful solution is to add <%# Register %> to your parent control. Yes, I know it's in your parent page, but it should also be in your control.
If you do this, you should be able to Strongly-Type your control. For example, a control with a class name of MyControl would be:
MyControl controlVar = (MyControl)this.LoadControl("MyControl.aspx");
If you are able to get the stronly-defined variable, you should have no problems.
Inside SideBar.ascx add a place holder named SideBarTopWelcomePlaceHolder.
<asp:PlaceHolder ID="SideBarTopWelcomePlaceHolder" runat="server"/>
Then load jmNavigation UserControl to SideBarTopWelcomePlaceHolder like this.
Control jmNav =
Control Page.LoadControl("~/controls/client/jmNavigation.ascx");
SideBarTopWelcomePlaceHolder.Add(jmNav);
HA! I'm such a DORK! I was declaring the "SideBarWelcome" within a control like this:
public Control sideBarTopWelcome
{
get { return Page.FindControl("SideBarTopWelcome"); }
}
When I should have done it like this:
public Control sideBarTopWelcome
{
get { return FindControl("SideBarTopWelcome"); }
}
Without Page. Thank you anyway, guys. I appreciate it.

ASP.Net: User control with content area, it's clearly possible but I need some details

I have seen two suggestions for my original question about whether it is possible to define a content area inside a user control and there are some helpful suggestions i.e.
Passing in content to ASP.NET user control
and
ASP.NET User Control inner content
Now, I like the theory of the latter better than the former just for aesthetic reasons. It seems to make more sense to me but the example given uses two variables content and templateContent that the answerer has not defined in their example code. Without these details I have found that the example does not work. I guess they are properties of the control? Or some such?
EDIT - DETAILS: What I am trying to do
I have need of an ASP.Net user control that conceals some content in a panel inside a placeholder and asks for the input of a code in a visible panel.
Essentially the user will put their code into the provided textbox in Panel A and submit it, it will be checked and, if it is valid, panel B and the locked content will be displayed.
I have done a test where the content was hard coded into panel B but as soon as I need to make the content a generic input it fails. If it were just text or somesuch then I could make it a property of the control, but as it is, in fact, another User Control I am having some difficulty getting this into the "hidden" panel.
Any other workable solutions are also welcome.
EDIT NOTE: The solution I'm trying to implement this in 2.0 I did find a 3.5 solution which I cannot use.
The former example seems workable but I'd prefer to go with the latter if someone could fill in the blanks for me.
Thanks.
Okay, so this is disturbingly easy but many of the tutorials on the web that talk about this kind of thing push to do extravagant things that require the control to parse ListItems or such.
So this solution is purely so that you can build a control that, for whatever reason, has a placeholder in it that could have anything inside it (kind of like a content area on a Master page). In this instance it happens to be because the Panel containing the placeholder is hidden until appropriate input actions have taken place in another panel.
First, you need to add this:
[ParseChildren(true,"Content")]
[PersistChildren(false)]
just above the part of the control which looks like this:
public partial class MyControl : System.Web.UI.UserControl
then in the control scoped declarations at the head of the control you want to declare thus:
private Control _content;
[PersistenceMode(PersistenceMode.InnerProperty)]
public Control Content { get { return _content; } set { _content = value; } }
Finally you need to place the content into the placeholder like this:
phContent.Controls.Add((Control)_content);
That last line goes into the Page_Init event. For reference "phContent" is the name of the place holder where you want the content to appear, like this:
<asp:Panel ID="pnlLockable" runat="server" Visible="False">
<asp:Placeholder runat="server" ID="phContent" />
</asp:Panel>
On the front end the resulting implementation looks like this:
<uc:MyControl runat="server" ID="lockit1">
<Content>
//...your stuff goes here...
</Content>
<uc:MyControl>
Note that I presume that what is inbetween the Content Tags is a root control. This is because I nested another user control in there. I imagine if you put whatever content you want within a panel or placeholder it should be fine.
Also you can read "How to: Create Templated ASP.NET User Controls". Really helpful.

ASP.NET: generate property value when adding control to page

Context: ASP.NET 3.5 / C#
Hi,
I created a user control
public partial class MyControl : UserControl
{
// EDIT: example first used "UniqueId" as property name, which was wrong.
public Guid MyId { get; set; }
// ...
}
and this example usage
<uc1:MyControl
ID="myControl"
MyId="443CBF34-F75F-11DD-BE2F-68C555D89123"
runat="server" />
Steps:
Add this control to a web form (aspx)
Expected result:
the HTML for the user control is added, a unique value (corresponding to Guid.NewGuid()) for MyId is set in the ASPX HTML at design-time as the MyId attribute value.
Actual result:
the HTML for the user control is added, a unique value for MyId is not set in the HTML at design time for the MyId Attribute value.
If this is not possible:
Workaround 1: Would it be possible to achieve this using a server control? How?
Workaround 2: is it possible to achieve this using a UserControl design-mode task?
Clarification:
persisting the property value is not an issue, since it never changes for a control intance and is automatically set by ASP.NET through the control declaration in the aspx page.
the MyId attribute does not need to be rendered at runtime.
Gr B!
Custom Design-time Control Features in Visual Studio .NET
You have a couple problems here, but first I will answer your questions about the workarounds.
No you are already using a server control.
No design-mode is to just make the lives of the developer easy, it doesn't effect anything else
You have two problems here. There is already a property called UniqueID I don't know if you were trying to overload that, but the question wasn't clear. The second problem is that your UniqueID essentially not getting stored anywhere. Try the following code:
public Guid UniqueId {
get { return (Guid)ViewState["MyUserControlUniqueId"]; }
set { ViewState["MyUserControlUniqueId"] = value; }
}
That will store the GUID in the ViewState so that you can retrieve it on post backs.
Update: Given your comment you need to override/use this method to add attributes to the rendered content.
http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.webcontrol.addattributestorender.aspx
If I understand your question correctly, you are exposing a property on your user control called MyId. This allows you to set the property wherever you put that control.
What you also want, is for the rendered HTML to also include this attribute and value.
If that's the case, the property MyId is not passed through to the HTML, it's only because the user control has MyId as a property that it's visible in the designer.
In your user control you will have defined the markup that gets rendered.
So for example if you have:
<asp:Panel runat="Server" Id="myControlDiv">Some other content</asp:Panel>
You can then in your controls prerender event (or wherever else you choose) put
myControlDiv.Attributes.Add("MyId", SomeGuid.ToString())
Which will then get output in the HTML as
<div id="generatedID" MyID="443CBF34-F75F-11DD-BE2F-68C555D89123">Some other content</div>
So you just want a unique ID generated that you only ever use in design time?
Why not override Object.GetHasCode();
And then exposure this as a property?

Categories

Resources