ASP.NET Custom/User Control With Children - asp.net

I want a create a custom/user control that has children.
For Example, I want my control to have the following markup:
<div runat="server" id="div">
<label runat="server" id="label"></label>
<div class="field">
<!-- INSERT CHILDREN HERE -->
</div>
</div>
and when I want to use it on a page I simply:
<ctr:MyUserControl runat="server" ID="myControl">
<span>This is a child</span>
<div runat="server" id="myChild">And another <b>child</b>
</ctr:MyUserControl>
The child controls inside my user control will be inserted into my user control somewhere. What is the best way to accomplish this?
The functionality is similar to a asp:PlaceHolder but I want to add a couple more options as well as additional markup and the such. Also the child controls still need to be able to be accessed by the page. (in the example above the page should have the myChild Control on it)
EDIT ------
It can be a template control as long as it allows me to reference the children on the page.

I asked something similar myself a while ago. See here.
I believe you will have to use an ITemplate as an InnerProperty:
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[TemplateInstance(TemplateInstance.Single)]
public ITemplate Content
{
get
{
return _content;
}
set
{
_content = value;
}
}
private ITemplate _content;
Then override the CreateChildControls method of your control:
protected override void CreateChildControls()
{
if (this.Content != null)
{
this.Controls.Clear();
this.Content.InstantiateIn(this);
}
base.CreateChildControls();
}
What's the harm in using an ITemplate You can combine it with your existing markup and write whatever HTML you want within the Content property.

Another way to approach this would be to look at the source of the Panel control (using Reflector for example). It looks like it just overrides the RenderBeginTag and RenderEndTag methods (among others to add attributes and whatnot) and defers the rest of the rendering to the WebControl class.

I know that the answer is a bit old but I have a problem which was not mentioned here.
I've tried this solution and it works well if the content are default aspx controls or plain html tags. When I put a custom web control inside I have a problem with NullReferenceException in the custom web control (child controls are all null). I overloaded OnInit method (in the custom web control code behind) to call EnsureChildControls() but child controls are not still instantiated. Do you have any idea or sugestions what the point is?
Here is the code which I use to instantiate the controls:
this._pnlButtons.Controls.Add( _lbtnOkHidden );
this._pnlButtons.Controls.Add( _lgbtnOk );
this._pnlPopup.Controls.Add( _pnlHeader );
this._pnlPopup.Controls.Add( _pnlContent );
this._pnlPopup.Controls.Add( _pnlButtons );
if ( this.Content != null )
this.Content.InstantiateIn( _pnlContent );
this._updatePanel.ContentTemplateContainer.Controls.Add( _lbShowPopup );
this._updatePanel.ContentTemplateContainer.Controls.Add( _lbtnShowPopupHidden );
this._updatePanel.ContentTemplateContainer.Controls.Add( _pnlPopup );
this._updatePanel.ContentTemplateContainer.Controls.Add( _modalExtender );
this.Controls.Add(_updatePanel);
base.CreateChildControls();

Related

ASP.Net User control How to read the child node

I have a user control defined like this
<%# Control .....
<Test:MyCustomControl id="xxx" runat="server>
</Test:MyCustomControl>
I would like to use this control on a page like
<Tag:MyControl runat="server">
<div>
my html
</div>
</Tag...
In my custom control codebehind I would like to read the inner html and set it to a property of Test:MyCustomcontrol
Currently I am getting an error saying that "...does not have property div"
How can I do this?
Note: For clarification the inner html can be an arbitrary html, so I need a way to read anything that user has typed in the page.
you can expose the div(running on the server) as a property from your UserControl
on the usercontrol html:
<div id="dvSomething" runat="server"></div>
on ur usercontrol codebehind ".cs file":
public System.Web.UI.HtmlControls.HtmlGenericControl TheDiv
{
get
{
return dvSomething;
}
set {
dvSomething = value;
}
}
on the page that contains the usercontrol:
WebUserControl11.TheDiv.InnerHtml = "addin something to div from page";
good luck
I will keep the other question open in case someone need a different solution:
I'm not sure about your requeriments like but here are two solutions, I hope this is what you want:
one adding control(you can add any by the way, not only textbox) and other pure html as per you described
TextBox txtAdd = new TextBox();
txtAdd.Style.Add("width", "200px");
WebUserControl11.Controls.Add(txtAdd);
TextBox txtRead = (TextBox)WebUserControl11.Controls[1];
((System.Web.UI.HtmlControls.HtmlGenericControl)WebUserControl11.Controls[0]).InnerHtml = "<b>something</b>";
string currentHtml = ((System.Web.UI.HtmlControls.HtmlGenericControl)WebUserControl11.Controls[0]).InnerHtml;
of course, the index will change depending how many elements you have on your user control

