Dynamically adding tab panels to tab containers

孙莫希
2023-12-01
Remember that ASP.NET applications and websites operate in a Stateless Environment.

In other words, after your page is finished processing the request all of the objects that were used for processing are destroyed.

So, when the next page request occurs the objects required to do the page processing are recreated.

In your case, however, you are declaring a Tab within a Button Click event....so that means that when the page request happens again this tab does not exist and, well errors and problems ensue.

So, to fix this problem you need to recreate your tabs in your Page Init event so that they exist at the time when the Page's ViewState is loaded (especially if your Tabs cause a postback to the server or else you'll lose the event data associated with the tab change ...so your server-side tab index changed event will never occur).

I'm not sure if any of this is making sense so I'll try to explain this in some example code.

Here is the ASPX Page markup...it contains a button called "addTab" which, when clicked, will dynamically add a new TabPanel to the TabContainer (which is also on the page). The page also has a Label on it "currentTabIndex" which will display the ActiveTab's ID when it is selected. The Label's text is set when the TabContainer posts back to the server when the ActiveTabChanged event occurs:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication2._Default" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="smanager" runat="server"></asp:ScriptManager>
        <asp:Button ID="addTab" Text="Add Tab" οnclick="addTab_Click" runat="server"/>
        <cc1:TabContainer ID="TabContainerContent" runat="server"  Height="150px" BackColor="White"  AutoPostBack="True">
        </cc1:TabContainer>  
        <asp:Label ID="currentTabIndex" runat="server"></asp:Label>
    </div>
    </form>
</body>
</html>

The first thing you need to (in your server side code) is declare some sort of container object that you can store the IDs of your Tabs in. This container object should have a page level scope. In this example I'm using a List<string> to store the IDs of my tabs in the following code:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication2
{
  public partial class _Default : System.Web.UI.Page
  {
    private List<string> dynamicTabIDs;
  }
}

Now, because ASP.NET applications/websites are stateless I am going to store this container in Session so that I can recreate it the next time the page loads. I'm doing this in the Page PreRender event because I plan on adding items to it sometime during the Page LifeCycle which occurs after the PageLoad event:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication2
{
  public partial class _Default : System.Web.UI.Page
  {
    private List<string> dynamicTabIDs;
 
    protected void Page_PreRender(object sender, EventArgs e){
      Session["dynamicTabIDs"] = dynamicTabIDs;
    }
  }
}

Now the list of TabIds is being stored somewhere so that I can retrieve it in order to recreate the dynamic TabPanels (tabs) the next time the page is requested. 

Where do I need to recreate dynamic TabPanels? 
In the Page Init event.

Why do I need to recreate the dynamic TabPanels in the Page Init Event and not in the Page Load event?

Well, because the Init event occurs before the Page's ViewState is loaded. The ViewState contains state information about all of the controls on the Page. If your controls do not exist at the time when ViewState is loaded then the ViewState for that control will not be loaded. Since ViewState helps in the creation of server side Events it is important that your controls exist at this point.

Hopefully you understand what I'm getting at here.

Back to the code, in the Page Init event retrieve the tab-IDs-container...if you can't then you'll have to create a new container for the tabs. Please note that you cannot use the Page's IsPostback property in this stage of the ASP.NET Page Life Cycle because the IsPostback property is set when ViewState is loaded...and the ViewState is loaded After the Page Init event...which means the IsPostback hasn't been set at this point and it will always return false.

Once you have retrieved the list of tab-IDs you need to recreate the tab associated with it (in the Page Init event):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication2
{

  public partial class _Default : System.Web.UI.Page
  {
    private List<string> dynamicTabIDs;

    protected void Page_Init(object sender, EventArgs e){
      //Checking to see if the dynamicTabIDs are in Session
      if (Session["dynamicTabIDs"] != null)
      {
        //if dynamicTabIDs are in session, recreating the Tabs
        //that are associated with the Tab IDs
        //and adding them to the TabContainer that will contain
        //all of the dynamic tabs.

        //retrieving the tab IDs from session:
        dynamicTabIDs = (List<string>)Session["dynamicTabIDs"];

        //looping through each TabID in session
        //and recreating the TabPanel that is associated with that tabID
        foreach (string tabID in dynamicTabIDs)
        {
          //creating a new TabPanel that is associated with the TabID
          AjaxControlToolkit.TabPanel tab = new AjaxControlToolkit.TabPanel();
          //Setting the ID property of the TabPanel
          tab.ID = tabID;
          //setting the TabPanel's HeaderText
          tab.HeaderText = "Tab " + (TabContainerContent.Tabs.Count + 1).ToString();

          //creating a Label to add to the TabPanel...at this point you'll have to
          //create whatever controls are required for the tab...
          Label tabContent = new Label();
          //Giving the Label an ID
          tabContent.ID = "lbl_tab_" + TabContainerContent.Tabs.Count.ToString();
          //Setting the Label's text
          tabContent.Text = "Tab " + (TabContainerContent.Tabs.Count + 1).ToString();

          //Adding the Label to the TabPanel
          tab.Controls.Add(tabContent);
 
          //Adding the TabPanel to the TabContainer that contains the dynamic tabs
          TabContainerContent.Tabs.Add(tab);
        }
      }
      else
      { //Creating a new list of dynamicTabIDs because one doesn't exist yet in session.
        dynamicTabIDs = new List<string>();
      }
    }
 
    protected void Page_PreRender(object sender, EventArgs e){
      Session["dynamicTabIDs"] = dynamicTabIDs;
    }
 
  }
}

