How to create a designer that behaves like the ASP:PANEL

C

Cameron Eckman

I am creating a custom server control where the user can add any content
similar to an ASP:pANEL. The custom control wraps the content in some extra
HTML above and below their content. I did not use a templated property as
it forces the developer to write more code in order to access any controls
they place within the template.

I started by inheriting from System.Web.UI.WebControls.Panel, which I do not
really want to do in the long run, and created a designer that inherited
from System.Web.UI.Design.WebControls.PanelDesigner. The control works in
the designer with the exception of not using the class names for some
reason.

What I would like is the control inherit from System.Web.UI.Control and
System.Web.UI.Design.ControlDesigner as I do not want all the extra baggage
the Panel control has. I have the control working just fine in the runtime
by just using in the Render() method and using this to render the child
controls:

foreach (Control control in this.Controls)
control.RenderControl(output);

However, I can not for the life of me figure out how to get child controls
to display in the design view. What do I need to do in the
GetDesignTimeHtml() method of the designer that I have inherited from
ControlDesigner? Or is there another designer I should use?

Thanks in advance. Here is sample code:

//// SERVER CONTROL

using System;
using System.Web.UI;

public class Section : System.Web.UI.Control // WebControls.Panel
{
private void RenderSpacer(HtmlTextWriter output)
{
output.AddAttribute(HtmlTextWriterAttribute.Nowrap, "true");
output.RenderBeginTag(HtmlTextWriterTag.Td);

output.AddAttribute(HtmlTextWriterAttribute.Src, "/images/spacer.gif");
output.AddAttribute(HtmlTextWriterAttribute.Width, "6");
output.AddAttribute(HtmlTextWriterAttribute.Height, "1");
output.RenderBeginTag(HtmlTextWriterTag.Img);
output.RenderEndTag(); // IMG

output.RenderEndTag(); // TD
}

protected override void Render(HtmlTextWriter output)
{
output.AddAttribute(HtmlTextWriterAttribute.Class, "sectionTable");
output.AddAttribute(HtmlTextWriterAttribute.Width, "100%");
output.RenderBeginTag(HtmlTextWriterTag.Table);
output.RenderBeginTag(HtmlTextWriterTag.Tr);

// Left Spacer cell
RenderSpacer(output);

// Content cell
output.AddAttribute(HtmlTextWriterAttribute.Class, "sectionContent");
output.RenderBeginTag(HtmlTextWriterTag.Td);

foreach (Control control in this.Controls)
control.RenderControl(output);

output.RenderEndTag(); // TD

// Right Spacer cell
RenderSpacer(output);

output.RenderEndTag(); // TR
output.RenderEndTag(); // TABLE
}
}



//// DESIGNER

using System;
using System.Text;

public class SectionDesigner : System.Web.UI.Design.ControlDesigner //
WebControls.PanelDesigner
{
private string RenderSpacer()
{
StringBuilder html = new StringBuilder();

html.Append("<TD nowrap>");
html.Append("<IMG src=\"/images/spacer.gif\" width=\"6\" height=\"1\">");
html.Append("</TD>\n");

return(html.ToString());
}

public override string GetDesignTimeHtml()
{
StringBuilder html = new StringBuilder();

try
{
Section control = (Section)this.Component;

html.Append("<TABLE class=\"sectionTable\" width=\"100%\">");
html.Append("<TR>\n");

// Left Spacer cell
html.Append(RenderSpacer());

html.Append("<TD class=\"sectionContent\" width=\"100%\">");
//
//This is where the child controls should be rendered
//
html.Append("</TD>\n");

// Right Spacer cell
html.Append(RenderSpacer());

html.Append("</TR>");
html.Append("</TABLE>");
}
catch (Exception ex)
{
html.Append(base.GetErrorDesignTimeHtml(ex));
}

if ((html == null) || (html.Length == 0))
html.Append(GetEmptyDesignTimeHtml());

return(html.ToString());
}


protected override string GetEmptyDesignTimeHtml()
{
return(base.GetEmptyDesignTimeHtml());
}
}



//// WEBPAGE

<BAXWEBUI:SECTION id="scMySection" runat="server">
<ASP:LABEL id="lblSection" runat="server" text="My Label" /><BR>
<ASP:pANEL id="pnlButtons" runat="server">
<ASP:LINKBUTTON id="cmdBack" runat="server" text="Back"
cssclass="buttonLeft" />
<ASP:LINKBUTTON id="cmdSubmit" runat="server" text="Submit"
cssclass="button" />
<ASP:LINKBUTTON id="cmdReset" runat="server" text="Reset"
cssclass="buttonRight" />
</ASP:pANEL>
</BAXWEBUI:SECTION>
 
L

Luciano Bargmann

Hi Cameron - and anyone else that could help on this :)

This is exactly the same thing I want to do. I want that my coders drag my
control to the design pane, have a panel with a "title" control already set,
so they can add their own controls below the title.

There are several things to note when creating this kind of control.
- They must inherit INamingContainer

- Call EnsureChildControls() in the end of your OnInit() and everytime you
need to access one of your inner controls (like in accessor methods);

- inside CreateChildControls(), you must follow these steps:
1. Create a new instance of your inner control, like this:
lblTitle= new Label();

2. Add the control to the container Controls collection:
this.Controls.Add(lblTitle);

[3. Give it an ID. I had to make this manually as I have some validation
controls being created and they need the ControlToValidate property set. Be
sure to follow the rule ContainerID + InnerCtrlID when setting IDs here. As
the brackets tell you, this step is optional :) ]
lblTitle.ID = this.ID + "lblTitle";

4. Set control properties. Here you show values of properties of your
control in their places. In my case, I have the Title property, wich I want
to show in my label:
lblTitle.Text = "Section title";

- Designer class - really needed?
I am not sure if the designer is needed, as we are inheriting from Panel,
that already has its own designer. Besides that, the inner controls, which
are in Controls collection, are already rendered in this function call
sequence:
ControlDesigner.GetDesignTimeHtml()
Control.RenderControl()
Control.Render()
Control.RenderChildren()

In runtime, the title renders perfectly. But in design time, I cannot show
my inner title. My design time should reflect the design time as much as
possible. Does anyone knows how to do it?

In other words, I would like to provide a control with predefined inner
controls, where the coder using it can add and remove the contents at will. I
want to automatically add some preset controls inside a panel and increase
development speed. Is that THAT hard? :D

If I use a designer inherited from ControlDesigner instead of
ReadWriteControlDesigner (base of PanelDesigner), the inner controls work
fine, but as you might already guessed, ControlDesigner is read-only, and
does not allow other controls to be dropped inside of it.

Other minor issues:
- how to not allow some controls to be changed or deleted (like the panel
title)?
- do we really need 2 VisualStudios open to debug design time behavior?

Maybe we can exchange more knowledge thru MSN Messenger; My address is
lucianoluke at bol.com.br

And if we can solve our problems, I promise that I will post an article in
CodeProject explaining this, plus a link to the article here :)

I would like to thank Lutz Roeder also, for his awesome .NET Reflector tool
that was an invaluable help in understanding the inners of WebControls in
ASP.NET.

Hope we find an answer soon,
Luciano Bargmann
----------------------------------------------------------------
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,073
Latest member
DarinCeden

Latest Threads

Top