Custom ASP.NET Container Control - asp.net

I've been trying to create a custom control that works exactly like the Panel control except surrounded by a few divs and such to create a rounded box look. I haven't been able to find a decent example of how to do this.
I need to be able to place text and controls inside the control and access it directly without referencing the panel (exactly the way the Panel control works).
Does anyone have any examples of this?

There are two ways to do this. One is to implement INamingContainer on your control, and it takes a lot of effort.
The other way is to inherit from Panel, and override the RenderBeginTag and RenderEndTag methods to add your custom markup. This is easy.
public class RoundedCornersPanel : System.Web.UI.WebControls.Panel
{
public override RenderBeginTag (HtmlTextWriter writer)
{
writer.Write("Your rounded corner opening markup");
base.RenderBeginTag(writer);
}
public override RenderEndTag (HtmlTextWriter writer)
{
base.RenderEndTag(writer);
writer.Write("Your rounded corner closing markup");
}
}

There are already quite a few answers here, but I just wanted to paste the most basic implementation of this without inheriting from Panel class. So here it goes:
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
[ToolboxData("<{0}:SimpleContainer runat=server></{0}:SimpleContainer>")]
[ParseChildren(true, "Content")]
public class SimpleContainer : WebControl, INamingContainer
{
[PersistenceMode(PersistenceMode.InnerProperty)]
[TemplateContainer(typeof(SimpleContainer))]
[TemplateInstance(TemplateInstance.Single)]
public virtual ITemplate Content { get; set; }
public override void RenderBeginTag(HtmlTextWriter writer)
{
// Do not render anything.
}
public override void RenderEndTag(HtmlTextWriter writer)
{
// Do not render anything.
}
protected override void RenderContents(HtmlTextWriter output)
{
output.Write("<div class='container'>");
this.RenderChildren(output);
output.Write("</div>");
}
protected override void OnInit(System.EventArgs e)
{
base.OnInit(e);
// Initialize all child controls.
this.CreateChildControls();
this.ChildControlsCreated = true;
}
protected override void CreateChildControls()
{
// Remove any controls
this.Controls.Clear();
// Add all content to a container.
var container = new Control();
this.Content.InstantiateIn(container);
// Add container to the control collection.
this.Controls.Add(container);
}
}
Then you can use it like this:
<MyControls:SimpleContainer
ID="container1"
runat="server">
<Content>
<asp:TextBox
ID="txtName"
runat="server" />
<asp:Button
ID="btnSubmit"
runat="server"
Text="Submit" />
</Content>
</MyControls:SimpleContainer>
And from codebehind you can do things like this:
this.btnSubmit.Text = "Click me!";
this.txtName.Text = "Jack Sparrow";

Create a class that inherits System.Web.UI.Control, and overrride the Render ( HtmlTextWriter ) method.
In this method, render surrounding start tags, then render the children(RenderChildren), then render end tags.
protected override void Render ( HtmlTextWriter output )
{
output.Write ( "<div>" );
RenderChildren ( output );
output.Write ( "</div>" );
}
Rounded corners is typically achieved using CSS and corner images for the top left, top right, bottom left and bottom right corners.
It could be done using 4 nested divs, acting as layers, each of them having one corner image as their background image.

Code project have something that might interest you : Panel Curve Container - An ASP.NET Custom Control Nugget. I am sure you can play with the code and have the behavior and look you want.

If you don't want to inherit directly from WebControl instead of from Panel, the easiest way to do this is to decorate the class with the attribute [ParseChildren(false)]. Although at first glance this might suggest that you don't want to parse children, what the false actually indicates is that you don't want the children to be treated as properties. Instead, you want them to be treated as controls.
By using this attribute, you get virtually all of the functionality out of the box:
[ToolboxData("<{0}:RoundedBox runat=server></{0}:RoundedBox>")]
[ParseChildren(false)]
public class RoundedBox : WebControl, INamingContainer
{
public override void RenderBeginTag(HtmlTextWriter writer)
{
writer.Write("<div class='roundedbox'>");
}
public override void RenderEndTag(HtmlTextWriter writer)
{
writer.Write("</div>");
}
}
This will allow you to add RoundedBox controls to your pages, and add children (either asp.net controls or raw html) that will be rendered inside your div.
Of course, css would be added to correctly style the roundedbox class.

Just another thing you can use, there's a rounded corner extender in the ASP.Net ajax toolkit.
I know it's not exactly what you asked for, but you don't have to write any custom code.
Hope that helps!

