Update: The 30 September 2009 release of the AjaxControlToolkit doesn't have the error that I fix here. My patch was applied in July and from September on the bug is gone in the official release as well. Good riddance! :)

==== Obsolete post follows

Update: On June 20th 2009, Codeplex notified me that the patch I did for the ACT has been applied. I haven't tested it yet, though. Get the latest source (not latest stable version) and you should be fine.

This post was updated on the 1st of July 2008 with some clearer explanations and some error corrections thanks to Santoé who pointed out some mistakes.

My ASP.Net app uses a TabContainer, with a TabPanel in the *x code and with additional TabPanels added dynamically in codebehind.

Well, I got a lot of errors so I've decided to debug and change the control in order to fix it.

Step 1: download the AjaxControlToolKit with source included and open the project locally.

First error : Specified argument was out of the range of valid values. Parameter name: index, somewhere in the TabPanelCollection indexer. The problem actually occurs in TabContainer in LoadClientState(string clientState) where there is a for (int i = 0; i < tabState.Length ; i++). It doesn't take into account the possibility that the number of Tabs and the number of values taken from the tabState can be different. So the code must look like this: for (int i = 0; i < tabState.Length && i < Tabs.Count; i++).

Step 2: In the AjaxControlToolkit\AjaxControlToolkit\Tabs\ folder there is a TabContainer.cs file. Change for (int i = 0; i < tabState.Length ; i++) to for (int i = 0; i < tabState.Length && i < Tabs.Count; i++).

The second error is actually a thrown error in the ActiveTabIndex property setter: if (value >= Tabs.Count) { throw new ArgumentOutOfRangeException("value"); }, but it all comes from this: if (Tabs.Count==0 && !_initialized), because it doesn't take into account the possibility that the Tabs.Count is smaller than the ActiveTabIndex, but not zero. So that should look like this: if (value >= Tabs.Count && !_initialized).

I've downloaded the latest AjaxControlToolKit (version Version 1.0.20229 - Feb 29 2008) and the scratched fix above doesn't seem to work anymore. Instead, try patching the ActiveTabIndex property like this:

[DefaultValue(-1)]
[Category("Behavior")]
[ExtenderControlProperty]
[ClientPropertyName("activeTabIndex")]
public virtual int ActiveTabIndex
{
get
{
if (_cachedActiveTabIndex > -1)
{
return _cachedActiveTabIndex;
}
if (Tabs.Count == 0)
{
return -1;
}
return _activeTabIndex;
}
set
{
if (value < -1)
throw new ArgumentOutOfRangeException("value");
if (Tabs.Count == 0 && !_initialized)
{
_cachedActiveTabIndex = value;
}
else
{
if (ActiveTabIndex != value)
{
if (ActiveTabIndex != -1
&& ActiveTabIndex < Tabs.Count)
{
Tabs[ActiveTabIndex].Active = false;
}
if (value >= Tabs.Count)
{
_activeTabIndex = Tabs.Count-1;
_cachedActiveTabIndex = value;
}
else
{
_activeTabIndex = value;
_cachedActiveTabIndex = -1;
}
if (ActiveTabIndex != -1
&& ActiveTabIndex < Tabs.Count)
{
Tabs[ActiveTabIndex].Active = true;
}
}
}
}
}


In other words, remove the ArgumentException code block and move the condition inside the next block, where you set the real _activeTabIndex to the highest legal value, yet you put the real value in _cachedActiveTabIndex.

Step 3: in the AjaxControlToolkit\AjaxControlToolkit\Tabs\ folder there is a TabContainer.cs file. Change the ActiveTabIndex property with the code above.

The same thing must be done in Javascript, in the Tabs.js file, if you intend to use a TabContainer with no static tabs defined. In case you do that, you will get a javascript error "Microsoft JScript runtime error: Sys.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: value
". The fix is to change the set_activeTabIndex function of the TabContainer in the file tabs.js to this:
set_activeTabIndex : function(value) {
if (!this.get_isInitialized()) {
this._cachedActiveTabIndex = value;
} else {
if (this._activeTabIndex != -1) {
this.get_tabs()[this._activeTabIndex]._set_active(false);
}
if (value < -1 || value >= this.get_tabs().length) {
this._activeTabIndex = this.get_tabs().length-1;
this._cachedActiveTabIndex=value;
} else {
this._activeTabIndex = value;
this._cachedActiveTabIndex=-1;
}
if (this._activeTabIndex != -1) {
this.get_tabs()[this._activeTabIndex]._set_active(true);
}
if (this._loaded) {
this.raiseActiveTabChanged();
}
this.raisePropertyChanged("activeTabIndex");
}
},


