views:

328

answers:

6

I want to create a control just like a Panel.

I want my control to accept some controls as childs without typing the template name, just like the Panel, as shown here:

<asp:Panel runat="server">
    My content
    <div>Content</div>
</asp:Panel>

I have controls with content inside without telling what is the ITemplate.

I basically want to convert this

<my:MyControl runat="server">
    <ContentTemplate>
        My content
        <div>Content</div>
    </ContentTemplate>
</my:MyControl>

Into this

<my:MyControl runat="server">
    My content
    <div>Content</div>
</my:MyControl>

Here is what I've got:

public class MyControl : CompositeControl
{
    [TemplateInstance(TemplateInstance.Single)]
    public ITemplate Content { get; set; }

    protected override void CreateChildControls()
    {
        base.CreateChildControls();

        Content.InstantiateIn(this);
    }
}

The above works with <Content></Content> tags inside the control, but without it doesn't work. And the attribute isn't doing anything at all (I guess). What's missing?

How can I achieve it? Any hints? Why does Panel support this?

A: 

Build a Custom User Control and then utilize this to further expand your child controls.

http://www.akadia.com/services/dotnet_user_controls.html

    using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
namespace Akadia
{
    namespace SubmitButton
    {
        // User Control which contain a text box for your
        // name and a button that will fire an event.
        public class SubmitButtonControl : System.Windows.Forms.UserControl
        {
            private System.Windows.Forms.TextBox txtName;
            private System.Windows.Forms.Label lblName;
            private System.Windows.Forms.Button btnSubmit;
            private System.ComponentModel.Container components = null;
            // Declare delegate for submit button clicked.
            //
            // Most action events (like the Click event) in Windows Forms
            // use the EventHandler delegate and the EventArgs arguments.
            // We will define our own delegate that does not specify parameters.
            // Mostly, we really don't care what the conditions of the
            // click event for the Submit button were, we just care that
            // the Submit button was clicked.
            public delegate void SubmitClickedHandler();
            // Constructor           public SubmitButtonControl()
            {
                // Create visual controls
                InitializeComponent();
            }
            // Clean up any resources being used.
            protected override void Dispose( bool disposing )
            {
                if( disposing )
                {
                    if( components != null )
                        components.Dispose();
                }
                base.Dispose( disposing );
            }
            .....
            .....
            // Declare the event, which is associated with our
            // delegate SubmitClickedHandler(). Add some attributes
            // for the Visual C# control property.
            [Category("Action")]
            [Description("Fires when the Submit button is clicked.")]
            public event SubmitClickedHandler SubmitClicked;
            // Add a protected method called OnSubmitClicked().
            // You may use this in child classes instead of adding
            // event handlers.
            protected virtual void OnSubmitClicked()
            {
                // If an event has no subscribers registerd, it will
                // evaluate to null. The test checks that the value is not
                // null, ensuring that there are subsribers before
                // calling the event itself.
                if (SubmitClicked != null)
                {
                    SubmitClicked();  // Notify Subscribers
                }
            }
            // Handler for Submit Button. Do some validation before
            // calling the event.
            private void btnSubmit_Click(object sender, System.EventArgs e)
            {
                if (txtName.Text.Length == 0)
                {
                    MessageBox.Show("Please enter your name.");
                }
                else
                {
                    OnSubmitClicked();
                }
            }
            // Read / Write Property for the User Name. This Property
            // will be visible in the containing application.
            [Category("Appearance")]
            [Description("Gets or sets the name in the text box")]
            public string UserName
            {
                get { return txtName.Text; }
                set { txtName.Text = value; }
            }
        }
    }
}
Joe Garrett
I don't think this is what he's looking for. He wants to build a control where the children are unknown until declared in markup.
Joel Coehoorn
He is looking for a method to create them I think...and this is an example not necessarily what should be done...just something to get the primary muscle working. :)
Joe Garrett
Of course I could be totally wrong..but, to me it sounds like what he wants...
Joe Garrett
+2  A: 

