Link to home
Start Free TrialLog in
Avatar of Smallfish2004
Smallfish2004Flag for Israel

asked on

ViewState does not persist for childcontrols of compositecontrol

Hi all,
I have created a compositecontrol to render YUI tabview. the composite control has a default "Views" property wich with the "ParseChildren(true,"Views")" populated correctly and in the CreateChildControls i add the views as a controls to the parent (TabStrip). all things are working fine (events are fired, controls are rendered in the right place, etc.) but 1 thing insist not to working: The viewstate of the childcontrols. When i place (for ex.) a DropDownList within the tab and loads its items on "!IsPostBack" (only the first load) it loads the items correctly but not saving them to the viewstate cuasing the items to "Disapear" after postback as well as the SelectedValue is "Zeroed" to the first item.

I was try searching the net but without any results.

What can i do to solve this ?

My Control declaration (i was tring to inherit either the CompositeControl and WebControl, nothing worked):
    /// <summary>
    /// This is a tabstrip control that shows its contents (TabView elements)
    /// in tabs. It has the ability to span the contents to a table
    /// with 2 columns along the screen
    /// </summary>
    /// <remarks>
    ///     <who>Yair Cohen</who>
    ///     <why>limited-size displays can display more information on the same page,
    ///         and, large-scale displays can show the tabs alltogether</why>
    ///     <when created="30/07/2006" updated="1/08/06"/>
    ///     <where>ASP.NET 2</where>
    /// </remarks>
    [ToolboxData("<{0}:TabStrip runat=server></{0}:TabStrip>")]
    [DisplayName("TabStrip")]
    [ParseChildren(true, "Views")]
    [DefaultProperty("Views")]
    [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]

    public class TabStrip : WebControl, INamingContainer
{
...
       /// <summary>
        /// Get access to the child views
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
        public List<TabView> Views
        {
            get
            {
                if (m_views == null)
                {
                    m_views = new List<TabView>();
                }
                return m_views;
            }

        }
...
        protected override void CreateChildControls()
        {
            base.CreateChildControls();

            foreach (TabView view in m_views)
            {
                Controls.Add(view);
            }

        }
...
}