Ok, so far so good. Now we just need to dynamically add a new TabPanel to the Tab Content when the "addTab" button is clicked...easy enough.

In the "addTab" button's click event, create a new tab and add it to the TabContainer that contains the dynamic TabPanels and add it's ID to the dynamicTabIDs list:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication2
{

  public partial class _Default : System.Web.UI.Page
  {
    private List<string> dynamicTabIDs;
 
    protected void Page_Init(object sender, EventArgs e){
      //Checking to see if the dynamicTabIDs are in Session
      if (Session["dynamicTabIDs"] != null)
      {
        //if dynamicTabIDs are in session, recreating the Tabs
        //that are associated with the Tab IDs
        //and adding them to the TabContainer that will contain
        //all of the dynamic tabs.

        //retrieving the tab IDs from session:
        dynamicTabIDs = (List<string>)Session["dynamicTabIDs"];

        //looping through each TabID in session
        //and recreating the TabPanel that is associated with that tabID
        foreach (string tabID in dynamicTabIDs)
        {
          //creating a new TabPanel that is associated with the TabID
          AjaxControlToolkit.TabPanel tab = new AjaxControlToolkit.TabPanel();
          //Setting the ID property of the TabPanel
          tab.ID = tabID;
          //setting the TabPanel's HeaderText
          tab.HeaderText = "Tab " + (TabContainerContent.Tabs.Count + 1).ToString();

          //creating a Label to add to the TabPanel...at this point you'll have to
          //create whatever controls are required for the tab...
          Label tabContent = new Label();
          //Giving the Label an ID
          tabContent.ID = "lbl_tab_" + TabContainerContent.Tabs.Count.ToString();
          //Setting the Label's text
          tabContent.Text = "Tab " + (TabContainerContent.Tabs.Count + 1).ToString();

          //Adding the Label to the TabPanel
          tab.Controls.Add(tabContent);

          //Adding the TabPanel to the TabContainer that contains the dynamic tabs
          TabContainerContent.Tabs.Add(tab);
        }
      }
      else
      { //Creating a new list of dynamicTabIDs because one doesn't exist yet in session.
        dynamicTabIDs = new List<string>();
      }
    }
 
    protected void Page_PreRender(object sender, EventArgs e){
      Session["dynamicTabIDs"] = dynamicTabIDs;
    }
 
    protected void addTab_Click(object sender, EventArgs e)
    {
        //creating a new TabPanel t
        AjaxControlToolkit.TabPanel tab = new AjaxControlToolkit.TabPanel();
        //Setting the ID property of the TabPanel
        tab.ID = "tab" + Convert.ToString(TabContainerContent.Tabs.Count);
        //setting the TabPanel's HeaderText
        tab.HeaderText = "Tab " + (TabContainerContent.Tabs.Count + 1).ToString();

        //creating a Label to add to the TabPanel...at this point you'll have to
        //create whatever controls are required for the tab...
        Label tabContent = new Label();
        //Giving the Label an ID
        tabContent.ID = "lbl_tab_" + TabContainerContent.Tabs.Count.ToString();
        //Setting the Label's text
        tabContent.Text = "Tab " + (TabContainerContent.Tabs.Count + 1).ToString();

        //Adding the Label to the TabPanel
        tab.Controls.Add(tabContent);

        //Adding the TabPanel to the TabContainer that contains the dynamic tabs
        TabContainerContent.Tabs.Add(tab);

        //Setting the ActiveTab to the newest tab added
        //Please be aware that you MUST set the ActiveTab or else you'll run into an exception
        TabContainerContent.ActiveTab = tab;
 
        //Adding the Tab's ID to the dynamicTabIDs list
        dynamicTabIDs.Add(tab.ID);
    }

  }

}

