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? :(
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.
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.
Related
What's the "skeleton" code for using CsQuery in the code-behind of a MasterPage in order to modify the HTML output? I need to be able to modify everything in the <body> of the HTML?
I'm hoping to use CsQuery to "touch-up" the HTML output of a Dynamic Data website without rewriting / messing with the default code.
Just looking for sample code specific to MasterPage code-behind? Thanks.
There is an example in the CsQuery project that shows how to do this (which I just made sure was working right!) in the CsQuery.WebFormsApp project.
The core of the usage looks like this. You must override the Render method in a class that inherits Page, and use this instead of Page as the base class for the codebehind in an aspx page:
public class CsQueryPage: System.Web.UI.Page
{
public CQ Doc { get; protected set; }
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
// most of the work is done for you with the
// `CsQuery.WebForms.CreateFromRender` method
var csqContext = WebForms.CreateFromRender(this, base.Render, writer);
// if you are using update panels, this lets you also manipulate that
// HTML, otherwise you don't need the IsAsync part
if (csqContext.IsAsync)
{
foreach (var item in csqContext.AsyncPostbackData) {
Cq_RenderUpdatePanel(item.Dom,item.ID);
}
}
else
{
Doc = csqContext.Dom;
Cq_Render();
}
// writes the altered content to the base HtmlTextWriter
csqContext.Render();
}
protected virtual void Cq_Render()
{ }
protected virtual void Cq_RenderUpdatePanel(CQ doc, string updatePanelId)
{ }
}
The two virtual methods are where you can alter the dom, which is populated in the Doc property of the CsQueryPage object - the intent of leaving them unimplemented here is that each aspx page that inherits CsQueryPage can optionally override them and make changes to the DOM.
To see how this works in practice just pull down the CsQuery code from github and run the example.
The same technique can be used for a UserControl which is also shown in the example. I don't actually show how to do it with MasterPage but it's very much the same-- MasterPage derives from UserControl, you just override it's Render method same as the other situations.
I've written a templated user control, MinimalTemplate, which currently does nothing other than render the HTML passed into its "ContentTemplate" placeholder. I want Visual Studio 2008 to have the same intellisense features for MinimalTemplate that it has for built-in templated controls such as Repeater.
Possibly related: I can manually type out my ContentTemplate tags, and it will build and run properly, but I get a validation error. I have already deleted the contents of my ReflectedSchemas folder, as suggested in this question.
Complete source for Minimal Template:
MinimalTemplate.ascx
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="MinimalTemplate.ascx.cs" Inherits="MyProject.MinimalTemplate" %>
<asp:placeholder runat=server id="contentPlaceHolder" />
MinimalTemplate.ascx.cs
using System.Web.UI;
namespace MyProject
{
[ParseChildren(false)]
public partial class MinimalTemplate : System.Web.UI.UserControl
{
[TemplateContainer(typeof(MessageContainer))]
[TemplateInstance(TemplateInstance.Single)]
public ITemplate ContentTemplate
{ get; set; }
void Page_Init()
{
if (ContentTemplate != null)
{
MessageContainer container = new MessageContainer();
ContentTemplate.InstantiateIn(container);
contentPlaceHolder.Controls.Add(container);
}
}
public class MessageContainer : Control, INamingContainer { }
}
}
What changes can I make to my MinimalTemplate code so that Visual Studio will validate and autocomplete its ContentTemplate tag?
Related.
Add [PersistenceMode(PersistenceMode.InnerProperty)] to ContentTemplate's attribute list. After adding it and rebuilding, the validation error disappeared and "ContentTemplate" appeared as expected in the Intellisense dropdown.
During my investigation, I'm certain I tried adding this property two or three times to no effect, so I expect the VS validator is a bit flaky. It smacks of voodoo programming, but do a Clean/Rebuild All and wait a few seconds before seeing whether the validation error persists.
(Also, you don't need the ParseChildren attribute for this control.)
I want a create a custom/user control that has children.
For Example, I want my control to have the following markup:
<div runat="server" id="div">
<label runat="server" id="label"></label>
<div class="field">
<!-- INSERT CHILDREN HERE -->
</div>
</div>
and when I want to use it on a page I simply:
<ctr:MyUserControl runat="server" ID="myControl">
<span>This is a child</span>
<div runat="server" id="myChild">And another <b>child</b>
</ctr:MyUserControl>
The child controls inside my user control will be inserted into my user control somewhere. What is the best way to accomplish this?
The functionality is similar to a asp:PlaceHolder but I want to add a couple more options as well as additional markup and the such. Also the child controls still need to be able to be accessed by the page. (in the example above the page should have the myChild Control on it)
EDIT ------
It can be a template control as long as it allows me to reference the children on the page.
I asked something similar myself a while ago. See here.
I believe you will have to use an ITemplate as an InnerProperty:
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[TemplateInstance(TemplateInstance.Single)]
public ITemplate Content
{
get
{
return _content;
}
set
{
_content = value;
}
}
private ITemplate _content;
Then override the CreateChildControls method of your control:
protected override void CreateChildControls()
{
if (this.Content != null)
{
this.Controls.Clear();
this.Content.InstantiateIn(this);
}
base.CreateChildControls();
}
What's the harm in using an ITemplate You can combine it with your existing markup and write whatever HTML you want within the Content property.
Another way to approach this would be to look at the source of the Panel control (using Reflector for example). It looks like it just overrides the RenderBeginTag and RenderEndTag methods (among others to add attributes and whatnot) and defers the rest of the rendering to the WebControl class.
I know that the answer is a bit old but I have a problem which was not mentioned here.
I've tried this solution and it works well if the content are default aspx controls or plain html tags. When I put a custom web control inside I have a problem with NullReferenceException in the custom web control (child controls are all null). I overloaded OnInit method (in the custom web control code behind) to call EnsureChildControls() but child controls are not still instantiated. Do you have any idea or sugestions what the point is?
Here is the code which I use to instantiate the controls:
this._pnlButtons.Controls.Add( _lbtnOkHidden );
this._pnlButtons.Controls.Add( _lgbtnOk );
this._pnlPopup.Controls.Add( _pnlHeader );
this._pnlPopup.Controls.Add( _pnlContent );
this._pnlPopup.Controls.Add( _pnlButtons );
if ( this.Content != null )
this.Content.InstantiateIn( _pnlContent );
this._updatePanel.ContentTemplateContainer.Controls.Add( _lbShowPopup );
this._updatePanel.ContentTemplateContainer.Controls.Add( _lbtnShowPopupHidden );
this._updatePanel.ContentTemplateContainer.Controls.Add( _pnlPopup );
this._updatePanel.ContentTemplateContainer.Controls.Add( _modalExtender );
this.Controls.Add(_updatePanel);
base.CreateChildControls();
I've searched the site and I cannot find a solution for my problem, so apologies if it's already been answered (I'm sure someone must have asked this before).
I have written a jQuery Popup window that I've packaged up as a WebControl and IScriptControl. The last step is to be able to write the markup within the tags of my control. I've used the InnerProperty attribute a few times, but only for including lists of strongly typed classes.
Here's my property on the WebControl:
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public something??? Content
{
get
{
if (_content == null)
{
_content = new something???();
}
return _content;
}
}
private something??? _content;
Here's the HTML Markup of what I'm after:
<ctr:WebPopup runat="server" ID="win_Test" Hidden="false" Width="100px" Height="100px"
Modal="true" WindowCaption="Test Window" CssClass="window">
<Content>
<div style="display:none;">
<asp:Button runat="server" ID="Button1" OnClick="Button1_Click" />
</div>
<%--Etc--%>
<%--Etc--%>
</Content>
</ctr:WebPopup>
Unfortunately I don't know what type my Content property should be. I basically need to replicate the UpdatePanel's ContentTemplate.
EDIT: So the following allows a Template container to be automatically created, but no controls show up, what's wrong with what I'm doing?
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ITemplate Content
{
get
{
return _content;
}
set
{
_content = value;
}
}
private ITemplate _content;
EDIT2: Overriding the CreateChildControls allows the controls within the ITemplate to be rendered:
protected override void CreateChildControls()
{
if (this.Content != null)
{
this.Controls.Clear();
this.Content.InstantiateIn(this);
}
base.CreateChildControls();
}
Unfortunately I cannot now access the controls within the ITemplate from the codebehind file on the file. I.e. if I put a button within my mark as so:
<ctr:WebPopup runat="server" ID="win_StatusFilter">
<Content>
<asp:Button runat="server" ID="btn_Test" Text="Cannot access this from code behind?" />
</Content>
</ctr:WebPopup>
I then cannot access btn_Test from the code behind:
protected void Page_Load(object sender, EventArgs e)
{
btn_Test.Text = "btn_Test is not present in Intellisense and
is not accessible to the page. It does, however, render correctly.";
}
EDIT3: FIXED! Edit 2 is the correct soluion. It was just Visual Studios 2010 being a pain in the buttocks. Closed the app and reopened it and all my controls within the Content property were accessible on the page.
EDIT4: Edit 2 didn't fix the issue. I had already tried the [TemplateInstance(TemplateInstance.Single)] attribute before anyone had mentioned it, however at the time I didn't think it had made a difference. It appears Visual Studios 2010 is just being weird today.
Since I removed the tag and it carried on working, I assumed the attribute hadn't made a difference. Since going back to the code AGAIN the controls have become unavailable. Adding the attribute back in allowed it all to work and for the controls to be accessible server side. MADNESS. I will be accepting Brian's answer as he mentioned the fix before anyone else.
public ITemplate Content
which then you render to the UI like:
Label label = new Label();
this.Content.InstantiateIn(label);
//Render label
EDIT: Make sure the template also defines
[TemplateInstance(TemplateInstance.Single)]
as this allows you to access the controls within the template directly.
You should try to use this:
win_StatusFilter.FindControl("btn_Test") // this will be a Control
win_StatusFilter.FindControl("btn_Test") as Button // this will be a Button if control found, otherwise it will be null.
Otherwise you should define some properties for your control, like in this article:
http://msdn.microsoft.com/ru-ru/library/36574bf6%28v=VS.90%29.aspx
Update:
According the remarks in this article about ContentTemplate property of the UpdatePanel:
http://msdn.microsoft.com/en-us/library/system.web.ui.updatepanel.contenttemplate(v=VS.90).aspx
you can get controls from ContentTemplate because of TemplateInstanceAttribute value (UpdatePanel.ContentTemplate has the TemplateInstance.Single).
So you should only use this code:
[PersistenceMode(PersistenceMode.InnerProperty)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[TemplateInstance(TemplateInstance.Single)]
public ITemplate Content
More information at:
http://msdn.microsoft.com/ru-ru/library/system.web.ui.templateinstanceattribute(v=VS.90).aspx
I have a UserControl that is working fine. It is declared like this.
public partial class DynamicList : System.Web.UI.UserControl
{
protected static BaseListController m_GenericListController = null;
public DynamicList()
{
m_GenericListController = new GenericListController(this);
}
}
Now I want to override this control so I can change some of the properties. I have created a class like this.
public partial class JobRunningList : DynamicList
{
public JobRunningList()
{
m_GenericListController = new JobListController(this);
(m_GenericListController as GenericListController).ModuleId = 14;
}
}
It appears that the controls in the DynamicList are not getting created though when I use the JobRunningList control now causing predictably bad results. The DynamicList UserControl has a ListView on it and a few other controls. It appears these are not created when using the JobRunningList. Is there any secret to this?
The boring workaround would be to make JobRunningList as plain old user control that contains a DynamicList and just sets the properties of the inner control in its OnLoad. That's awkward if DynamicList has many other properties that you want to access from the page though, as JobRunningList would have to define matching properties of its own. Getting back to the inheritance approach, then...
The DynamicList class just contains the code behind logic, so what you're doing works nicely if you want the second control to reuse the logic behind the first but provide a new UI of its own.
The markup in your .ascx file gets compiled into another class that inherits DynamicList, so if you can get your JobRunningList class to inherit that class instead of DynamicList, you'll get the result you want. This class gets a default name derived from the filename, but you can avoid guessing that by setting a ClassName in the control directive to use instead of the automatic name.
Take a simple base control like
<%# Control Language="C#" AutoEventWireup="true"
CodeFile="HelloControl.ascx.cs" Inherits="HelloControlBase"
ClassName="MyControls.HelloControl" %>
Hello <%= Name %>
with an unexciting code-behind like
public partial class HelloControlBase : System.Web.UI.UserControl
{
public string Name
{
get;
set;
}
}
Now we want to override the Name property in a new control. First we need HelloAlice.ascx
<%# Control Language="C#" AutoEventWireup="true"
CodeFile="HelloAliceControl.ascx.cs"
Inherits="HelloAliceControl" %>
Not much to see here, since we're leaving all the work to the original ascx. Now in the code-behind,
public partial class HelloAliceControl : MyControls.HelloControl
{
protected void Page_Load(object sender, EventArgs e)
{
this.Name = "Alice";
}
}
We just inherit MyControls.HelloControl and set the Name property, and it looks like we're done.
The problem is knowing when MyControls.HelloControl is visible. As long as your derived control is in the same directory as the parent control you'll probably be OK, otherwise it's quite easy to run into build errors complaining that the class doesn't exist because the parent control hasn't been compiled yet.
If I understand correctly, you want the interface to be the same. In that case, I would create some properties instead. Perhaps just a simple enumeration i.e. ListType.