Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

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


:P
On this page:

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

 


The Voices of Reason


 

Justin Lovell
March 08, 2004

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

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.

Rick Strahl
March 08, 2004

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

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!

Mike
April 19, 2004

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

Got it working. Looks good.
thanks.

fanghaifei(from china)
April 26, 2004

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

Good ,I see it happily

David Prothero
May 12, 2004

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

Fantastic! This saved me. Thank you!!

dietrich
June 16, 2004

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

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

Rick Strahl
June 16, 2004

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

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...

Raph
September 14, 2004

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

Do you have a sample style sheet?

Rick Strahl
September 14, 2004

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

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;

}

yaip
December 10, 2004

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

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.

Doug Simpson
January 11, 2005

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

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?

Artimus Beams
July 13, 2005

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

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




ABC
February 06, 2006

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

Thanks. I was looking for same type of control collection from last 4-5 days.

Mauricio Londoño (Colombia)
March 29, 2006

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

GRACIAS!!!, Trabaja perfecto, busqué esta solucion por 4 dias.

Joel Rumerman
May 05, 2006

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

Rick,

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

- Joel

Irfan
May 26, 2006

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

This is very helpfull
It save lot of my time ....
Thanks ..........

florin
June 07, 2006

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

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.

Sunny
June 07, 2006

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

Great , Thanks a lot , Saved me from a stressfull weekend

Duke Fama
December 28, 2006

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

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
January 02, 2007

# Scott on Writing


Umar Farooq
February 12, 2007

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

Just beautiful.

Mike
June 08, 2007

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

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...

Kyriakos
January 04, 2008

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

Thanks mate!

Pxtl
January 29, 2008

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

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.

Pxtl
January 30, 2008

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

else
{
base(AddParsedSubObject);
}

is needed if you want anything else to be rendered in your usercontrol.

Pxtl
January 30, 2008

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

Whoop, nm - wrote that wrong, and misunderstood what was happening anyways.

kim
March 01, 2008

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

nice information.....

Aakanksh
April 10, 2008

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

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

Aleksey
July 03, 2008

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

Grate article!

Rob
February 18, 2009

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

Once again, great information. You rock!!!

melnac
March 16, 2010

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

Hi,
can you explain me how can i have a property with nested properties ?

Something like the asp:menuitem.

Thanks in advance,
melnac.

melnac
March 16, 2010

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

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.

António Santos
May 20, 2010

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

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;
}
}

}

António Santos
May 20, 2010

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

at last post should replace

private SelectionComponentParametersCollection _parameters = new SelectionComponentParametersCollection();

with

private MyParametersCollection _parameters = new MyParametersCollection();

sorry

Antonio Santos
May 20, 2010

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

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

Đonny
July 28, 2010

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

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)

Yury
October 20, 2010

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

Thank you for this article, it helped me very much!

Jeffrey Monroe
November 29, 2010

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

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. :)

Sebastien DErrico
October 26, 2011

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

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 - 2024