Paypal Form Ruins My ASP.NET webforms layout -> How to Solve?

I am a student who is doing up a simple website in asp.net. My problem is, I wish to integrate Paypal on one of the pages, but asp.net has the ridiculous <form runat="server"> that is getting in my way. I am building a simple site layout using blueprint css, a very basic three-column layout. However, I need my main content section to be able to use the paypal form (buy now button), and the other areas of the site to use user controls, which I presume requires them to be wrapped in that irritating form tag. In fact, I would like to have a sitemap path control at the top of the main section of the site: something very basic. How might I achieve that? My problem is: I can't put the Paypal button in the form, and I don't know how to shift a 4th div into place. I am not even sure how divs and forms stack on each other.
Could I have some help please?
The page with the problem is: http://clubofpep.org/sandbox/sandbox_Alumni.aspx.
Contrary to popular belief, you can have more than one form on ASP.Net webforms pages. What you cannot do is have more than one form with runat="server", nest a second form inside ASP.Net's main form, or use asp.net server controls outside the main form.
Therefore, to integrate a separate paypal form with the rest of an asp.net webforms page, you have to make sure that you can put it either before or after all of the asp.net web controls on the page, and then edit the aspx markup to make sure your paypal form is completely outside of asp.net's form.
The other thing is that a quick web search shows a multitude of paypal controls written for asp.net that will work with the required asp.net form to submit the payment. You could always try one of those.
namespace CustomForm
{
public class GhostForm : System.Web.UI.HtmlControls.HtmlForm
{
protected bool _render;
public bool RenderFormTag
{
get { return _render; }
set { _render = value; }
}
public GhostForm()
{
//By default, show the form tag
_render = true;
}
protected override void RenderBeginTag(HtmlTextWriter writer)
{
//Only render the tag when _render is set to true
if (_render)
base.RenderBeginTag(writer);
}
protected override void RenderEndTag(HtmlTextWriter writer)
{
//Only render the tag when _render is set to true
if (_render)
base.RenderEndTag(writer);
}
}
}
USAGE:
ASPX:
<%# Register TagPrefix="CF" Namespace="CustomForm" Assembly="CustomForm" %>
<body>
<CF:GhostForm id="mainForm" runat="server">
...
</body>
<img src="https://www.sandbox.paypal.com/en_US/i/btn/btn_xpressCheckout.gif"> <asp:Button ID="checkoutBtn" runat="server" OnClick="CheckButton_Click"
Text="Checkout" Width="100" CausesValidation="false" />
Code-Behind:
protected void Page_Load(object sender, EventArgs e)
{
...
// Workaround for PayPal form problem
GhostForm mainForm = new GhostForm();
mainForm.RenderFormTag = false;
// Go ahead and submit to PayPal :)
}

How can I include additional markup within a 'Content' inner property of an ASP.Net WebControl?