I looked at this question because I wanted to produce a 2 column layout panel. (not quite but its a much simpler example of what I needed. I'm sharing the solution that I wound up using:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace Syn.Test
{
[DefaultProperty("Text")]
[ToolboxData("<{0}:MultiPanel runat=server></{0}:MultiPanel>")]
[ParseChildren(true)]
[PersistChildren(false)]
public class MultiPanel : WebControl, INamingContainer
{
public ContentContainer LeftContent { get; set; }
public ContentContainer RightContent { get; set; }
protected override void CreateChildControls()
{
base.CreateChildControls();
}
protected override void Render(HtmlTextWriter output)
{
output.AddStyleAttribute("width", "600px");
output.RenderBeginTag(HtmlTextWriterTag.Div);
output.AddStyleAttribute("float", "left");
output.AddStyleAttribute("width", "280px");
output.AddStyleAttribute("padding", "10px");
output.RenderBeginTag(HtmlTextWriterTag.Div);
LeftContent.RenderControl(output);
output.RenderEndTag();
output.AddStyleAttribute("float", "left");
output.AddStyleAttribute("width", "280px");
output.AddStyleAttribute("padding", "10px");
output.RenderBeginTag(HtmlTextWriterTag.Div);
RightContent.RenderControl(output);
output.RenderEndTag();
output.RenderEndTag();
}
}
[ParseChildren(false)]
public class ContentContainer : Control, INamingContainer
{
}
}
The issue I still have is the intellisense does't work for in this scenario, it won't suggest the Left and Right Content tags.

public class myCustomPanel : Panel
{
public override void RenderBeginTag(HtmlTextWriter writer)
{
writer.AddAttribute(HtmlTextWriterAttribute.Class, "top_left_corner");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
base.RenderBeginTag(writer);
}
public override void RenderEndTag(HtmlTextWriter writer)
{
base.RenderEndTag(writer);
writer.RenderEndTag();
}
}

Related

Can not call Public List from App_Code folder in Web Form

So here's my predicament. I'm writing a custom, one-off content management system, and I can not for the life of me getting this method to work correctly. What I want to do is create a laundry list worth of methods in separate folders and call them as I need them on whichever web forms I want to call them on.
I created a WebApp and created a folder inside of the app called App_Code. Inside of App_Code, there is a public class called "TestimonialService". Here it is:
/******************** TESTIMONIAL SERVICE ****************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BlueTreeSecurity.App_Code.Data;
namespace BlueTreeSecurity.App_Code.Testimonials
{
public class TestimonialService
{
private readonly CMSObjectContext _context;
public TestimonialService(CMSObjectContext context)
{
this._context = context;
}
#region methods
/// <summary>
/// Gets all testimonials
/// </summary>
/// <returns>testimonial collection</returns>
public List<Testimonial> GetAllTestimonials()
{
var query = from t in _context.Testimonials
orderby t.DisplayOrder ascending
select t;
if (query.Count() > 0)
{
var testimonial = query.ToList();
return testimonial;
}
else
{
return null;
}
}
#endregion
}
}
Then on the actual aspx.cs page I call said function like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using BlueTreeSecurity.App_Code;
using BlueTreeSecurity.App_Code.Data;
using BlueTreeSecurity.App_Code.Testimonials;
namespace BlueTreeSecurity
{
public partial class Testimonials : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
this.Page.Title = "Testimonials | ...";
Bind_Data();
}
protected void Bind_Data()
{
/** when i try to use intellisense here it's not recognized. **/
var testimonials = TestimonialService.GetAllTestimonials();
rptTestimonials.DataSource = testimonials;
rptTestimonials.DataBind();
}
}
}
The exact error spat back is this:
Error 1
An object reference is required for the non-static field, method, or property
'BlueTreeSecurity.App_Code.Testimonials.TestimonialService.GetAllTestimonials()'
Anything would be appreciated, guys. I'm ripping my hair out here.
Here's the Project structure
- Blue Tree Security (main project)
- App_Code
+ Data
+ Testimonials
+ TestimonialService.cs
Rest of the .aspx, .aspx.cs, and .ascx files.
If i'm not totally misinterpreting here, the error message you are getting is telling you what the problem is. Make GetAllTestimonials() static or instantiate a TestimonialService instance.
protected void Bind_Data()
{
var testimonialService = new TestimonialService(yourContextObect);
var testimonials = testimonialService.GetAllTestimonials();
rptTestimonials.DataSource = testimonials;
rptTestimonials.DataBind();
}

Ad Hoc Styles in WebControl

I'm creating a WebControl that is used in several of my ASP.NET pages. In one instance, I'd like to add some ad hoc style attributes such as Width and Float.
Since I can't anticipate which attributes will be needed in the future, I'd like the markup using the control to be able to add any random style. I've got the control so it supports standard styles like Color, Width, etc., but not Float.
Is there any way to allow such attributes to be specified in the markup and have them propagate through to the rendered control unchanged? I'd like not to have to create my own custom Float property and any other possible style that might be needed.
I tried just adding style="..." in the markup, but this is simply stripped out and does not appear anywhere in the rendered control.
My previous answer pertains to User Controls, my mistake!
For a WebControl you can over ride the AddAttributesToRender method.
The following seems to work quite well:
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public string style
{
get
{
String s = (String)ViewState["style"];
return ((s == null) ? String.Empty : s);
}
set
{
ViewState["style"] = value;
}
}
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
if(!string.IsNullOrEmpty(style))
{
writer.AddAttribute(HtmlTextWriterAttribute.Style, style);
}
}
EDIT: Changed public property to "style" to take advantage of intellisence.
I would add a CssClass property to your WebControl. This would allow any page that uses your control to supply its own look and feel.
It may not be what you are looking for but if you having a surrounding element you could apply the styles as a string as per the following:
.ascx
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="WebUserControl1.ascx.cs" Inherits="HubbInvestor.WebUserControl1" %>
<div style="<%=AdHocStyle%>">
Some Text:
<asp:Button ID="Button1" runat="server" Text="A Button" />
</div>
.ascx.cs
public partial class WebUserControl1 : System.Web.UI.UserControl
{
private string adHocStyle = string.Empty;
public string AdHocStyle
{
get { return adHocStyle; }
set { adHocStyle = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
}
}
of course you don't get any nice intellisense completion on the styles

ASP.NET: How to process postback events for my own control?

I have my own Control1 which is dynamically added as child control to Control2 which implements INamingContainer in CreateChildControls() of control2.
Control1 itself implements IPostBackEventHandler. But RaisePostBackEvent() method is never called on Control1, despite I do call postback method from JavaScript.
And yes, there are other controls which implement IPostBackEventHandler interface on the page.
What did I miss?
What could cause the issue?
UPDATE: Control1 is always created exactly the same way and assigned exactly the same ID in Control2
it looks like this in Control2:
protected override void CreateChildControls()
{
if(!this.DesignMode)
{
Control1 c = new Control1();
c.ID = "FIXED_ID";
}
base.CreateChildControls();
}
UPDATE2:
Control1:
public class Control1: Control, IPostBackEventHandler
{
...
protected virtual void RaisePostBackEvent(string eventArgument)
{
if(!String.IsNullOrEmpty(eventArgument))
{
// Some other code
}
}
}
if I add line
Page.RegisterRequiresRaiseEvent(c);
In CreateChildControls() in Control2 then this method is being called but always with null eventArgument.
UPDATE3:
In JavaScript on some onClick event I do the following:
__doPostBack(Control1.UniqueID,'commandId=MyCommand');
where Control1.UniqueID is of course substituted with real uniqueID during rendering. I checked, this script is being called.
Can you show us the source code of first control? Anyway there is a simple example.
public class TestControl2 : CompositeControl
{
protected override void CreateChildControls()
{
base.CreateChildControls();
if (!DesignMode)
this.Controls.Add(new TestControl());
}
}
public class TestControl : WebControl, IPostBackEventHandler
{
public TestControl() : base(HtmlTextWriterTag.Input) { }
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
writer.AddAttribute(HtmlTextWriterAttribute.Type, "button");
writer.AddAttribute(HtmlTextWriterAttribute.Name, base.UniqueID);
writer.AddAttribute(HtmlTextWriterAttribute.Onclick, Page.ClientScript.GetPostBackEventReference(this, null));
writer.AddAttribute(HtmlTextWriterAttribute.Value, "Submit Query");
}
void IPostBackEventHandler.RaisePostBackEvent(string eventArgument)
{
// Raise post back event
}
}
Edit
Why you are generating the post back script out of the control and manually? You have to use Page.ClientScript.GetPostBackEventReference method. It generates and includes some necessary inline and embedded scripts to the page.
Why you are deriving your class from Control? It's good for those controls which don't have any user interface.
From MSDN
This is the primary class that you
derive from when you develop custom
ASP.NET server controls. Control does
not have any user interface (UI)
specific features. If you are
authoring a control that does not have
a UI, or combines other controls that
render their own UI, derive from
Control. If you are authoring a
control that does have a UI, derive
from WebControl or any control in the
System.Web.UI.WebControls namespace
that provides an appropriate starting
point for your custom control.
You have to derive your control from WebControl class as follows.
public class TestCtl : WebControl, IPostBackEventHandler
{
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
// Add onclick event.
writer.AddAttribute(HtmlTextWriterAttribute.Onclick, Page.ClientScript.GetPostBackEventReference(this, "Arguments"));
}
void IPostBackEventHandler.RaisePostBackEvent(string eventArgument)
{
throw new NotImplementedException();
}
}
I'm going to guess that it's the "dynamically added as child control to Control2" that is the issue, but without any code it's pretty hard to diagnose.
When during the page lifecycle are you dynamically adding it? Are you recreating the dynamic control in the exact same way, with the same ID, after the postback?

ASP.NET User Control inner content

I have a user control which accepts a title attribute. I would also like that input inner HTML (ASP Controls also) inside of that user control tag like so:
<uc:customPanel title="My panel">
<h1>Here we can add whatever HTML or ASP controls we would like.</h1>
<asp:TextBox></asp:TextBox>
</uc:customPanel>
How can I achieve this? I have the title attribute working correctly.
Thanks.
Implement a class that extends Panel and implements INamingContainer:
public class Container: Panel, INamingContainer
{
}
Then, your CustomPanel needs to expose a property of type Container and another property of type ITemplate:
public Container ContainerContent
{
get
{
EnsureChildControls();
return content;
}
}
[TemplateContainer(typeof(Container))]
[TemplateInstance(TemplateInstance.Single)]
public virtual ITemplate Content
{
get { return templateContent; }
set { templateContent = value; }
}
Then in method CreateChildControls(), add this:
if (templateContent != null)
{
templateContent.InstantiateIn(content);
}
And you will be using it like this:
<uc:customPanel title="My panel">
<Content>
<h1>Here we can add whatever HTML or ASP controls we would like.</h1>
<asp:TextBox></asp:TextBox>
</Content>
</uc:customPanel>
You need to make sure EnsureChildControls is called. There are number of ways of doing that such as through the CreateChildControls base method but you can simply do this to get the inner contents of a custom control to render. It gets more complicated when you need to remember state and trigger events but for plain HTML this should work.
protected override void Render(HtmlTextWriter writer) {
EnsureChildControls();
base.Render(writer);
}

How to build ImageButton Control Adapter (or more general, how to build a simple control adapter)?

My inspiration for this question was my discovery of the very annoying default style (border-width: 0px;) on the ImageButton web control. The simple solution is to override it by adding your own style to the control e.g. Style="border-width: 2px;".
How every, it would have been nice to just make a simple control adapter that would just step in at the right place and just tell the control not to render the default styling.
After looking a bit at the code from the CSSFriendly ControlAdapter project, it seems like they are recreating much of the rendering, which is overkill for what I want to do -- i.e. just change the default styling that is rendered out.
So the question, how to just modify the rendering of the default styles through control adapters, and leave the rest as is?
Is it even possible?
Thanks, Egil.
There are two ways to do this. Both will require writing up a custom Control Adapter. Either you can set the actual value in code, or you can just not include the value at all and then use CSS to set your value. Here's the code you'll need to do this.
namespace TestApp
{
using System.IO;
using System.Web.UI;
using System.Web.UI.Adapters;
public class ImageAdapter : ControlAdapter
{
protected override void Render(HtmlTextWriter writer)
{
base.Render(new RewriteImageHtmlTextWriter(writer));
}
public class RewriteImageHtmlTextWriter : HtmlTextWriter
{
public RewriteImageHtmlTextWriter(TextWriter writer)
: base(writer)
{
InnerWriter = writer;
}
public RewriteImageHtmlTextWriter(HtmlTextWriter writer)
: base(writer)
{
InnerWriter = writer.InnerWriter;
}
public override void AddAttribute(HtmlTextWriterAttribute key, string value, bool fEncode)
{
if (key == HtmlTextWriterAttribute.Border)
{
// change the value
//value = "2";
// -or-
// don't include the value
//return;
}
base.AddAttribute(key, value, fEncode);
}
public override void AddStyleAttribute(HtmlTextWriterStyle key, string value)
{
if (key == HtmlTextWriterStyle.BorderWidth)
{
// change the value
//value = "2px";
// -or-
// don't include the value
//return;
}
base.AddStyleAttribute(key, value);
}
}
}
}
Then you'll need to add an entry into one of your browser files like this
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.WebControls.Image" adapterType="TestApp.ImageAdapter, TestApp" />
</controlAdapters>
</browser>
</browsers>

Resources