I'm running into some problems with a custom control I'm workign on that includes a collection property. The collection contains child controls (well DataBindingItems) and I have all of the collection loading and display logic working just fine.

 

However, I'm running into a problem where the Collection items need a reference back to the Page object and the parent control and I can't figure a way to reliably do this.

 

Here's the collection property defined inside of the control:

 

///// <summary>

///// Collection of all the preserved properties that are to

///// be preserved/restored. Collection hold, ControlId, Property

///// </summary>

[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]

[PersistenceMode(PersistenceMode.InnerProperty)]

public List<wwDataBindingItem> DataBindingItems

{

    get

    {

        return _DataBindingItems;

    }

}

List<wwDataBindingItem> _DataBindingItems = new List<wwDataBindingItem>();

 

 

Here's what the control looks like on a form:

 

<ww:wwDataBinder runat='server' ID="Binder">

<DataBindingItems>

    <ww:wwDataBindingItem runat="server" BindingSource="Customer.DataRow" BindingSourceMember="CompanyNamex"

        ControlId="txtName" DisplayFormat="{0}">

    </ww:wwDataBindingItem>

<ww:wwDataBindingItem runat="server" BindingSource="Customer.DataRow"

BindingSourceMember="Address"

        ControlId="txtTime">

    </ww:wwDataBindingItem>

</DataBindingItems>

</ww:wwDataBinder>

 

DataBindingItems is the collection that holds the items. wwDataBindingItem is a Control object that holds basically nothing more than properties and has a couple of methods that perform the individual field binding. It works fine, except when default values are required for the top level container.

 

The wwDataBindingItems controls don't have this.Page set. Nor do they have this.Parent set, so it's really tough to get a reference back to the wwDataBinder object as well.

 

This should be trivial to do right – just hook the OnAddedControl() method and assign the required values to the object there (basically pass the Binder parent, which has all the info needed). But… I can't find anyplace to hook this in, in the control architecture.

 

It looks like ASP.NET is loading the Collection content before any other even fires. This means before OnInit() – by the time Init fires the Collection is already filled. AddedControl and AddParsedSubObject don't fire at all, which means even though this collection looks like a set of child controls it's really treated like a raw object – not items that get added to the control like child controls.

 

So unless I'm missing some event that is hookable here, I don't think this can be done at the control level.

 

There are a few other odd things about the collection attached controls. The DesignMode property doesn't work on the controls either. So I have to explicitly add:

 

        /// <summary>

        /// Explicitly set designmode flag - stock doesn't work on Collection child items

        /// </summary>

        protected new bool DesignMode = (HttpContext.Current == null);

 

 

My next step I guess is to use a custom collection object and pass a reference to the parent control to the collection. Then create a custom Add() method and assign the parent object to the child as a reference.

 

But man is that ugly.

 

I also figured this would be easy as pie to do something like this with a generic collection:

 

public class wwDataBindingItemCollection : List<wwDataBindingItem>

{

    wwDataBinder _ParentDataBinder = null;

 

    public wwDataBindingItemCollection()

    {

    }

    public wwDataBindingItemCollection(wwDataBinder Parent)

    {

        this._ParentDataBinder = Parent;

    }

 

    public override void Add(wwDataBindingItem Item)

    {

        if (_ParentDataBinder != null)

        {

            Item.Page = _ParentDataBinder.Page;

            Item.Binder = _ParentDataBinder;

        }

 

        base.Add(Item);

    }

   

}

 

Be nice if that worked. It doesn't – the various generic methods can't be overridden. In fact you can't subclass any methods on a generic collection class other than object's base methods.

 

So the only alternative is to go ahead and manually create a collection class.

 

public class wwDataBindingItemCollection : CollectionBase

{

    /// <summary>

    /// Internal reference to the wwDataBinder object

    /// that is passed to the individual items if available

    /// </summary>

    wwDataBinder _ParentDataBinder = null;

   

    /// <summary>

    /// Preferred Constructor - Add a reference to the wwDataBinder object here

    /// so a reference can be passed to the children.

    /// </summary>

    /// <param name="Parent"></param>

    public wwDataBindingItemCollection(wwDataBinder Parent)

    {

        this._ParentDataBinder = Parent;

    }

 

    /// <summary>

    /// Not the preferred constructor - If possible pass a reference to the

    /// Binder object in the overloaded version.

    /// </summary>

    public wwDataBindingItemCollection()

    {

    }

 

    /// <summary>

    /// Public indexer for the Items

    /// </summary>

    /// <param name="index"></param>

    /// <returns></returns>

    public wwDataBindingItem this[int index]

    {

        get

        {

            return this.InnerList[index] as wwDataBindingItem;

        }

        set

        {

            this.InnerList[index] = value;

        }

    }

 

    /// <summary>

    /// Add a wwDataBindingItem to the collection

    /// </summary>

    /// <param name="Item"></param>

    public void Add(wwDataBindingItem Item)

    {

        if (_ParentDataBinder != null)

        {

            Item.Page = _ParentDataBinder.Page;

            Item.Binder = _ParentDataBinder;

 

            if (this._ParentDataBinder.DesignMode)

            {

                UpdateListInDesignMode();

 

            }

        }

 

        InnerList.Add(Item);

    }

 

    /// <summary>

    /// Add a wwDataBindingItem to the collection

    /// </summary>

    /// <param name="index"></param>

    /// <param name="Item"></param>

    public void AddAt(int index,wwDataBindingItem Item)

    {

        if (_ParentDataBinder != null)

        {

            Item.Page = _ParentDataBinder.Page;

            Item.Binder = _ParentDataBinder;

 

 

            if (this._ParentDataBinder.DesignMode)

            {

                UpdateListInDesignMode();

            }

        }

 

        InnerList.Insert(index,Item);

}

 

… a few other utility methods dealing with design mode

}

 

This works – now I can explicitly take over the Add process and assign both the Page object and the Binder to the child object.

 

This seems a lot of work for a simple thing, but it works. I was looking through another example later today and I noticed the same approach used by another developer doing something very similar than I am – creating Extender Properties in design mode for ASP.NET controls – contrary to what I thought this does work.

 

More on that in a few days.