I've searched the site and I cannot find a solution for my problem, so apologies if it's already been answered (I'm sure someone must have asked this before).
I have written a jQuery Popup window that I've packaged up as a WebControl and IScriptControl. The last step is to be able to write the markup within the tags of my control. I've used the InnerProperty attribute a few times, but only for including lists of strongly typed classes.
Here's my property on the WebControl:
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public something??? Content
{
get
{
if (_content == null)
{
_content = new something???();
}
return _content;
}
}
private something??? _content;
Here's the HTML Markup of what I'm after:
<ctr:WebPopup runat="server" ID="win_Test" Hidden="false" Width="100px" Height="100px"
Modal="true" WindowCaption="Test Window" CssClass="window">
<Content>
<div style="display:none;">
<asp:Button runat="server" ID="Button1" OnClick="Button1_Click" />
</div>
<%--Etc--%>
<%--Etc--%>
</Content>
</ctr:WebPopup>
Unfortunately I don't know what type my Content property should be. I basically need to replicate the UpdatePanel's ContentTemplate.
EDIT: So the following allows a Template container to be automatically created, but no controls show up, what's wrong with what I'm doing?
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ITemplate Content
{
get
{
return _content;
}
set
{
_content = value;
}
}
private ITemplate _content;
EDIT2: Overriding the CreateChildControls allows the controls within the ITemplate to be rendered:
protected override void CreateChildControls()
{
if (this.Content != null)
{
this.Controls.Clear();
this.Content.InstantiateIn(this);
}
base.CreateChildControls();
}
Unfortunately I cannot now access the controls within the ITemplate from the codebehind file on the file. I.e. if I put a button within my mark as so:
<ctr:WebPopup runat="server" ID="win_StatusFilter">
<Content>
<asp:Button runat="server" ID="btn_Test" Text="Cannot access this from code behind?" />
</Content>
</ctr:WebPopup>
I then cannot access btn_Test from the code behind:
protected void Page_Load(object sender, EventArgs e)
{
btn_Test.Text = "btn_Test is not present in Intellisense and
is not accessible to the page. It does, however, render correctly.";
}
EDIT3: FIXED! Edit 2 is the correct soluion. It was just Visual Studios 2010 being a pain in the buttocks. Closed the app and reopened it and all my controls within the Content property were accessible on the page.
EDIT4: Edit 2 didn't fix the issue. I had already tried the [TemplateInstance(TemplateInstance.Single)] attribute before anyone had mentioned it, however at the time I didn't think it had made a difference. It appears Visual Studios 2010 is just being weird today.
Since I removed the tag and it carried on working, I assumed the attribute hadn't made a difference. Since going back to the code AGAIN the controls have become unavailable. Adding the attribute back in allowed it all to work and for the controls to be accessible server side. MADNESS. I will be accepting Brian's answer as he mentioned the fix before anyone else.
public ITemplate Content
which then you render to the UI like:
Label label = new Label();
this.Content.InstantiateIn(label);
//Render label
EDIT: Make sure the template also defines
[TemplateInstance(TemplateInstance.Single)]
as this allows you to access the controls within the template directly.
You should try to use this:
win_StatusFilter.FindControl("btn_Test") // this will be a Control
win_StatusFilter.FindControl("btn_Test") as Button // this will be a Button if control found, otherwise it will be null.
Otherwise you should define some properties for your control, like in this article:
http://msdn.microsoft.com/ru-ru/library/36574bf6%28v=VS.90%29.aspx
Update:
According the remarks in this article about ContentTemplate property of the UpdatePanel:
http://msdn.microsoft.com/en-us/library/system.web.ui.updatepanel.contenttemplate(v=VS.90).aspx
you can get controls from ContentTemplate because of TemplateInstanceAttribute value (UpdatePanel.ContentTemplate has the TemplateInstance.Single).
So you should only use this code:
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[TemplateInstance(TemplateInstance.Single)]
public ITemplate Content
More information at:
http://msdn.microsoft.com/ru-ru/library/system.web.ui.templateinstanceattribute(v=VS.90).aspx

Asp.Net Custom Control design time question

I'm developing a set of custom controls, one of them are the "parent", any others must be added inside an ITemplate property of this parent.
But in the page, at design time, I can see, by intellisense, the other controls at page level, and I can theorically add them everywhere.
I want to emulate the behaviour of the asp:Table and the asp:TableRow, you can't directly add an asp:TableRow outside an asp:Table...
Is there a way to reach this?
Many thanks!
edit: I've partially solved with the KP suggestion, but if you read at the comment it's not the "real" way to do this (I think).
No one knows how to do that? :(
I've edited the entire answer based on our discussion. Here's a working and tested example. We have below two controls - ParentControl and ChildControl. ParentControl is visible via Intellisense, where ChildControl is only visible as a child of ParentControl as you wanted. For simple rendering purposes, the children render as li tags and output their 'text' property. The parent control ensures each child is asked to render during its own RenderContents event.
Child Control:
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace TestControls
{
[ToolboxItem(false), Bindable(false)]
public class ChildControl : WebControl
{
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
base.Render(writer);
//render the text property as a list item for example's sake
writer.RenderBeginTag(HtmlTextWriterTag.Li);
writer.Write(this.Text);
writer.RenderEndTag();
}
[Browsable(true)]
public string Text { get; set; }
}
}
Parent Control:
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace TestControls
{
[ToolboxData("<{0}:ParentControl runat=\"server\"></{0}:ParentControl>")]
[DefaultProperty("Children"), ParseChildren(true, "Children")]
public class ParentControl : WebControl
{
private List<ChildControl> _children;
protected override void RenderContents(HtmlTextWriter writer)
{
base.RenderContents(writer);
//create a div, and write some sample text
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.Write("Parent Control. Children:");
//create a ul, and ask each child control to render
writer.RenderBeginTag(HtmlTextWriterTag.Ul);
foreach (ChildControl child in _children)
{
child.RenderControl(writer);
}
//close all tags
writer.RenderEndTag();
writer.RenderEndTag();
}
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public virtual List<ChildControl> Children
{
get
{
if (_children == null)
_children = new List<ChildControl>();
return _children;
}
}
}
}
In my markup, I registered the controls via namespace:
<%# Register TagPrefix="test" Namespace="TestControls" %>
And then added some markup:
<test:ParentControl ID="test" runat="server">
<test:ChildControl ID="child" Text="Hello World from Child 1" runat="server" />
<test:ChildControl ID="child2" Text="Hello World from Child 2" runat="server" />
</test:ParentControl>
In the above markup, Intellisense picks up on the outer parent control, but does not see the child control. Once the cursor is inside the parent control, Intellisense picks up on the ChildControl tag as desired.
The final output is:
Parent Control. Children:
* Hello World from Child 1
* Hello World from Child 2
Also , here's a good article on how the whole intellisence creation works, which I followed to create the above.
I hope this helps. You'd still have to deal with rendering at the child control level in the way you see fit for your specific controls, however the above gets you started and does meet the need of a working Intellisense model.
I've done this before but I don't have the code available. I can tell you that I figured it out by using Reflector on the built-in ASP.NET Datagrid control. I was able to reverse engineer the relationship between the "contained" ("row") and "container" ("grid"). You have to arrange the classes together in a very specific way using attributes.

ASP.NET RenderControl or RenderChildren fail

I need to use objChildControl.RenderControl or objControl.RenderChildren to manually render my child controls. But it looks like these methods are incomplete.
All my child controls use the OnPreRender event to register clientscript and client stylesheets (since these can only be created in the prerender event).
I have 2 main issues, passing the current System.Web.UI.Page object to a child control and making sure the OnPreRender event is fired on these child controls.
It seems that I can't use the RenderControl method on my child controls since the OnPreRender event will not be called.
I can however pass the Page object by objChildControl.Page = Me.Page
When I use RenderChildren I cannot pass the Page object, or can I?
And i'm not sure if the OnPreRender event is even called when I use RenderChildren.
Some help would be appreciated, since i'm stuck ;)
Update
I found a way to get the result I need, but it is not the solution I want.
Example:
Code I want:
<wc:ParentControl id="objParent" runat="server" bla="etc">
<Content> <!-- This is an InnerProperty of the ParentControl --><DIV>bla bla bla bla.....<wc:SomeControl id="objSomeControl" runat="server" /><wc:3rdPartyControl id="obj3rdPartyControl" runat="server" /></DIV></Content>
</wc:ParentControl>
CodeBehind: objParentControl.Content.RenderControl(Writer)
And then the issues mentioned above will begin. How to make sure that for all the children within Content the OnPreRender will be called?
Code which does work (but then the RenderControl method is just useless):
<wc:ParentControl id="objParentControl" runat="server"></wc:ParentControl>
<wc:Content id="objContent" runat="server"><DIV>bla bla bla bla.....<wc:SomeControl id="objSomeControl" runat="server" /><wc:3rdPartyControl id="obj3rdPartyControl" runat="server" /></DIV></wc:Content>
Then just use the RenderBeginTag and RenderEndTag of the wc:Content control.
Then the OnPreRender event is called.
But I wan't to embed the content into the parentcontrol by using an InnerProperty.
And then manually rendering the childcontrols by RenderControl or RenderChildren.
I had a similar issue. I'm not sure if it's the same issue you're experiencing, but the problem I was having was that I had a ParseChildren(true) attribute on my container control. Because ParseChildren was true, the child controls would be put into a property, rather than into the containing control's child controls collection, and would never get their OnPreRender function called.
I ended up overriding the CreateChildControls function in my containing control class, where I added everything from my parsed collection to the Controls collection. Because I'm overriding the Render function anyway, I don't worry about the controls in the Controls collection being rendered when I didn't want them to be.
Something like below:
[ParseChildren(true, "MyKids")]
public class Example : Control {
private ArrayList _kids = new ArrayList();
public ArrayList MyKids {
get { return _kids; }
set { _kids = value; }
}
protected override CreateChildControls() {
Controls.Clear();
foreach(Control c in _kids)
Controls.Add(c);
}
protected override Render(HtmlTextWriter writer) {
...
}
}

Resources