Hey Bruno, the answer is nothing to do with usercontrols in terms of the way you want to nest controls.

It's actually to do with templating, and templates. That's how server controls are able to have specific controls nested in them.

This is usually found in databound controls.

The answer unfortunately is not trivial, but you can find a complete tutorial here

UPDATE

Hmm....I think where just complicating things. Whats stopping you from creating a custom control, nesting controls inside of it, and then accessing those control via the ControlCollection property of your parent control?

andy
How can I put controls inside `<StatsTemplate>` without placing this tag? Is it an annotation on the template?
BrunoLM
+4  A: 

Bruno, you are probably looking for this. And also a similar answer here.

Augusto Radtke
The attribute `[TemplateInstance(TemplateInstance.Single)]` doesn't seem to be working...
BrunoLM
+1  A: 

Depending on what you need to do with it, you could just create a subclass of the asp.net Panel control.

I created something like the following to encapsulate the common HTML I wanted on each of my own panels. (PanelWidth is an enum defining some standard sizings.)

public class StyledPanel : System.Web.UI.WebControls.Panel
{
    public PanelWidth PanelWidth { get; set; }

    public override void RenderBeginTag(HtmlTextWriter writer) {            
        string containerClass = string.Format("panel-container-{0}", (int)PanelWidth);
        writer.WriteLine("<div class=\"" + containerClass + "\"" + ">");
    }

    public override void RenderEndTag(HtmlTextWriter writer) {
        writer.WriteLine("</div>");
    }
}

Call this with, eg

<uc:StyledPanel PanelWidth="Full" runat="server">
  <div>Stuff</div>
</uc:StyledPanel>
David
That's a nice idea, but my control needs to inherit from another class, meaning I can't use this solution since C# doesn't work with multiple inheritance.
BrunoLM
+1  A: 

I'm writting this from memory but I believe the answer is a simple as decorating your control class with the attributes ParseChildrenAttribute and PersistChildrenAttribute and you do not need to work with templates as you are proposing.

ParseChildrenAttribute.ChildrenAsProperties specifies whether the parser should treat the nested content of the control tag as properties. Setting this to false will make the parser not to try to map the tags to property names.

The PersistChildrenAttribute will tell the parser to treat the nested content as controls and the parsed controls will be added as child controls to your custom panel control.

The code for your control would then look something like this:

[ParseChildren(false)]
[PersistChildren(true)]
public class MyControl : CompositeControl 
{
    public override void RenderBeginTag(HtmlTextWriter writer)
    {
        base.RenderBeginTag(writer); // TODO: Do something else here if needed
    }     

    public override void RenderEndTag(HtmlTextWriter writer)
    {
        base.RenderEndTag(writer); // TODO: Do something else here if needed
    }
} 

For reference you could fire up .NET Reflector and look at the implementation of the Panel control.

Martin Hyldahl
A: 

The first two lines make it work:

[ParseChildren(false)]
[PersistChildren(true)]
[ToolboxData("<{0}:MyPanel runat=server>Panel</{0}:MyPanel>")]
public class MyPanel : WebControl
{
}

Html:

<%@ Register Assembly="WebApplication3" Namespace="WebApplication3" TagPrefix="cc1" %>
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <cc1:MyPanel ID="MyPanel1" runat="server">
        <h2>
            Welcome to ASP.NET!
        </h2>
        <p>
            To learn more about ASP.NET visit <a href="http://www.asp.net" title="ASP.NET Website">
                www.asp.net</a>.
        </p>
        <p>
            You can also find <a href="http://go.microsoft.com/fwlink/?LinkID=152368&amp;amp;clcid=0x409"
                title="MSDN ASP.NET Docs">documentation on ASP.NET at MSDN</a>.
        </p>
    </cc1:MyPanel>
</asp:Content>
Jeroen