I'm playing around with a composite control that uses the ASP.NET templating system.
I want to be able to define a HeaderTemplate and a FooterTemplate in my markup, and programmatically add a UserControl between the two.
The markup I'm aiming for is something like this:
<asp:DropZone runat="server" ID="LeftZone">
<HeaderTemplate>
<h1>Getting started</h1>
</HeaderTemplate>
<FooterTemplate>
<h3>The end of it...</h3>
</FooterTemplate>
</asp:DropZone>
My DropZone class looks like this:
public class DropZone : Control, INamingContainer
{
private ITemplate headerTemplate;
private ITemplate footerTemplate;
[DefaultValue((string)null),
Browsable(false),
PersistenceMode(PersistenceMode.InnerProperty),
TemplateInstance(TemplateInstance.Single)]
public virtual ITemplate HeaderTemplate
{
get { return headerTemplate; }
set { headerTemplate = value; }
}
[DefaultValue((string)null),
Browsable(false),
PersistenceMode(PersistenceMode.InnerProperty),
TemplateInstance(TemplateInstance.Single)]
public ITemplate FooterTemplate
{
get { return footerTemplate; }
set { footerTemplate = value; }
}
protected override void OnInit(EventArgs e)
{
EnsureChildControls();
base.OnInit(e);
}
private void AppendTemplate(ITemplate template, Control container)
{
if (template == null) return;
var ph = new PlaceHolder();
container.Controls.Add(ph);
template.InstantiateIn(ph);
}
protected override void CreateChildControls()
{
Controls.Clear();
AppendTemplate(HeaderTemplate, this);
Control helloWorld = Page.LoadControl("~/WebParts/HelloWorld.ascx");
if (helloWorld != null)
{
Controls.Add(helloWorld);
}
AppendTemplate(FooterTemplate, this);
ChildControlsCreated = true;
base.CreateChildControls();
}
}
However, this does not work as the ITemplate fields are never instantiated.
Any help or guidance would be highly appreciated.
UPDATE: I had to derive my custom control from CompositeControl to get things work as expected.
See (for instance) Templated Server Control Example, and MSDN Search for "asp.net templated controls".
Related
I am building a usercontrol to place webparts in my pagelayouts.
What i'm basicly doing:
<uc:WebPartInclude WPName="WebPartName" runat="server"</uc:WebPartInclude>
And this all works fine, except for one thing: It now surrounds the content of the webpart with <div> ... </div>.
Since i'm a sucker for clean code, this div has got to go ^_^
public class WebPartInclude : Control
{
public string WPName = "";
private WebControl webPartControl = null;
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
webPartControl = (WebControl)System.Reflection.Assembly.GetExecutingAssembly().CreateInstance("full path..." + WPName);
}
protected override void Render(HtmlTextWriter writer)
{
base.RenderChildren (writer);
}
protected override void CreateChildControls()
{
if (webPartControl != null)
Controls.Add(webPartControl);
ChildControlsCreated = true;
}
public override ControlCollection Controls
{
get
{
EnsureChildControls();
return base.Controls;
}
}
}
So the webpart renders the following:
<div>
... webpart contents
</div>
And i want the surrounding divs to go, got any idea?
Well, since there was no way of fixing it in a nice way, i just used the HtmlTextWriter to write the contents to a string and strip the surrounding <div>...</div>.
Problem solved, just not in a nice way.
this is what i had research in this few day, but i still cannot show the controls in design time... its keep show Type " System.Web.UI.UserControl does not have a public property name. The control is not showing while i have a inner property
Example .aspx code:
<XF:XFButton ID="XFButton1" runat="server" >
<Button1 ClientSideEvents-Click = "function(s,e){ApplyJavascript();}"></Button1>
</XF:XFButton>
Example behind .ascx code:
namespace XESControlsTestApp.WebControl
{
[PersistenceMode(PersistenceMode.InnerProperty)]
[ParseChildren(true)]
[Browsable(true)]
public class ContentContainer : Control, INamingContainer {
private ITemplate _content;
public ITemplate ContentTemplate
{
get { return this._content; }
set { this._content = value; }
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (this._content != null)
{
ContentContainer container = new ContentContainer();
this._content.InstantiateIn(container);
this._content.InstantiateIn(this);
}
}
}
I want to create a Repeater that displays the header/footer based on properties, only if the DataSource is empty.
public class Repeater : System.Web.UI.WebControls.Repeater
{
public bool ShowHeaderOnEmpty { get; set; }
public bool ShowFooterOnEmpty { get; set; }
[DefaultValue((string)null),
PersistenceMode(PersistenceMode.InnerProperty),
TemplateContainer(typeof(System.Web.UI.WebControls.RepeaterItem)),
Browsable(false)]
public ITemplate EmptyTemplate { get; set; }
}
I also want to create a EmptyTemplate, if the DataSource is empty display this template...
I have no idea on how to implement this. What should I override to achieve this behavior?
[ToolboxData("<{0}:SmartRepeater runat=\"server\"></{0}:SmartRepeater>")]
public partial class SmartRepeater : Repeater
{
public bool ShowHeaderOnEmpty { get; set; }
public bool ShowFooterOnEmpty { get; set; }
private ITemplate emptyTemplate = null;
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate EmptyTemplate
{
get { return this.emptyTemplate; }
set { this.emptyTemplate = value; }
}
protected override void OnDataBinding(EventArgs e)
{
base.OnDataBinding(e);
if (this.Items.Count == 0)
{
this.Controls.Clear();
if (this.HeaderTemplate != null && ShowHeaderOnEmpty)
this.HeaderTemplate.InstantiateIn(this);
if (this.EmptyTemplate!=null)
this.EmptyTemplate.InstantiateIn(this);
if (this.FooterTemplate != null && ShowFooterOnEmpty)
this.FooterTemplate.InstantiateIn(this);
}
}
}
Usage:
<UC:SmartRepeater ID="rep" runat="server" ShowHeaderOnEmpty="true" ShowFooterOnEmpty="true">
<HeaderTemplate>HEADER</HeaderTemplate>
<ItemTemplate>Item</ItemTemplate>
<SeparatorTemplate>, </SeparatorTemplate>
<EmptyTemplate><b>Nothing</b></EmptyTemplate>
<FooterTemplate>FOOTER</FooterTemplate>
</UC:SmartRepeater>
Use ListView instead of Repeater.
It already contains EmptyDataTemplate and EmptyItemTemplate elements so you don't need to do anything :)
I would create a Web User Control (.ascx) that contains your header section, a [child] repeater control, and a footer section. You can put all your logic in that custom control.
If you want to do this with just a repeater you can do this:
<asp:Repeater runat="server" OnItemDataBound="ShowHideHeaderFooter">
<HeaderTemplate>
<asp:PlaceHolder runat="server" ID="PlaceHolderHeader">
HEADER STUFF
</asp:PlaceHolder>
</HeaderTemplate>
<ItemTemplate>
ITEM STUFF
</ItemTemplate>
<FooterTemplate>
<asp:PlaceHolder runat="server" ID="PlaceHolderFooter">
FOOTER STUFF
</asp:PlaceHolder>
</FooterTemplate>
</asp:Repeater>
and then in your code behind
protected void ShowHideHeaderFooter(object sender, RepeaterItemEventArgs e)
{
if(e.Item.ItemType == ListItemType.Header && theDataSource.Count == 0 && !ShowHeaderOnEmpty)
{
e.Item.FindControl("PlaceHolderHeader").Visible = false;
}
...
}
override the render event to output the HTML you want based on the all properties you have mentioned.
A Server based control is not good solution for me, since my panel should by default always contain a asp checkbox which will allow the user to hide and show the panels content.
I created my Panel as a templated user control but now I have the problem that I cannot declare variables in it.
[ParseChildren(true)]
public partial class MyPanel: System.Web.UI.UserControl
{
private ITemplate messageTemplate = null;
[TemplateContainer(typeof(MessageContainer))]
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate Content
{
get
{
return messageTemplate;
}
set
{
messageTemplate = value;
}
}
void Page_Init()
{
MessageContainer container = new MessageContainer();
messageTemplate.InstantiateIn(container);
PlaceHolder1.Controls.Add(container);
}
[ParseChildren(true)]
public class MessageContainer : Control, INamingContainer
{
internal MessageContainer()
{
}
}
}
If I do the following in MyPage.aspx then the control definitions are not inserted into MyPage.aspx.designer.cs a they do normally:
<my:MyPanel>
<Content>
<asp:TextBox id = "foo" runat="server" />
</Content>
</my:MyPanel>
Therefore foo is not created as control variable by the designer, so I have no access to it.
How can I create my own Panel which allows declaration of controls in it?
EDIT:
I now tried with [ParseChildren(false)]. Variables for contained variables are now generated in the designer code of the form. The problem is now that messageTemplate.InstantiateIn(container) throws an exception.
You haven't given code for the control. In general, it needs to implement INamingContainer and should have properties of type ITemplate to accept templates.
Check on MSDN on how to develop one. And here's the sample code from MSDN. Also check this article for data bound templated control.
First of all, you need to use the runat="server" attribute.
<asp:TextBox id = "foo" runat="server"/>
Afterwards you can try
var textbox = this.MyCustomPanel.FindControl("foo") as TextBox;
Instead of using FindControl I guess it is possible to achieve this behaviour by setting an attribute on the designer settings of the INamingTemplate Container of your Usercontrol
You don't need to create a templated control, just create a Composite Web Control. Create a Panel & Checkbox, add them to the control collection of the composite control, adjust the rendering to display it as you want, and run with it.
look here
* EDIT **
Here is a working implementation of what you need. Make to create a reference for the Web.dll.
CustomPanel.cs
using System;
using System.ComponentModel;
using System.Drawing;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace Web
{
[ AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal),
AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal),
ToolboxData("<{0}:CustomPanel runat=\"server\"> </{0}:CustomPanel>"),
]
public class CustomPanel : CompositeControl
{
private Panel panelContainer;
private CheckBox chkHideContent;
private Panel panelInnerContainer;
[Bindable(true),
Category("Appearance"),
DefaultValue(""),
Description("The text to display with the checkbox.")]
public string CheckBoxText
{
get
{
EnsureChildControls();
return chkHideContent.Text;
}
set
{
EnsureChildControls();
chkHideContent.Text = value;
}
}
[Bindable(true)]
[Category("Data")]
[DefaultValue("")]
[Localizable(true)]
public bool IsCheckBoxChecked
{
get
{
return chkHideContent.Checked;
}
}
[Bindable(true)]
[Category("Data")]
[DefaultValue("")]
[Localizable(true)]
public bool HideInnerPanel
{
set
{
EnsureChildControls();
panelInnerContainer.Visible = value;
}
}
[Bindable(true)]
[Category("Data")]
[DefaultValue("")]
[Localizable(true)]
public ControlCollection InnerPanelControls
{
get
{
EnsureChildControls();
return panelInnerContainer.Controls;
}
}
protected virtual void OnCheckboxChanged(EventArgs e)
{
if (chkHideContent.Checked)
{
panelInnerContainer.Visible = false;
}
else
{
panelInnerContainer.Visible = true;
}
}
private void _checkbox_checkChanged(object sender, EventArgs e)
{
OnCheckboxChanged(EventArgs.Empty);
}
protected override void RecreateChildControls()
{
EnsureChildControls();
}
protected override void CreateChildControls()
{
Controls.Clear();
panelContainer = new Panel();
panelContainer.ID = "panelContainer";
chkHideContent = new CheckBox();
chkHideContent.ID = "chkHideContent";
chkHideContent.CheckedChanged += new EventHandler(_checkbox_checkChanged);
chkHideContent.AutoPostBack = true;
panelInnerContainer = new Panel();
panelInnerContainer.ID = "panelInnerContainer";
this.Controls.Add(panelContainer);
this.Controls.Add(chkHideContent);
this.Controls.Add(panelInnerContainer);
}
protected override void Render(HtmlTextWriter writer)
{
panelContainer.RenderBeginTag(writer);
chkHideContent.RenderControl(writer);
panelInnerContainer.RenderControl(writer);
panelContainer.RenderEndTag(writer);
}
}
}
Default.aspx
<%# Register assembly="Web" namespace="Web" tagprefix="cc1" %>
<cc1:CustomPanel ID="CustomPanel1" runat="server" />
Default.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
Label lbl = new Label();
lbl.Text = "IT WORKS!";
CustomPanel1.CheckBoxText = "Hide my innards!";
CustomPanel1.InnerPanelControls.Add(lbl);
}
Here is what I want:
I want a control to put on a page, which other developers can place form elements inside of to display the entities that my control is searching. I have the Searching logic all working. The control builds custom search fields and performs searches based on declarative C# classes implementing my SearchSpec interface.
Here is what I've been trying:
I've tried using ITemplate on a WebControl which implements INamingContainer
I've tried implementing a CompositeControl
The closest I can get to working is below.
OK I have a custom WebControl
[
AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal),
AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal),
DefaultProperty("SearchSpecName"),
ParseChildren(true),
ToolboxData("<{0}:SearchPage runat=\"server\"> </{0}:SearchPage>")
]
public class SearchPage : WebControl, INamingContainer
{
[Browsable(false),
PersistenceMode(PersistenceMode.InnerProperty),
DefaultValue(typeof(ITemplate), ""),
Description("Form template"),
TemplateInstance(TemplateInstance.Single),
TemplateContainer(typeof(FormContainer))]
public ITemplate FormTemplate { get; set; }
public class FormContainer : Control, INamingContainer{ }
public Control MyTemplateContainer { get; private set; }
[Bindable(true), Category("Behavior"), DefaultValue(""),
Description("The class name of the SearchSpec to use."), Localizable(false)]
public virtual string SearchSpecName
{
get;
set;
}
[Bindable(true), Category("Behavior"), DefaultValue(true),
Description("True if this is query mode."), Localizable(false)]
public virtual bool QueryMode
{
get;
set;
}
private SearchSpec _spec;
private SearchSpec Spec
{
get
{
if (_spec == null)
{
Type type = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.Name == SearchSpecName).First();
_spec = (SearchSpec)Assembly.GetExecutingAssembly().CreateInstance(type.Namespace + "." + type.Name);
}
return _spec;
}
}
protected override void CreateChildControls()
{
if (FormTemplate != null)
{
MyTemplateContainer = new FormTemplateContainer(this);
FormTemplate.InstantiateIn(MyTemplateContainer);
Controls.Add(MyTemplateContainer);
}
else
{
Controls.Add(new LiteralControl("blah"));
}
}
protected override void RenderContents(HtmlTextWriter writer)
{
// <snip>
}
protected override HtmlTextWriterTag TagKey
{
get
{
return HtmlTextWriterTag.Div;
}
}
}
public class FormTemplateContainer : Control, INamingContainer
{
private SearchPage parent;
public FormTemplateContainer(SearchPage parent)
{
this.parent = parent;
}
}
then the usage:
<tster:SearchPage ID="sp1" runat="server" SearchSpecName="TestSearchSpec" QueryMode="False">
<FormTemplate>
<br />
Test Name:
<asp:TextBox ID="testNameBox" runat="server" Width="432px"></asp:TextBox>
<br />
Owner:
<asp:TextBox ID="ownerBox" runat="server" Width="427px"></asp:TextBox>
<br />
Description:
<asp:TextBox ID="descriptionBox" runat="server" Height="123px" Width="432px"
TextMode="MultiLine" Wrap="true"></asp:TextBox>
</FormTemplate>
</tster:SearchPage>
The problem is that in the CodeBehind, the page has members descriptionBox, ownerBox and testNameBox. However, they are all null. Furthermore, FindControl("ownerBox") returns null as does this.sp1.FindControl("ownerBox"). I have to do this.sp1.MyTemplateContainer.FindControl("ownerBox") to get the control.
How can I make it so that the C# Code Behind will have the controls generated and not null in my Page_Load event so that developers can just do this:
testNameBox.Text = "foo";
ownerBox.Text = "bar";
descriptionBox.Text = "baz";
OK, I fixed this by making SearchPage extend Panel and removing ParseChildren(true).