Monday, January 22, 2007

Templated Control with Designer Support

Für mein aktuelles Projekt benötigten wir eine schön formatierte Box die einen Titel hat. Statt diese auf jeder Seite x-mal mit Table Tags zu bauen und zu formatieren, wollten wir ein eigenes Control verwenden.

1. Versuch: Erstellen eines einfachen Controls abgeleitet von der Klasse Controls befriedigte unsere Bedürfnisse nur mässig, da zwischen dem Start- und Endtag nur Text verwendet werden konnte. Wir wollten jedoch ASP.NET Controls in der Box platzieren.

[DefaultProperty("Title"), ToolboxData("<{0}:SimpleBox runat=server>")]
public class SimpleBox : Control
{
private string _title;

[Bindable(true), Category("Appearance"), DefaultValue("")]
public string Title
{
get { return _title; }
set { _title = value; }
}

protected override void Render(HtmlTextWriter output)
{
string outputString = string.Empty;

// generate output (html)
outputString = "<table cellspacing="0" cellpadding="0" border="1">"
+ " <tr>"
+ " <td>" + Title + "</td>"
+ " </tr>"
+ " <tr>"
+ " <td>";

// read between the begin and end tag
if ((HasControls()) && (Controls[0] is LiteralControl))
{
outputString += ((LiteralControl)Controls[0]).Text;
}

string outputStringEnd = "</td>"
+ " </tr>"
+ "</table>";

// speak your mind
output.Write(outputString);
}
}


2. Versuch: Erstellen eines Composite Controls
Das war schon besser. Die Controls zwischen Start- und Endtag wurden erkannt und gerendert. Einziges Problem waren die Events der Childcontrols. Diese wurden nicht gefeuert. Könnte man ausprogrammieren, aber...

[DefaultProperty("Title"), ToolboxData("<{0}:ComposedBox runat=server>")]
public class ComposedBox : Control, INamingContainer
{
private string _title;

//[Bindable(true), Category("Appearance"), DefaultValue("")]
public string Title
{
get
{
return _title;
}
set
{
_title = value;
}
}

protected override void CreateChildControls()
{
string outputStringStart = "<table cellspacing="0" cellpadding="0" border="1">"
+ " <tr>"
+ " <td>" + Title + "</td>"
+ " </tr>"
+ " <tr>"
+ " <td>";

string outputStringEnd = "</td>"
+ " </tr>"
+ "</table>
";

this.Controls.AddAt(0, (new LiteralControl(outputStringStart)));
this.Controls.AddAt(this.Controls.Count - 1, (new LiteralControl(outputStringEnd)));
}
}


3. Versuch: Das Templated Control
Hier funktioniert alles auf anhieb, ohne grossen Programmieraufwand. In kurzer Zeit war unser Control erstellt und die formatierte Box stand zur Verfügung. In einer überarbeiteten Variante integrierten wir dann auch noch den fehlenden Designer Support. Great!


public class BoxTemplate : CompositeControl, INamingContainer
{
private String _message = null;
public BoxTemplate() { }
public BoxTemplate(String message)
{
_message = message;
}
public String Message
{
get { return _message; }
set { _message = value; }
}
}

[DefaultProperty("Title"),
ParseChildren(true), PersistChildren(false),
Designer("CSBS.TVCITTool.Client.Web.Controls.TemplatedBoxControlDesigner, CSBS.TVCITTool.Client.Web.Controls"),
ToolboxData("<{0}:TemplatedBox runat=server>")]
public class TemplatedBox : CompositeControl, INamingContainer
{
private string _title;
private ITemplate _messageTemplate = null;
private String _message = null;

public String Message
{
get { return _message; }
set { _message = value; }
}

[Bindable(true), Category("Appearance"), DefaultValue("")]
public string Title
{
get { return _title; }
set { _title = value; }
}

[PersistenceMode(PersistenceMode.InnerProperty), DefaultValue(null), TemplateContainer(typeof(BoxTemplate)),
TemplateInstance(TemplateInstance.Single), Browsable(false)]
public ITemplate MessageTemplate
{
get { return _messageTemplate; }
set { _messageTemplate = value; }
}

public override void DataBind()
{
EnsureChildControls();
base.DataBind();
}

protected override void CreateChildControls()
{
// If a template has been specified, use it to create children.
// Otherwise, create a single literalcontrol with message value

string outputStringStart = "
<table class="boxframe" cellspacing="'\">"
+ " <tbody><tr>"
+ " <td class="boxheader">" + Title + "</td>"
+ " </tr>"
+ " <tr>"
+ " <td>";

string outputStringEnd = "</td>"
+ " </tr>"
+ "</tbody></table>
";

if (MessageTemplate != null)
{
Controls.Clear();
BoxTemplate i = new BoxTemplate(this.Message);
MessageTemplate.InstantiateIn(i);
Controls.Add(i);
}
else
{
this.Controls.Add(new LiteralControl(this.Message));
}

this.Controls.AddAt(0, (new LiteralControl(outputStringStart)));
this.Controls.AddAt(this.Controls.Count, (new LiteralControl(outputStringEnd)));
}
}

public class TemplatedBoxControlDesigner : CompositeControlDesigner
{
private TemplatedBox _designControl;
private TemplateGroupCollection _templateGroupCollection;

public override void Initialize(System.ComponentModel.IComponent component)
{
base.Initialize(component);
_designControl = (TemplatedBox)component;
SetViewFlags(ViewFlags.DesignTimeHtmlRequiresLoadComplete, true);
SetViewFlags(ViewFlags.TemplateEditing, true);
}

public override TemplateGroupCollection TemplateGroups
{
get
{
if (_templateGroupCollection == null)
{
_templateGroupCollection = base.TemplateGroups;
TemplateGroup group;
TemplateDefinition templateDefinition;
group = new TemplateGroup("ContextRowTemplates");
templateDefinition = new TemplateDefinition(this, "MessageTemplate", _designControl, "MessageTemplate", false); //der vierte Parameter ist der Name der Property des Control
group.AddTemplateDefinition(templateDefinition);
_templateGroupCollection.Add(group);
}
return _templateGroupCollection;
}
}
}

No comments: