Contact   •   Products   •   Search

Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs

Collection properties in ASP.Net Server Controls (Building a TabStrip Control)


I’ve been struggling for the last few days with putting the finishing touch on a simple client side Tab control that I had built to provide the ability to break large pages into smaller pages that can be controlled via a simple tabstrip at the top. The control itself is super simple – basically a table with OnClick handlers, along with some generated script functions that activate the appropriate page using ID tags in the document.

 

The control works well and requires a handful of lines of code added to a page. Something as simple as this:

 

AdminTabs.AddTab("Main","default","Main");

AdminTabs.AddTab("Email","javascript:ActivateTab(this);ShowTabPage('Email');","Email");

AdminTabs.AddTab("CC Processing","default","CreditCard");

AdminTabs.AddTab("Configuration","default","Configuration");

if (!this.IsPostBack)

      AdminTabs.SelectedTab  = "Main"; // or set this in the designer

 

 

The tab control works all client-side, so there’s no server side event handling. The only thing that happens server side is the rendering and the state management of tracking the currently active page via a hidden variable on the form (rather than ViewState because the var is set on the client side via script code).

 

Creating this control was a snap and using ASP.Net’s object model was a clean and efficient affair. It basically involved creating a TabPage class, a TabPageCollection class (based on CollectionBase) and then implementing the actual control and rendering mechanism.

 

However, what wasn’t so easy was to create a design time interface. The tab control uses a collection of a TabPage class and getting this collection to properly persist into the HTML from the designer took some guess work and the help of several people who had gone through this before. I couldn't find all this in one place so I'm providing here what I found I needed.

 

The control should work like this:

 

<ww:wwwebtabcontrol id="WwWebTabControl2" runat="server" selectedtab="Tab2">

   <tabpages>

      <ww:tabpage caption="Main" actionlink="default" tabpageclientid="Tab1">ww:tabpage>

      <ww:tabpage caption="Email" actionlink="default" tabpageclientid="Tab2">ww:tabpage>

      <ww:tabpage caption="Configuration" actionlink="default"        

                  tabpageclientid="Tab3">ww:tabpage>

   tabpages>

ww:wwwebtabcontrol>

 

Just using the Collection on the control will not give you the above automatically. In fact the collection will not even be loaded with this data if you try to open the form with the collection. It will not even show up on the property sheet.

 

Specifying how complex control members such as objects and collections persist is driven through attributes that must be specified on the control itself and on any complex members. For a collection this means something like this:

 

Control Level

 

[ToolboxData("<{0}:wwWebTabControl runat=server>")]

[ToolboxBitmap(typeof(System.Web.UI.WebControls.Image))]

[ParseChildren(true)]

[PersistChildren(false)]

public class wwWebTabControl : Control

 

 

ParseChildren tells the control that it needs to run through the items and add them to the collection. Several people actually suggested to me that this attribute should be set to false but that caused items not to be parsed into the actual collection when the form first loaded. PersistChildren set to false indicates that the items are to be persisted as nested elements rather than as attributes on the control itself (using control-subproperty syntax). Both of these are vital!

 

The control should also implement the AddParsedSubObject method which makes sure that the inner items can be parsed into the collection:

 

protected override void AddParsedSubObject(object obj)

{

      if (obj is TabPage )

      {

            this.TabPages.Add((TabPage) obj);

            return;

      }

}

 

Collection Member Level

The collection property on the control itself also requies a couple of attributes to make it work properly.

 

[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]

[PersistenceMode(PersistenceMode.InnerProperty)]

public TabPageCollection TabPages

{

      get { return  Tabs; }

}

private TabPageCollection Tabs = new TabPageCollection();

                  

 

DesignerSerializationVisibility(DesignerSerializationVisibility.Content) determines how code is generated for this object. Specifically the above visibility creates individual TabPage object references in ASPX code behind file. Using visibility of Visible does not create code instances of the TabPages – which is actually desirable in this situation. You can always reference each of the pages dynamically through the collection itself.

 

The PersistenceMode attribute determines how the designer persists the collection’s content so it can load it at design or run time. In this case I chose InnerProperty which means it creates child controls (TabPages) that contain all their content as attributes. You can also use InnerDefaultProperty for objects that have default properties and that persist as the element content – a asp:ListItem is a good example of this type of item. Generally if you use a collection use InnerProperty. In addition you can also use Attribute which persists the object as attributes in the parent control. This won’t work for a collection, but it is useful for sub objects which can persist as with special syntax into the parent object by using ParentName-ChildProperty=’value’ syntax.

 

Item Implementation

When you use the above attributes VS.Net provides its default Collection Editor for editing the individual items – TabPage items in this case. To get items to display in the editor it’s easiest to derive them from Control as Control implements the default type converter interfaces needed for the CollectionEditor to create the items and assign property values and be able to serialize the collection into the HTML.


If you don’t derive from collection you need to implement a TypeConverter for your class and use the TypeConverter attribute to specify it. Using Control or any Control derived class is definitely easier although it adds a little overhead to your collection items.

 

Designer Rendering

 

One of the really cool things about ASP.Net (WinForms too) is that you can render controls at design time, so the user can actually see what the control will look like in the designer while building the control within the full page layout.

 

What I wanted to do is have the user see the control right from the start rather than just the default display which is basically the name of the control as a string. ASP. Net controls in the designer still call the Render method to draw themselves. This means as soon as I add tabs to my control the control will actually display them in real time with all the attribute settings reflected in the rendering.

 

Problem is that when there are no tabs, nothing gets rendered. So in order to do this I decided why not add a couple of dummy items to the collections when there are no items, render them, then remove them at the end of the process. The code to do this looks like this:

 

protected override void Render(HtmlTextWriter writer)

{

      bool NoTabs = false;

      string Selected = null;

 

      // *** If no tabs have been defined in design mode write a canned HTML display

      if (this.DesignMode && (this.TabPages == null || this.TabPages.Count == 0)  )

      {

            NoTabs = true;

            this.AddTab("No Tabs","default","Tab1");

            this.AddTab("No Tabs 2","default","Tab2");

            Selected = this.SelectedTab;

            this.SelectedTab="Tab2";

      }

 

      // *** Render the actual control

      this.RenderControl();

 

      // *** Dump the output into the ASP out stream

      writer.Write(this.Output);

 

      // *** Call the base to let it output the writer's output

      base.Render (writer);

 

      if (NoTabs)

      {

            this.TabPages.Clear();

            this.SelectedTab = Selected;

      }

}

 

Figuring out whether we are in DesignMode can be done by checking (HttpContext.Current == null) which is assigned here to a private property of the object.

 

Complete Tab Control Example Source

For completeness and reference’s sake I’m providing the code for the wwWebTabControl in its entirety here so you can see all the points above put together and in action.

 

This control works on the concept of hiding and displaying content based on ID tags in the client HTML document. When you click a button Id tags are activated and deactivated hiding or displaying content depending on the tab selection. You need to match up the TabPageClientId on each TabPage with an ID in the document that is to be displayed. For example:

 

AdminTabs.AddTab("Main","javascript:ActivateTab(this);ShowTabPage('Main');","Main");

AdminTabs.AddTab("Main","default","Main");

 

Main specifies that the tab caption. The second parameter is the script or Url that fires in response to a click. Here the code calls two generated functions which activate the tab specified and then show the the content that is wrapped in the Main ID tag in the HTML page. Main is the TabPageClientId that maps this tab to an ID in the HTML page.

 

The two statements above are identical – default is merely a shortcut for the first line which is the most common thing you’ll want to do – Activate the tab selected and display the content that is related to it.

 

In order to use the control you will likely want to add it your Tools window. From there you can simply drag and drop the control and start adding Tab Pages through the designer.

 

You can download the control along with a simple example page from here:

 

http://www.west-wind.com/files/tools/wwWebTabControl.zip

 

Make Donation


Feedback for this Post

 
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Justin Lovell March 08, 2004 @ 5:11am
Ah - Rick, I see that you have taken your time to blog about my suggestion at the ASP.NET Forums.

I would like to add to the fact that I have done two blog posts regarding collection management in server controls:

http://blogs.aspadvice.com/jlovell/archive/2004/03/03/688.aspx

http://blogs.aspadvice.com/jlovell/archive/2004/02/29/663.aspx

I do have simple code there that just "essentially" does the business of collections and server controls.

Cheers.
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Rick Strahl March 08, 2004 @ 11:21am
Justin, yes your comments where what eventually made me figure out the final problem and make it work. Most of the issue was operator error as usual :-}... Heck I had to write this up so I can remember how this works and find it again. Intuitive this stuff isn't... Thanks again for your help!
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Mike April 19, 2004 @ 11:50pm
Got it working. Looks good.
thanks.
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by fanghaifei(from china) April 26, 2004 @ 3:12pm
Good ,I see it happily
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by David Prothero May 12, 2004 @ 8:52am
Fantastic! This saved me. Thank you!!
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by dietrich June 16, 2004 @ 7:01am
I see the SelectedTabCssClass and TabCssClass during design time and when the page first loads, but stlyes are lost when I click the tab.

Thanks
--Dietrich
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Rick Strahl June 16, 2004 @ 7:08am
What are you doing when you switch pages? A tab switch basically happens on the client unless you hit use a physical link to another page...
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Raph September 14, 2004 @ 2:32am
Do you have a sample style sheet?
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Rick Strahl September 14, 2004 @ 7:06am
http://www.west-wind.com/wwstore/wwWebStore.css has the tab related settings in it. It's really just two settings you need.

.tabbutton, .selectedtabbutton
{
vertical-align: middle;
cursor: hand;
color: Black;
background-color: lightsteelblue;
text-align: center;
font-size: 8pt;
border-right: solid 2px white;
}
.selectedtabbutton
{
font-size: 10pt;
FONT-WEIGHT: bold;
COLOR: Cornsilk;
BACKGROUND-COLOR: #003399;
cursor:default;

}
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by yaip December 10, 2004 @ 6:50am
Works great in IE. In Mozilla things are different. Say, if I am on the third tab and my third tab has a button. If I click on the button, the contents of third tab are updated (as expected) but it takes me back to first tab.
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Doug Simpson January 11, 2005 @ 7:46am
Do you have experience with collections in a complex property of a control? I have noticed that the collection elements do not persist in the HTML designer until another (simple) property is changed?
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Artimus Beams July 13, 2005 @ 5:53am
I have been struggling to understand exactly what the "PersistenceMode.InnerProperty" attribute is used for. What attributes are for the designer, and which are used specifically for runtime. Is it safe to assume that the following two attributes below are used primarily for the designer, and that the .net runtime would still know to parse my sub objects into the collection property if these two attributes were not added, but I still had the [ParseChildren(true)] and [PersistChildren(false)] attribues added to the main container?<br><br>

1. [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]<br>

2. [PersistenceMode(PersistenceMode.InnerProperty)]

Thnx
AB



# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by ABC February 06, 2006 @ 7:57am
Thanks. I was looking for same type of control collection from last 4-5 days.
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Mauricio Londoño (Colombia) March 29, 2006 @ 10:00am
GRACIAS!!!, Trabaja perfecto, busqué esta solucion por 4 dias.
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Joel Rumerman May 05, 2006 @ 5:46am
Rick,

Again, you provide me with information that I couldn't find elsewhere! Thx!

- Joel
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Irfan May 26, 2006 @ 1:30am
This is very helpfull
It save lot of my time ....
Thanks ..........
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by florin June 07, 2006 @ 6:03am
I am trying to use this control on a page that has to add/remove the tabs dynamic. The problem i that the Visible property on the TabPage is not considered, and IT IS NOT kept between postbacks.

If the 1st part can be easy fixed (don't render of if you want to have this on client just hide it using css), I can't figure it out why Visible is not maintened.
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Sunny June 07, 2006 @ 11:47am
Great , Thanks a lot , Saved me from a stressfull weekend
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Duke Fama December 28, 2006 @ 4:47am
Hey, Can I programmatically manipulate it ? And what if I want IE 7 style tabs can I tweak this tho achieve that ?
# Scott on Writing
by Scott on Writing January 02, 2007 @ 1:18am
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Umar Farooq February 12, 2007 @ 2:49am
Just beautiful.
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Mike June 08, 2007 @ 4:51pm
Is it possible to add properties to the <tabpages> tag that persist between the designer properties grid and the declaration? I can't seem to get that to work properly...
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Kyriakos January 04, 2008 @ 3:15pm
Thanks mate!
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Pxtl January 29, 2008 @ 2:03pm
Very nice - been looking for this for a while. The MSDN documentation of creating your own user-controls with all the support MS ones get in the designer is atrocious.

In the "Item Implementation" section, you have a line that says "if you don't derive from collection" and then goes on to describe the usefulness of deriving from Control. Do you mean "Control" in the previous section, not "collection"? Because I prefer to use the generic list over implementing collections.
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Pxtl January 30, 2008 @ 7:49am
else
{
base(AddParsedSubObject);
}

is needed if you want anything else to be rendered in your usercontrol.
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Pxtl January 30, 2008 @ 7:50am
Whoop, nm - wrote that wrong, and misunderstood what was happening anyways.
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by kim March 01, 2008 @ 2:49am
nice information.....
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Aakanksh April 10, 2008 @ 7:52pm
Hi,

This is a very useful thing.. Thank You.

I needed some help. i put the tab in a Master Page. When i select a tab i move to another page. now how do i access that tab in the Master Page and set the selected tab.

If there is any other way to know which tab is selected (through Master Pages)please let me know.

Thank You
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Aleksey July 03, 2008 @ 3:58am
Grate article!
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Rob February 18, 2009 @ 1:12pm
Once again, great information. You rock!!!
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by melnac March 16, 2010 @ 10:29am
Hi,
can you explain me how can i have a property with nested properties ?

Something like the asp:menuitem.

Thanks in advance,
melnac.
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by melnac March 16, 2010 @ 5:13pm
Sorry for the 2nd post.

I want to do a collection property with multilevel nested child items.

<c1:MyControl>
<items>
<c1:MyItem>
<items>
<c1:MyItem>
<items>
... and so on
</items>
</c1:MyItem>
</items>
</c1:MyItem>
</items>
</c1:MyControl>

thanks in advance,
melnac.
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by António Santos May 20, 2010 @ 6:48pm
Hello

I´m getting nuts with the following error

MyControls.MyParametersCollection must have items of type MyControls.MyParameter.
MyControls.MyParameter is of type System.Web.HtmlControls.HtmlGenericControl

this is my code, please help ...


using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Data;
using System.Data.SqlClient;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Drawing;
using System.Web.UI.HtmlControls;
using System.Web.UI.Design;
using System.Drawing.Design;
using System.ComponentModel;
using System.ComponentModel.Design;

namespace MyControls
{
public class MyParametersCollection : CollectionBase
{
public MyParametersCollection()
{

}

public void Add(MyParameter param)
{
List.Add(param);
}

public void Remove(MyParameter param)
{
List.Remove(param);
}

public MyParameter this[int index]
{
get { return (MyParameter)List[index]; }
set { base.List[index] = value; }
}

}

public class MyParameter
{
public enum ParameterType
{
Conditional,
External
}

public enum ParameterDataType
{
String,
Numeric,
Boolean,
Description
}

public MyParameter()
{
}

ParameterType _type = ParameterType.Conditional;
ParameterDataType _datatype = ParameterDataType.String;
string _value = "", _pagecontrol = "";

[Category("MyControl"), Description("Component Type"), NotifyParentProperty(true)]
public ParameterType Type
{
get { return _type; }
set { _type = value; }
}

[Category("MyControl"), Description("Component Data type"), NotifyParentProperty(true)]
public ParameterDataType DataType
{
get { return _datatype; }
set { _datatype = value; }
}

[Category("MyControl"), Description("Component value"), NotifyParentProperty(true)]
public object Value
{
get { return _value; }
set { _value = value; }
}

[Category("MyControl"), Description("Component id"), NotifyParentProperty(true)]
public string PageControl
{
get { return _pagecontrol; }
set { _pagecontrol = value; }
}
}

[DefaultProperty("CurrentParameters"), ToolboxData("<{0}:MyControl runat=server></{0}:MyControl>")]
[Designer(typeof(MyControlDesigner))]
[ParseChildren(true)]
[PersistChildren(false)]
public class MyControl : WebControl, INamingContainer
{

private SelectionComponentParametersCollection _parameters = new SelectionComponentParametersCollection();

[Category("Control"), Description("Defines control")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[NotifyParentProperty(true)]
public MyParametersCollection CurrentParameters
{
get { return _parameters; }
}

protected override void AddParsedSubObject(object obj)
{
if (obj is MyParameter) {
this.CurrentParametersAdd((MyParameter)obj);
return;
}
}

protected override void Render(HtmlTextWriter output)
{
EnsureChildControls();
base.Render(output);
}

}

public class MyControlDesigner : ControlDesigner
{
string widthcode,widthdescription;

public override bool AllowResize
{
get { return true; }
}

public override string GetDesignTimeHtml()
{

string designTimeHtml = "<input type=\"text\" style=\"height: 20px; width:200px"\" value=\"\">";
designTimeHtml += "<button style=\"color:white;background-color:navy;font-family:Verdana;font-size:XX-Small;\">?</button>";
designTimeHtml += "<input type=\"text\" style=\"height: 20px; width:"450px\" value=\"\">";

return designTimeHtml;
}
}

}
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by António Santos May 20, 2010 @ 6:51pm
at last post should replace

private SelectionComponentParametersCollection _parameters = new SelectionComponentParametersCollection();

with

private MyParametersCollection _parameters = new MyParametersCollection();

sorry
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Antonio Santos May 20, 2010 @ 6:56pm
Design

<%@ Register TagPrefix="btc" Namespace="MyControls"%>

<btc:MyControl id="MYCUSTOMER" runat="server" CssClass="fonteedicao">
<btc:CurrentParameters>
<btc:MyParameter Type="Conditional" DataType="String" Value="0001" PageControl="GRDCUSTOMERS" />
</btc:CurrentParameters>
</btc:MyControl>

Thanks
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Đonny July 28, 2010 @ 12:57am
One important think (I spent half-day with) is thet the collection property (tabpages) must be statically typed to type which probably:
1) Implements non-generic ICollection
2) Implements Add method (or extension Add method exists for it)
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Yury October 20, 2010 @ 4:16am
Thank you for this article, it helped me very much!
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Jeffrey Monroe November 29, 2010 @ 4:54pm
Doug Simpson on January 11, 2005 @ 7:46 am made the comment:

Do you have experience with collections in a complex property of a control? I have noticed that the collection elements do not persist in the HTML designer until another (simple) property is changed?

I am running into a similar problem with Smart Tags and action lists. The property setter for the action list is never called for a collection so the HTML designer is never updated until I update a simple string property in the Smart Tag.

Anyone had experience with this?


Thanks and great article Rick.... still valid after all these years. :)
# re: Collection properties in ASP.Net Server Controls (Building a TabStrip Control)
by Sebastien DErrico October 26, 2011 @ 8:55am
Thank you very much Rick!

I was struggling for 2 days about multiple collections in one server component.

Thanks! Thanks! Thanks!
Sébastien


IT Development Consultant
sebastien@hollox.net
(438) 882-8687
 


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2014