Step 4: in the AjaxControlToolkit\AjaxControlToolkit\Tabs\ folder there is a tabs.js file. Change the set_activeTabIndex function with the code above.

This fixed it for me for now.

Step 5: Compile the now patched AjaxControlToolKit and use the resulting dll in your project instead of the default one.

As a reference, my test app does the following things:
  • Starts with a TabContainer with single TabPanel defined in the aspx
  • Has a button that adds new tabs to the TabContainer dynamically on the Click event
  • The panels have buttons in them that can be clicked
  • The active tab must be preserved during postbacks
  • The page must work both on synchronous and asynchronous postbacks


Here is the code for the page

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using AjaxControlToolkit;

public partial class _Default : Page
{
private int? _tabCount;

/// <summary>
/// Keep in ViewState the number of dynamically added tabs
/// </summary>
public int TabCount
{
get
{
if (_tabCount == null)
{
if (ViewState["TabCount"] == null)
TabCount = 0;
else
TabCount = (int) ViewState["TabCount"];
}
return _tabCount.Value;
}
set
{
_tabCount = value;
ViewState["TabCount"] = value;
}
}

protected void Page_Load(object sender, EventArgs e)
{
InitTabs();
}

/// <summary>
/// Add the dynamical tabs after each postback
/// </summary>
private void InitTabs()
{
for (int c = 0; c < TabCount; c++)
AddPanel();
}

/// <summary>
/// Dynamically add a panel to the TabContainer
/// </summary>
private void AddPanel()
{
TabPanel tp = new TabPanel();
tp.HeaderText = "Test Dinamic";
TextBox tb = new TextBox();
Button btn = new Button();
btn.Text = "Click me!";
tp.Controls.Add(btn);
tp.Controls.Add(tb);
TabContainer1.Tabs.Add(tp);
}

/// <summary>
/// Click event to add a new panel
/// and update the TabCount property
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void btnAddPanel_Click(object sender, EventArgs e)
{
AddPanel();
// do this if you didn't have any staticly defined
// tabs or else the dynamic tabs will be invisible
If (TabCount==0) TabContainer1.ActiveTabIndex=0;
TabCount++;
}
}

Comments

Jayaraman Kumar

Thanks. Can we get the fixed code for the dll version 3.0.2

Jayaraman Kumar

Siderite

Haven&#39;t been workin gin the area for quite some time. I am sorry I can&#39;t help you right now.

Siderite

sunil

hi all, this is sunil. I am trying to remove a tab dynamically and adding them. but the newly added tab is not getting active on tabchanged event Plz. help me

sunil

Minakshi

I gone through your blog... its amazing .... thanks ... whenever i tried to remove tab i am showing confirmation message, on click of &quot;yes&quot; i am getting &quot;microsoft jscript runtime error &#39;null&#39; is null or not an object&quot; Please help me ...

Minakshi

SunilPT

This piece of code really helps man... it saved my life, thank you very very much.!!!!!!!!

SunilPT

Anonymous

Download latest version of AjaxControlToolkit http://ajaxcontroltoolkit.codeplex.com/releases/view/43475

Anonymous

Anonymous

http://ajaxcontroltoolkit.codeplex.com/releases/view/43475 You can download the latest version of toolkit and problem will be resolved.

Anonymous

Swapnil

I still get this error while any postback event occure like selecetedindex changg etc. Please help me. !!!!!!!!!

Swapnil

Siderite

