views:

362

answers:

2

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? :(

A: 

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.

Dave Swersky
+2  A: 

Hi,

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.

KP
to my sub-control I've added [ToolboxItem(false)], then I've exposed it as property of parent control with attribute [PersistenceMode(PersistenceMode.InnerProperty)]. I think it's not the "correct" way to do this, but at this time is the only one that do the trick...
tanathos
Why is this the 'incorrect' way? I believe you're on the right track. Another example using same attributes: http://www.robertwray.co.uk/blog/2008/02/describing-aspnet-control-properties-declaratively.html
KP
because in this way my sub control doesn't render itself automatically, it's a simple property of his parent, I've to implement a render logic at the parent level... honestly, I don't understand how the asp:TableRow and asp:Table works, I've see their code in reflector, but I didn't found a reason that makes TableRow invisible outside of Table....
tanathos
@tanathos - fair enough. I edited the answer, with a good link which describes how intellisense is generated. ToolboxItem looks to be the only attribute you need in order to hide the control from top-level visibility in your markup.
KP
Yes, it seems it's something involving ToolBoxItem. If I set it to false, the subcontrol doesn't appear on intellisense (at all), but the control works, it renders on the html, even if the tag is not recognised by VS. But I've tried every combination, I can't get it to appears only on the parent control... nothing or all, it seems...Finally I think that for now I'll implement a rendering system based upon attributes.Anyway, thanks for the time you spent helping me!
tanathos
No problem. This one was bugging me that there was no solution, so I wrote a very simple controls in a working example. See above! :)
KP
Great! It works well! Doing exactly what I need.With a basic trick you can also extend the support to use more controls inside the parent, simply creating new control inheriting from ChildControl, or from a base class.Thanks again man!
tanathos
no problem, glad to help!
KP