Lastly, we need to add code that handles the TabContainer's OnActiveTabChanged event. In the ASPX page markup we have specified that the method to call during this event is named "TabContainerContent_OnActiveTabChanged". This means that we have to add a method to the server side code with this name. In this method, to demonstrate that TabContainer's events work properly, we are simply going to set the "currentTabIndex" label's text to the Tab ID of the active tab:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication2
{

  public partial class _Default : System.Web.UI.Page
  {
    private List<string> dynamicTabIDs;

    protected void Page_Init(object sender, EventArgs e){
      //Checking to see if the dynamicTabIDs are in Session
      if (Session["dynamicTabIDs"] != null)
      {
        //if dynamicTabIDs are in session, recreating the Tabs
        //that are associated with the Tab IDs
        //and adding them to the TabContainer that will contain
        //all of the dynamic tabs.
 
        //retrieving the tab IDs from session:
        dynamicTabIDs = (List<string>)Session["dynamicTabIDs"];

        //looping through each TabID in session
        //and recreating the TabPanel that is associated with that tabID
        foreach (string tabID in dynamicTabIDs)
        {
          //creating a new TabPanel that is associated with the TabID
          AjaxControlToolkit.TabPanel tab = new AjaxControlToolkit.TabPanel();
          //Setting the ID property of the TabPanel
          tab.ID = tabID;
          //setting the TabPanel's HeaderText
          tab.HeaderText = "Tab " + (TabContainerContent.Tabs.Count + 1).ToString();

          //creating a Label to add to the TabPanel...at this point you'll have to
          //create whatever controls are required for the tab...
          Label tabContent = new Label();
          //Giving the Label an ID
          tabContent.ID = "lbl_tab_" + TabContainerContent.Tabs.Count.ToString();
          //Setting the Label's text
          tabContent.Text = "Tab " + (TabContainerContent.Tabs.Count + 1).ToString();

          //Adding the Label to the TabPanel
          tab.Controls.Add(tabContent);

          //Adding the TabPanel to the TabContainer that contains the dynamic tabs
          TabContainerContent.Tabs.Add(tab);
        }
      }
      else
      { //Creating a new list of dynamicTabIDs because one doesn't exist yet in session.
        dynamicTabIDs = new List<string>();
      }
    }
 
    protected void Page_PreRender(object sender, EventArgs e){
      Session["dynamicTabIDs"] = dynamicTabIDs;
    }

    protected void addTab_Click(object sender, EventArgs e)
    {
        //creating a new TabPanel t
        AjaxControlToolkit.TabPanel tab = new AjaxControlToolkit.TabPanel();
        //Setting the ID property of the TabPanel
        tab.ID = "tab" + Convert.ToString(TabContainerContent.Tabs.Count);
        //setting the TabPanel's HeaderText
        tab.HeaderText = "Tab " + (TabContainerContent.Tabs.Count + 1).ToString();

        //creating a Label to add to the TabPanel...at this point you'll have to
        //create whatever controls are required for the tab...
        Label tabContent = new Label();
        //Giving the Label an ID
        tabContent.ID = "lbl_tab_" + TabContainerContent.Tabs.Count.ToString();
        //Setting the Label's text
        tabContent.Text = "Tab " + (TabContainerContent.Tabs.Count + 1).ToString();

        //Adding the Label to the TabPanel
        tab.Controls.Add(tabContent);

        //Adding the TabPanel to the TabContainer that contains the dynamic tabs
        TabContainerContent.Tabs.Add(tab);

        //Setting the ActiveTab to the newest tab added
        //Please be aware that you MUST set the ActiveTab or else you'll run into an exception
        TabContainerContent.ActiveTab = tab;
 
        //Adding the Tab's ID to the dynamicTabIDs list
        dynamicTabIDs.Add(tab.ID);
    }

    protected void TabContainerContent_OnActiveTabChanged(object sender, EventArgs e) {
      //displaying the ID of the active tab
      currentTabIndex.Text=TabContainerContent.ActiveTab.ID;
    }

  }
}

One thing that I have to clarify is why I chose to use to store the list of TabIDs instead of a list of TabPanels....

I tried that when I first started to answer this question, but I ran into a bunch of problems. The errors I was getting were complaining about scripts being rendered before the Render event, or it was complaining about controls being modified in when they weren't allowed to.

The reason must have something to do with saving tabs into Session. The only way I was able to get around this was to store a list of tab ID's instead of a list of TabPanels in session. In other words, I could not use List<AjaxControlTollkit.TabPanel> dynamicPanels...I had to use private List<string> dynamicTabIDs;



 类似资料:

相关阅读

相关文章

相关问答