If it is an ASP.Net Button, yes it does. The thing that you see on web pages as an attribute (OnClick) in the code behind is just Click. A weird ASP.Net only convention. So you do something like *btnDynamic.Click+=method;* or you could even use lambda expressions if you use .Net 3.5 *btnDynamic.Click+=(sender,args)=&gt;{ // do something };*

Siderite

Anonymous

Thanks Siderite. I think I understand what you said above but how do I add an event to a dynamically created button (i.e. &quot;Click Me&quot;) and how do I handle it in the code behind? It&#39;s dynamically added but there is no click event on the button. -Chris

Anonymous

Siderite

In the code above, you have the TabCount view state property to hold to the count of created tabs, so that you can recreate them in InitTabs. It&#39;s the principle I was telling you above. Since the Click event is after Load, you need to create the control in the Click event as well.

Siderite

Siderite

Every time you put a control in the aspx or ascx, you actually instruct it to add that control in the init phase. It&#39;s like loading the aspx with a LoadXml method in the Init phase. Controls that are added to a control collection try to &quot;catch up&quot; with the state of their parent. So even if you add a control in the Load phase, it will still execute everything until and including the load phase. In order for events to fire, you need to have the control created after you postback, and in the Init or Load phases, not later. The OnClick event is later. That being said, it means that if you add a control dynamically in a click event, you need to create it in the click handler method (and have an ID), but if you want it to have events after you postback you also have to recreate any control you added in the Init or Load phases.

Siderite

Anonymous

This is great stuff, Thanks. A question though: How do you handle the Click event of a dynamically added button on one the dynamically added TabPanels? Say you want to save some data on a panel and the user clicks a save button on that panel. I suppose you could have one button and then keep tract of the active tab but you still need to reference text boxes, etc.

Anonymous

Siderite

Actually, the problem is deeper than this. In the sources from July, the changes in my patch were added, I tested it myself. However, it seems somebody patched the patch. Somebody wanted that damn error throwing there, so they just added it again. I am sorry, but I can&#39;t really work on this right now. Hopefully in the coming weeks I will be able to test it everything and see what is going on there.

Siderite

Quinton

I just downloaded the latest source from &quot;http://ajaxcontroltoolkit.codeplex.com/SourceControl/ListDownloadableCommits.aspx#DownloadLatest&quot;, and the fixes above have not been added as of today. I made the changes according to your suggestions and it works perfectly for me. Thank you for taking the time to let everyone about this.

Quinton

Anonymous

Thanks a lot, your post was Gold!! I hope they add this to the next official release...

Anonymous

Core Development

thanks. it helps. page init worked for me !! protected void Page_Init(object sender, EventArgs e) { InitTabs(); }

Core Development

Anonymous

hello, I saw your code and implemented the same in my project. But i made one change. ie call your InitTab(); in Page_Init that way you will get your dynamic TabPanel... hope this will solve your problem.. ashish

Anonymous

Anonymous

Excellent work. Thanks you so much. Saved me a giant headache!

Anonymous

Rep100

Thanks, your code was very helpfully. Excellent work!!!

Rep100

it's MV

Thanks you very much. Your code is excellent. I searched so many sites. But your help makes more useful to me.

it's MV

Marcelo

Man, thanks a lot. It solved my problem. Does anybody know if this bug is reported to microsoft?

Marcelo

Siderite

The session is used for storing values, like strings, integers, serialized objects. You cannot really store a tab panel there. What you need to do is recreate the tab panel every time, based on values in the session.

Siderite

JannuD

Hi Siderite, You have done a wonderful job. I am using IDs for each tab I dynamically create. Between postbacks I am putting the tabs in session so that I can build it back along with the new tab request. However, since, I am populating the tabs from session, the script handler is lost &amp; an exception is thrown... Can you help me with this?

JannuD

Siderite

Well, look for me in the blog chat when I am at work, or leave me an email as a message in the same chat and I will contact you for the code sample. If you can make a small project demoing the bug, I can try to find a fix.

Siderite

Corporate Dog

Hmmm. Still seems like there&#39;s a problem here. I have a delete button on each tab that triggers a click event in codebehind, which removes the active TabPanel from the TabContainer. The problem crops up in subsequent postbacks (for instance, a &#39;Save&#39; postback) when the deleted tab WASN&#39;T the last tab in my container. The viewstate still appears to contain data (or at least placeholders) for that deleted tab, and inserts that data into the (now smaller) set of tabs. So a tab gets populated with the garbage viewstate data, and all of my valid tab viewstate data gets shifted to the next tab, pushing the last tab&#39;s data into oblivion. I&#39;m probably explaining this poorly, but I suspect the issue lies in (or around) Siderite&#39;s code that loops through the tabstate, and uses the tab count as an upper limit. Anyone else experience this?

Corporate Dog

Siderite

You are all welcome. Creating new things is a lot more complicated than fixing bugs in existing products. Give credit where it&#39;s due.

Siderite

Qui

After all this time since you posted your entry your still a live-saver. Worked like a charm! Amazing Microsoft still has not solved this bug. Perhaps they should hire you? ;)

Qui