The tabview is a templated control (i tried inheriting panel, it didn't work):
    [ParseChildren(false)]
    [PersistChildren(true)]
    [DefaultProperty("ContentTemplate")]
    public class TabView : WebControl, INamingContainer
    {
...
        private ITemplate m_contentTemplate;
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
        public ITemplate ContentTemplate
        {
            get
            {
                return m_contentTemplate;
            }
            set
            {
                m_contentTemplate = value;
            }
        }
...
}

An interesting issue was that i override the LoadViewState for the composit e(TabStrip) control and it was never called.

Any Suggestions ?

Thanks a lot
Yair
Avatar of DropZone
DropZone
Flag of United States of America image

This is a common issue caused by the recursive way in which the control tree is recreated and restored.

The problem is that your control creates its children dynamically and when a PostBack occurs, the Controls collection of your control may not have created its children, because CreateChildControls() is not called automatically.  To solve this, you override the Controls property get method and call EnsureChildControls() which will in turn call CreateChildControls() and set a flag that the children were created.  This may seem wasteful, specially if then you need to recreate your controls on DataBind or some other event that occurs later on in the page's life cycle, but it is the only way to ensure that on every PostBack your dynamic controls receive their state and events properly.

public override ControlCollection Controls
   {
      get
         {
            this.EnsureChildControls();

            return base.Controls;
          }
   }

        -dZ.
Avatar of Smallfish2004

ASKER

Sorry but it does not working for me ...

My Code is:
Composite control:
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.ComponentModel;

namespace CompositeControlTest
{
    [ParseChildren(true, "Views")]
    [DefaultProperty("Views")]
   
    public class TabStrip : CompositeControl
    {
        private List<TabView> m_views;

        public List<TabView> Views
        {
            get { return m_views; }
            set { m_views = value; }
        }

        public override ControlCollection Controls
        {
            get
            {
                base.EnsureChildControls();

                return base.Controls;
            }
        }

        protected override void CreateChildControls()
        {
            base.CreateChildControls();

            foreach (TabView view in m_views)
            {
                Controls.Add(view);
            }
        }


    }
}
//-----------------------------------------------------------------------------------
// Child controls
//-----------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI.WebControls;

namespace CompositeControlTest
{
    public class TabView : Panel
    {
        public string Name
        {
            get
            {
                return ViewState["Name"] as string ?? string.Empty;
            }
            set
            {
                ViewState["Name"] = value;
            }
        }
    }
}


I would very appreciate your help.
Thank you very much.
Remember, for the control tree to be re-created, all controls need to be created and added dynamically when the EnsureChildControls() method is called.

I see that your CreateChildControls method adds all the m_views items to the Controls collection, but nowhere do I see who populates the m_views list.  If this is something you create by DataBinding()?

Then the control needs to be a bit more complicated and follow the mechanism used by the Repeater or other complex controls in the Framework.  The Repeater creates its items *ONLY* on DataBind, however, they need to exist also when the control is recreated on postback, so that ViewState is applied to it.  The way that it accomplishes this is by having 2 ways of creating the children:  when it is databinding and when it is not.  Instead of creating the children in CreateChildControls it uses a delegate method:  CreateControlHierarchy().  This method receives two parameters:

protected void CreateControlHierarchy(bool databinding, object data)

The first is to say if it is databinding (needs to create children completely, or just restore them), and the second one is the data to bind.

You then call CreateControlHierarchy from OnDataBinding -- instead of the base DataBind method -- with databinding = true:

protected override void OnDataBinding(eventargs e) {
    base.OnDataBinding(e);
    CreateControlHierarchy(true, this.DataSource);
}

This ensures that CreateControlHierarchy will DataBind the children as normal.  Then the other mechanism is to call CreateControlHierarchy from CreateChildControls() with databinding = false.  This is the path that will be taken when the control is being recreated on postback and not databinding (because you made sure that OnDataBinding does not call CreateControlsHierarchy):

protected override void CreateChildControls() {
    childcount = ViewState["_childcount"];
    if (childcount != null && int(childcount) > 0)
        CreateControlHierarchy(false, null);
}


Then the final step is the CreateControlHierarchy() method.  It needs to do 2 things:
    1. when databinding, create children and bind them
    2. when not databinding, create dummy children just to receive viewstate.

protected void CreateControlHierarchy(bool databinding, object data) {
    if (databiding) {
        if (data != null) {
            //
            // do what you need to do to create the children
            // and add them to the Controls collection
            //
 
           // bind the children
           foreach (Control ctrl in this.Controls) {
                ctrl.DataBind();
            }
        }
    } else {
         // create dummy items.
         for (int i = 0; i < (ViewState["_childcount"]); i++) {
             Control ctrl = new TabView; // just a new child
             this.Controls.Add(ctrl);  // adding it to the tree will restore its ViewState
         }
    }
}

I did this from memory, so I may have missed a detail or two.  I suggest you view the source of the Repeater control using Reflector and study how it does this.  Also, the MSDN .NET SDK documentation has a very good chapter on this.  I believe its called Creating Custom DataBound Controls.  It may seem complicated and unnecessary, but it seems to be the best way to ensure that the control tree is recreated on postback.  Creating databound server controls is a bitch.

I hope that my explanation is understandable.  Here are a couple of resources to guide you:

http://msdn2.microsoft.com/en-us/library/aa479322.aspx
http://msdn2.microsoft.com/en-us/library/ms366539.aspx

          -dZ.
I forgot to include that at the end of the databinding path of CreateControlsHierarchy, you need to set the ViewState["_childcount"] with the amount of children, so that they will be recreated on postback.

    -dZ.
Thanks, it would be great for databound control, but my control is not databound. The Views (m_views) are populated by the ASP.NET Parser because of these lines:
"
    [ParseChildren(true, "Views")]
    [DefaultProperty("Views")]
"
and it works fine, the m_views collection get all the views written as a child in the ASP.NET page markup. But, the ASP.NET does not add them to the Controls collection and i need to add them manually and thus i added them in the CreateChildControls.

any suggestions ?
Smallfish,
     Where are the m_views created when you use them in CreateChildControls?

        protected override void CreateChildControls()
        {
            base.CreateChildControls();

            foreach (TabView view in m_views)
            {
                Controls.Add(view);
            }
        }

Recall that on PostBack, they need to be created in order for them get their ViewState applied.  I suggest the following:  Put a breakpoint in the above code, before the foreach() loop and inspect the values of m_views to see if they have been initialized.

     -dZ.
It is initialize by the ASP.NET parser, and yes, it is always get there initialized but without viewstate
ASKER CERTIFIED SOLUTION
Avatar of DropZone
DropZone
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Forced accept.

Computer101
EE Admin