Learn how to a build a cloud-first strategyRegister Now

x
?
Solved

ViewState does not persist for childcontrols of compositecontrol

Posted on 2007-08-08
10
Medium Priority
?
1,525 Views
Last Modified: 2013-11-05
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
0
Comment
Question by:Smallfish2004
  • 5
  • 3
9 Comments
 
LVL 18

Expert Comment

by:DropZone
ID: 19679211
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.
0
 
LVL 4

Author Comment

by:Smallfish2004
ID: 19687463
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.
0
 
LVL 18

Expert Comment

by:DropZone
ID: 19690731
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.
0
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 
LVL 18

Expert Comment

by:DropZone
ID: 19690792
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.
0
 
LVL 4

Author Comment

by:Smallfish2004
ID: 19752112
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 ?
0
 
LVL 18

Expert Comment

by:DropZone
ID: 19753173
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.
0
 
LVL 4

Author Comment

by:Smallfish2004
ID: 19797557
It is initialize by the ASP.NET parser, and yes, it is always get there initialized but without viewstate
0
 
LVL 18

Accepted Solution

by:
DropZone earned 2000 total points
ID: 19798877
I am so sorry for misunderstanding your problem from the beginning.  As you rightly mentioned, the ParseChildren attribute will parse the controls from the template but will not add them into the controls collection.  For that you need to override the AddParsedSubObject() method and do it there:

protected override void AddParsedSubObject(object obj)
{
    // Process our parsed TabViews...
    if (obj is TabView)
        m_views.Add(obj);
}

The parser will create a list of instances of objects created from tags it recognizes, however, it won't do anything with them, calling instead AddParsedSubObject() to process the object.  The default implementation of AddParsedSubObject() is to add only literal controls to the Controls collection, so each implementor needs to override it to do what they want it to do.

It is possible that the base class from which you are extending already overrides this method to ignore any parsed objects.  This is common in order to avoid adding newlines and other whitespace that may be found on templates between the attribute's tags.

Once the controls are added properly into your m_views list, you should be able to add them to the Controls collection as you have in the CreateChildControls() method.

A few things to note:
- You still need the EnsureChildControls override.
- You have to make sure that all controls that you want persisted have EnableViewState=True (or that it is disabled globally for the page).

    -dZ.
0
 
LVL 1

Expert Comment

by:Computer101
ID: 20521781
Forced accept.

Computer101
EE Admin
0

Featured Post

Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Today is the age of broadband.  More and more people are going this route determined to experience the web and it’s multitude of services as quickly and painlessly as possible. Coupled with the move to broadband, people are experiencing the web via …
IntroductionWhile developing web applications, a single page might contain many regions and each region might contain many number of controls with the capability to perform  postback. Many times you might need to perform some action on an ASP.NET po…
Is your data getting by on basic protection measures? In today’s climate of debilitating malware and ransomware—like WannaCry—that may not be enough. You need to establish more than basics, like a recovery plan that protects both data and endpoints.…
Look below the covers at a subform control , and the form that is inside it. Explore properties and see how easy it is to aggregate, get statistics, and synchronize results for your data. A Microsoft Access subform is used to show relevant calcul…
Suggested Courses
Course of the Month20 days, 17 hours left to enroll

810 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question