ravindar (ravindar.thati@zoho.

Hey friend, if you were with me, i would really hug you to express my happyness. you saved my time and made me to feel happy. I have been trying for the solution for the past 2 days. Atlast you saved me thank you

ravindar (ravindar.thati@zoho.

Mmmmagic

Will you marry me? I have been banging my head against the wall for a couple of days trying to integrate this tab control into dot net nuke. thanks for your help.

Mmmmagic

Siderite

Thank you, guys! :) Your comments make me write the blog. Has anyone tried the ActiveTabIndex=0 approach? (except, obviously, the poster)

Siderite

da5id

Thank You! I really hope you have submitted this for the next release. I thought that I was in for a couple of hours of trying to find the right value to ace in the ViewState, then I found your blog post. Thanks again.

da5id

Anonymous

I found another way of fixing this. After adding or removing tabs dynamically, call this: this.tabContainer.ActiveTabIndex = 0;

Anonymous

Siderite

Well, yes, if your active tab is set to 5 and you delete the 5th tab, you will have problems there. The best solution, I think, is to make a check and see if the activetabindex is equal or greater than tab count and in that case automatically set it to tabcount-1. But that is, again, a tweak of the TabContainer sources.

Siderite

Anonymous

ok. i&#39;m going to answer my own question. when removing a tab, it generates a javascript error. to prevent this, you&#39;ll need to set the activeTab! if (TabContainerMain.Tabs.Count &gt; 0) { TabContainerMain.Tabs.RemoveAt(0); TabCount--; if (TabContainerMain.Tabs.Count &gt; 0) TabContainerMain.ActiveTab = TabContainerMain.Tabs[TabContainerMain.Tabs.Count - 1]; } hope this helps.

Anonymous

Anonymous

hi. thanks. your article helped with the add tab issue. however, doing the remove throws a javascript error i&#39;m executing tabcontrolmain.tabs.remove(tabcontrolmain.activetab) Sys.ArgumentOutOfRangeException : any ideas?

Anonymous

swapna

Thanks a ton !! It helped me get the dynamic tabs on my page work... Did anyone let the ajaxtoolkit developers know of this? It would be good if they fix this in their next release... Anyways Thanks again for your detailed explanation!

swapna

santo2

This comment has been removed by the author.

santo2

Anonymous

This comment has been removed by a blog administrator.

Anonymous

Siderite

well, it does solve the problem of creating the dynamic tabs, but if you want to create them during init time you need to find a solution to remember the dynamic tab count since ViewState is not accessible in Init.

Siderite

jon_s

does this help solve the problem where dynamically generating tabs needs to occur in the page_init. I am building tabs based on the URL and my getUrl class occurs after the controls on the page init event

jon_s

lumpi

thanks a lot. You Saved my day. Wonderfull

lumpi

Ironman

I did a rebuild of the ajax toolkit and seems to work out right now. Stupid of me not to rebuild, but I&#39;m relative new to .NET Thanks.

Ironman

Siderite

It seems to me that you changed the project but did not refresh the DLL reference. Or maybe you referenced the DLL in Release and compiled in Debug or something like that. I haven&#39;t even installed 3.5 yet, so I don&#39;t know what the differences between the two versions of AjaxControlToolkit are. As soon as I will, I will probably update this post.

Siderite

Ironman

Hi SideRite, With the latest toolkit for framework 3.5 I still got the problem. As you can read here http://www.codeplex.com/AtlasControlToolkit/WorkItem/View.aspx?WorkItemId=15658 there is someone else also complaining. I tried to modify the .cs and .js but then I got the error stating the source and the DLL are not the same. What should I do ... ?

Ironman

Anonymous

Thank you very much for your hint, the bug is still present in the current version of the toolkit...

Anonymous

Siderite

Sorry, mate, I am up to my ears in boring work. Can you please post the relevant code so I can debug it and find a fix? That would significantly speed things up.

Siderite

Nuno

Hi, thanks for this fantastic solution, but I&#39;m also having the problem with the wrong tab count... Have you come to any conclusion about this already??

Nuno

Siderite

I don&#39;t have the time to fix this rightaway, but I will try to do something about it. Keep checking this link.

Siderite

Anonymous

May many thanks to u.. its solved my problem, but i m alog geting one more problem, whenevr i create some tabs dynamically , i m geting a wrong tab count on any other event of page..

Anonymous

Luis

Man thanks, you really helped it out. You know I could have sword that the error was on my code because MS would never release something with a bug on it !!!!

Luis

Post a comment