Really complex databinding: ITypedList with weakly typed collections

Preface
When you, as a developer, have written a class library which has to be bound to complex user controls like a datagrid, and you want control over the databinding process, you are confronted with one of the most complex interfaces to implement: ITypedList. I'm not sure if the reason why complex databinding is called complex is because of the complexity of the interface which makes this all possible, ITypedList, but I wouldn't be surprised.

Complex databinding is the kind of databinding where a set (for example an IList or collection) of objects is bound to a multi-element control, like a datagrid control. The datagrid control for example has to display columns, name them, display the correct data in each of these columns, so in other words it has to do a variety of things and it's essential that the datagrid knows what kind of objects are stored in the collection that's bound. Everybody who has bound an ArrayList of objects to a datagrid control will know that this won't be a problem, you'll see a nice set of columns and each column reflects a property of the object at a given index in the ArrayList. However examining the ArrayList documentation shows that it doesn't implement ITypedList. So why is this interface so important when complex databinding databinding seems to work without ITypedList?

The reason for that is that ITypedList lets you control which columns are visible, with which description and how they should be treated, for example, should they be read only, even if there is a set clause in the property definition? An ArrayList with objects will show up in a datagrid with all columns editable (except if the related property has just a get clause) and every public property of the objects inside the ArrayList will have a column in the datagrid (unless the attribute BrowsableAttribute(false) is applied (winforms only)). In situations where data in inner structures have to be exposed as properties (e.g. the column objects in a DataView) or some properties have to be read only based on the state of the object, the default behaviour of the complex binding functionality of a collection is not sufficient: you have to implement ITypedList to tell the bound control exactly how the objects inside the bound collection have to show up in the control and which properties should show up and how they should be treated. Another issue with simple collections bound to a datagrid is the absence of any columns in the datagrid if the collection bound is empty (e.g. when datagrid columns are autogenerated from the bound datasource). This is understandable, as the datagrid doesn't see any objects in the bound collection and thus can't determine which properties are exposed by the objects in the collection, which results in an empty datagrid and no columns.

You can solve this by implementing ITypedList and simply supply the bound control with the data it otherwise would have retrieved from the actual objects in the bound collection. This becomes fairly complex when we throw in navigation through the object graph bound to the datagrid via the collection. Imagine you bind an ArrayList of Customer objects to the datagrid and each Customer contains an 'Orders' collection and each Order in that collection contains an OrderRows collection. How are these properties going to show up in the datagrid, if the datagrid is bound to an ArrayList with Customer objects? ITypedList will be able to supply the datagrid with this information.

ITypedList inner workings in general
ITypedList defines two methods: GetListName and GetItemProperties. GetListName is not that interesting and not required for what we want to do so we concentrate on GetItemProperties. Complex databinding works with property descriptors. A property descriptor is an object, often derived from the abstract class System.ComponentModel.PropertyDescriptor, which exposes information about a property like its name, type, if it is read only and also methods to get and set the property's value. A datagrid control tries to grab a list of property descriptors for the object type in the bound collection. Most datagrid controls can handle one type of object per hierarchy level or 'band', and the vanilla .NET grids (webforms/winforms) are no exception on this, so it will try this once per bound collection on a hierarchy level. With that list of property descriptors, one per publicly exposed property, it can then build the columns and read the data of each property and also set the data if the user changes the value of a cell.

ITypedList inner workings: single level hierarchy
If ITypedList is implemented on the collection bound to the datagrid, the datagrid will simply call ITypedList.GetItemProperties and will use the PropertyDescriptorCollection object returned. If ITypedList is not implemented (and with an ArrayList object, that's the case), the datagrid will call an overload of the static method System.ComponentModel.TypeDescriptor.GetProperties() to retrieve the property descriptors for the type of the object in the bound collection. With an empty ArrayList, there is no type information of the objects in the collection, because they're not there, nor does ArrayList implement ITypedList, which results in the situation where the datagrid doesn't have any property information whatsoever so it will simply display no columns. Some datagrid controls will allow you to pre-define columns and these will also be visible even if the data bound to the datagrid is completely empty, like an empty ArrayList, however in this situation, we don't have predefined columns or our datagrid control doesn't support showing pre-defined columns even if you bind an empty ArrayList.

ITypedList inner workings: multi level hierarchy
In the example mentioned earlier with an ArrayList of Customer objects and with each Custom object containing an ArrayList of Order objects, you have a hierarchy of objects which you can navigate through in a datagrid control (most datagrid controls allow you to do that, either by displaying 'bands' or 'levels' or another sort of 'level' description). When you, as a user see the Customer objects in the grid and you navigate to the Orders collection of a particular Customer object, the grid has to retrieve the property descriptors of the objects in the Orders collection. However it doesn't know of this collection, its DataSource is bound to an ArrayList of Customer objects. If the bound collection, in this case thus an ArrayList of Customer objects, doesn't implement ITypedList, the datagrid will try to access an instance of the Orders collection it has to view and will try to retrieve the property descriptors from that instance. If the bound collection does implement ITypedList, it will ask that collection to provide the property descriptors for the collection to view, even if this is another collection.

It does this by passing the property descriptors of the properties the user has navigated to the GetItemProperties call in the listAccessors array. Say, we implement ITypedList on a subclass of ArrayList and bind that subclass to the datagrid control. The user navigates via a given Customer object to its Orders collection. At that moment, our implementation of ITypedList which contains the Customer objects gets the call to supply property descriptors for the Orders collection of a given Customer object. There is however no instance information passed in, just property descriptors. A property descriptor object does have a method GetValue(), however you can't use that method without a reference to the object holding the property described by the particular property descriptor. We run into a problem: how do we know the properties of the object stored in the collection returned by the property described by the property descriptor passed in the listAccessors list? When we examine the type of that property, it's an ArrayList, that won't help much. We need some extra information. We'll see later on how that's supplied.

We have to solve the mistery of the listAccessors array before we can move on. What does it contain? As said, it contains all properties the user has used to navigate to the property which exposes the collection of objects we have to provide the property descriptors for. So this means that we can ignore the complete array except the last entry! The last entry in the array will be the property descriptor we're looking for. This is essential information as it will make our code much simpler.

The example
As an example I'll use a class called Customer which holds the data for a single customer such as contact name and company name. It will furthermore expose a collection object which holds Order objects and which is exposed as the property Orders. It will also contain a Hashtable with runtime information for the business logic. As mentioned briefly above, when you apply the .NET BrowsableAttribute attribute (located in the System.ComponentModel namespace) with a value of 'false' to a property, most controls which support complex binding will skip that property as a property to bind to. I say 'most', because ASP.NET datagrid controls and seem to ignore the BrowsableAttribute completely: a webforms datagrid will not skip a property which has a BrowsableAttribute(false) applied to it. This makes the BrowsableAttribute not usable if we also want to bind a collection of our classes to a webforms datagrid, we need something stronger, as otherwise the Hashtable will show up in the datagrid, and that's not what we want. Because we don't own the webforms datagrid source code, we have to use a generic mechanism to tell the control which properties to bind to. ITypedList is the interface to make that magic happen, together with a custom attribute class. As ITypedList is an interface we have to implement ourselves, we need a collection class to implement ITypedList on, the collection class which will be used to store our Customer objects in and also to store our Order objects in, which is present in each Customer object. The easiest way to do that is to create a subclass of System.Collections.ArrayList, as we're going to use a generic collection class, which are weakly typed. Weakly typed means: there is a single class definition for all types, not a specific, strongly typed collection which solely allows a single type to be added to the collection. To make it more real, the Order object contains an OrderRows collection of OrderRow objects.

The classes
Our grocery store list looks like this:

  • Customer, Order and OrderRow classes
  • Our collection class derived from ArrayList
  • Own attribute class for hiding a property for databinding
  • Own attribute class for specifying the type contained in the collection

That last one is not described yet. As mentioned above, in a hierarchy bound to a grid, the bound collection has to supply the property descriptors for all collections in the hierarchy without instance information or references. We'll use a custom attribute to specify the type inside a generic collection exposed by a property, like the Orders property of the Customer class. With that attribute, we can supply the property descriptors for the type specified as value in the attribute, and solve the problem of not having enough information to supply the correct property descriptors.

Our example will illustrate the following elements:

  • Custom attributes
  • ITypedList implementation on a weakly typed collection
  • How to hide properties for databinding if they're marked with a given attribute

I've kept the example simple however it does illustrate all information required to successfully implement ITypedList. More advanced topics could deal with own PropertyDescriptor derived classes which override GetValue() and SetValue() (e.g. to set a given object somewhere in a collection instead of a property), or decentralized property descriptor manufacturing, for example by defining an interface every object has to implement and which is called by the GetItemProperties() method to provide the property descriptors for the class implementing that interface.

Enough ramblings for now, let's get to the real deal and discuss some code! I'll show the C# code here, VB.NET users have to convert this to VB.NET using one of the online converts like this one. The full example archive uses an example form, which you have to convert by hand to get the events properly wired, but it contains just two controls, so that's not a big problem.

TypeContainedAttribute class
The first class I'll discuss is the TypeContainedAttribute class. This attribute class will contain the type information for the type inside a collection exposed by a property. It is this attribute which will tell our GetItemProperties implementation which type is inside a collection deep in the hierarchy.

/// <summary>
/// Attribute to use on properties which return a weakly typed collection.
/// This attribute will tell the property descriptor construction code to construct a list of
/// properties of the type set as the value of the attribute.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class TypeContainedAttribute : Attribute
{
    #region Class Member Declarations
    private Type    _typeContainedInCollection;
    #endregion
    /// <summary>
    /// CTor
    /// </summary>
    /// <param name="typeContainedInCollection">The type of the objects contained in the collection
    /// returned by the property this attribute is applied to.</param>
    public TypeContainedAttribute(Type typeContainedInCollection)
    {
        _typeContainedInCollection = typeContainedInCollection;
    }

    #region Class Property Declarations
    /// <summary>
    /// Gets typeContainedInCollection, the type set in the constructor
    /// </summary>
    public Type TypeContainedInCollection
    {
        get
        {
            return _typeContainedInCollection;
        }
    }
    #endregion
}

The attribute class is pretty simple, it takes a Type object and stores that internally and exposes that Type object via a property.

HiddenForDataBindingAttribute class
Similar is our other attribute, the one we'll use to mark properties as hidden for databinding:

/// <summary>
/// Attribute to use on properties to make them hidden for complex databinding.
/// This attribute will tell the property descriptor construction code to skip this property
/// for databinding purposes, the property will then not show up in the bound control.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class HiddenForDataBindingAttribute : Attribute
{
    #region Class Member Declarations
    private bool    _isHidden;
    #endregion
    /// <summary>
    /// CTor
    /// </summary>
    public HiddenForDataBindingAttribute()
    {
        _isHidden = false;
    }

    /// <summary>
    /// CTor
    /// </summary>
    /// <param name="isHidden">Set to true if the property this attribute is applied
    /// to should not be used in a complex databinding scenario. Default: false</param>
    public HiddenForDataBindingAttribute(bool isHidden)
    {
        _isHidden = isHidden;
    }

    #region Class Property Declarations
    /// <summary>
    /// Gets isHidden
    /// </summary>
    public bool IsHidden
    {
        get
        {
            return _isHidden;
        }
    }
    #endregion
}

This attribute takes a boolean as value in its constructor and exposes that value via a property. The value, when omitted, is false, so the attribute will not have any effect. As with all custom attribute classes, these attributes will not have any effect unless there is code implemented which uses the attributes to perform certain logic, in our case, constructing property descriptors in GetItemProperties(). As a side note: attributes are applied to/in types, not to instances. This means that we can't control the attribute's value for a particular instance, we can only apply them to types, or parts of types like a property or public field.

Customer class
One of our three classes (Customer, Order, OrderRow) is listed below. It illustrates the usage of our two custom attributes and also shows the usage of our own collection class which is described later on in more detail. The code's formatting is compressed a bit to save space (hence the 'K & R formatting')

/// <summary>
/// Simple Customer class. For illustration purposes only.
/// </summary>
public class Customer
{
    #region Class Member Declarations
    private int    _customerID;
    private string    _contactName, _companyName;
    private Hashtable    _runtimeAttributes;
    private ComplexDatabindingArrayList    _orders;
    #endregion

    /// <summary>
    /// CTor
    /// </summary>
    public Customer() {
        _contactName = string.Empty;
        _companyName = string.Empty;
        _runtimeAttributes = new Hashtable();
        _orders = new ComplexDatabindingArrayList(typeof(Order));
    }

    #region Class Property Declarations
    /// <summary>
    /// Gets / sets CustomerID, this class' primary key field and uniquely
    /// identifying this customer.
    /// </summary>
    public int CustomerID {
        get { return _customerID; }
        set { _customerID = value;}
    }

    /// <summary>
    /// Gets / sets ContactName
    /// </summary>
    public string ContactName {
        get { return _contactName; }
        set { _contactName = value; }
    }

    /// <summary>
    /// Gets / sets CompanyName
    /// </summary>
    public string CompanyName {
        get { return _companyName; }
        set { _companyName = value; }
    }

    /// <summary>
    /// Gets the RuntimeAttributes collection, a set of name-value pairs used for business logic.
    /// </summary>
    [HiddenForDataBinding(true)]
    public Hashtable RuntimeAttributes {
        get { return _runtimeAttributes; }
    }

    /// <summary>
    /// Gets the orders collection of this customer object
    /// </summary>
    [TypeContained(typeof(Order))]
    public ComplexDatabindingArrayList Orders {
        get { return _orders; }
    }
    #endregion
}

Two properties are of importance here: RuntimeAttributes and Orders. RuntimeAttributes, because we don't want that property to show up in a datagrid and Orders because we have applied our custom attribute TypeContainedAttribute() to it to illustrate that it exposes a collection with Order objects. Remember, we can't get a reference to the physical collection object exposed by this property at runtime in our GetItemProperties() method.

ComplexDatabindingArrayList class
It's now time to introduce our core class for this example, the ComplexDatabindingArrayList class. This class is a subclass of ArrayList and implements ITypedList. It delegates any other logic to the ArrayList class so it's pretty compact as well. Again, the formatting is compressed a bit to save space.

/// <summary>
/// ComplexDatabindingArrayList implementation. This class derives from ArrayList and
/// implements ITypedList and some other logic to make it possible to bind an instance
/// of this class to a grid or other complex databinding aware control, while keeping
/// the weakly typed nature of the ArrayList, and be able to have advanced ITypedList
/// provided databinding functionality, e.g.: this class makes it possible to autogenerate
/// columns in a grid even though the collection is empty.
/// </summary>
/// <remarks>It stores the type of the objects it contains. This is because attributes are
/// stored with the type, not the instance. So the initial collection of objects has to
/// have a type definition internally.</remarks>
public class ComplexDatabindingArrayList : ArrayList, ITypedList {
    #region Class Member Declarations
    private Type    _containedType;
    #endregion

    /// <summary>
    /// CTor
    /// </summary>
    /// <param name="containedType">The type of the objects to be stored in this instance</param>
    public ComplexDatabindingArrayList(Type containedType) {
        _containedType = containedType;
    }

    /// <summary>
    /// CTor
    /// </summary>
    /// <param name="c">The ICollection whose elements are copied to the new list.</param>
    /// <param name="containedType">The type of the objects to be stored in this instance</param>
    public ComplexDatabindingArrayList(ICollection c, Type containedType):base(c) {
        _containedType = containedType;
    }

    /// <summary>
    /// CTor
    /// </summary>
    /// <param name="capacity">The number of elements that the new list is initially capable
    /// of storing.</param>
    /// <param name="containedType">The type of the objects to be stored in this instance</param>
    public ComplexDatabindingArrayList(int capacity, Type containedType):base(capacity) {
        _containedType = containedType;
    }

    /// <summary>
    /// ITypedList.GetItemProperties implementation. This implementation will simply return
    /// all property descriptors retrieved for the object stored (or specified to be stored)
    /// in this instance, except properties which have the HiddenForDataBindingAttribute attribute
    /// applied to them with a value of true.
    /// </summary>
    /// <param name="listAccessors">list of accessors which contains the complete navigation
    /// path in a hierarchical bound object graph</param>
    /// <returns>PropertyDescriptorCollection to be used by the calling control</returns>
    /// <remarks>It will provide an array of property descriptors
    /// for the type contained in this instance of the ArrayList. If this type is not determinable,
    /// it will return an empty collection.</remarks>
    public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) {
        Type typeOfObject = null;

        // determine the type of the object to return the properties of. We first have to check
        // whether the properties requested are for an object in this instance (listAccessors==null or empty), or
        // that we're called to supply the property descriptors for an instance deeper in the object graph we're the
        // root of.
        if((listAccessors==null)||listAccessors.Length==0)
        {
            // provide the property descriptors for objects of this instance.
            typeOfObject = _containedType;
        }
        else
        {
            // use the last entry in the listAccessors, grab its TypeContainedAttribute and instantiate an instance of
            // the type in that attribute, use that entity instance to produce properties.
            TypeContainedAttribute typeAttribute =
                (TypeContainedAttribute)listAccessors[listAccessors.Length-1].Attributes[typeof(TypeContainedAttribute)];
       
            if(typeAttribute==null)
            {
                // not found, not specified, can't determine properties.
                return new PropertyDescriptorCollection(null);
            }
            typeOfObject = typeAttribute.TypeContainedInCollection;
        }
       
        // create property descriptors.
        return GetPropertyDescriptors(typeOfObject);
    }

    /// <summary>
    /// ITypedList.GetListName implementation. Not used in this example, so it returns always the
    /// same string.
    /// </summary>
    /// <param name="listAccessors"></param>
    /// <returns>Always the same string.</returns>
    public string GetListName(PropertyDescriptor[] listAccessors) {
        return "A Complex Databinding aware list";
    }

    /// <summary>
    /// Creates the actual property descriptor collection for the type passed in.
    /// </summary>
    /// <param name="typeOfObject">the type of the object to get the property descriptors for</param>
    /// <returns>filled property descriptor collection</returns>
    /// <remarks>It will skip every property which has the HiddenForDataBindingAttribute attribute
    /// applied to it with a value of true.</remarks>
    private PropertyDescriptorCollection GetPropertyDescriptors(Type typeOfObject) {
        PropertyDescriptorCollection typePropertiesCollection = TypeDescriptor.GetProperties(typeOfObject);
        ArrayList propertyDescriptorsToUse = new ArrayList();
        // now walk all properties in the property descriptor collection. If the property has the
        // HiddenForDataBindingAttribute applied to it, it should be skipped.
        foreach(PropertyDescriptor property in typePropertiesCollection)
        {
            HiddenForDataBindingAttribute hiddenAttribute =
                (HiddenForDataBindingAttribute)property.Attributes[typeof(HiddenForDataBindingAttribute)];
            if(hiddenAttribute!=null)
            {
                // check if the value is false.
                if(hiddenAttribute.IsHidden)
                {
                    // skip
                    continue;
                }
            }
            // add it, as it doesn't have the attribute applied to it OR the attribute's value is false
            propertyDescriptorsToUse.Add(property);
        }
        return new PropertyDescriptorCollection((PropertyDescriptor[])propertyDescriptorsToUse.ToArray(typeof(PropertyDescriptor)));
    }

    #region Class Property Declarations
    /// <summary>
    /// Gets / sets containedType
    /// </summary>
    public Type ContainedType {
        get { return _containedType; }
        set { _containedType = value; }
    }
    #endregion
}

The first thing to notice is the _containedType member. As attributes can't be applied to instances, we can't tell a particular instance of this class which type is in that collection. So our initial collection, with customers, which type does that collection contain? This question can be answered with different solutions, one is to store the type contained in the collection as well. The advantage of this is that it also works if the collection is empty, which is what we want.

The second thing to notice is of course the GetItemProperties routine. The first thing it does is to determine if it has to provide the properties of the objects in a collection deep in the hierarchy or for objects it contains itself. Be sure to check for null and for an empty array, as some controls supply null and others supply an empty array when the properties of the objects inside itself have to be provided. When there is no hierarchy navigated, we can simply use the type specified as containedType for this instance of the ComplexDatabindingArrayList. When this is not the case and listAccessors is not empty or null, we have to use our TypeContainedAttribute attribute to help us determine which type is contained by the collection exposed by the property. If the attribute is not applied, the routine simply gives up, as it doesn't know what types are in the exposed collection. This is a weakness in the mechanism, because we can't fall back on 'do it the hard way, datagrid!', which is performed by the datagrid when you bind a vanilla ArrayList or other collection which doesn't implement ITypedList.

When the type is determined, the actual work can start, which is performed by a separate private routine, GetPropertyDescriptors. This routine first grabs the property descriptors for all the properties of the type, by using TypeDescriptor.GetProperties(type). Because we want to hide properties which have our own HiddenForDataBindingAttribute attribute applied to them, we have to filter these out. To do this, we walk the retriever property descriptors and check if the property has the attribute present and with a true value. If so, the property is skipped. The properties which are left are the ones interesting for databinding and we return these.

To use our classes, we create simple form with a vanilla .NET datagrid control on it and bind to it a ComplexDatabindingArrayList instance with a single customer. This illustrates that properties are hidden and also that navigating to the Orders collection will show the columns but no data. With a normal ArrayList no column would have appeared.

Please download the complete VS.NET 2003 example project (C#) by clicking here (.zip, 9KB) or a VB.NET port here. Thanks to Gary L. Winey for the port.

28 Comments

  • Excellent article Frans! And a great working example as well.

  • I will be needing some advanced databinding stuff in the near future. So I guess this is &quot;gefundenes Fressen&quot;. Now for reading and understanding it too. :-)



    Thanks Frans !

  • You are a f%$king legend!



    Thank you!

  • Thanks for the article Frans.



    Out of curiosity, when implementing this in a webform environment, how would you suggest keeping the datagrid contents in sync with the underlying datasource (in this case, keeping the customer objects contained within the ComplexDatabindingArrayList in sync with the datagrid for persisting back to the db)?

  • Mike: the grid uses the property descriptors to retrieve data (using the property descriptor method 'GetValue') and to set data (using the SetValue method). So this is build into the property descriptor class :)

  • Frans,



    Thanks for the response. I think I'm having a bit of trouble presenting my question in a manner that's easy to understand.



    My thinking (and yes, this is usually where the problem comes in...) is that the collection, in a web environment, will go out of scope after binding to the datagrid. Let say you are allowing batch updates to objects that reside in a datagrid (updating multiple rows and clicking save changes). On click, you're going to have to create a new customer object for each row in the grid, then create a new collection object and add each customer object to the collection. Now, I know this can't be correct, which means I'm missing something. Are you caching the original collection, or saving it in a session scope?



    I'm guessing the answer to this is so blindingly obvious that I'll kick myself for bothering you with the question. I appreciate the time.

  • Mike, in a webscenario, databinding is pretty much a 1-way street. So databinding is only really useful in the scenario where you're rendering the page. As you said, in a postback, everything is gone. The objects used when building the page can be stored in viewstate or session, but the binding is gone, no currency manager is keeping the control and the object together.



    So in a postback you can have teh objects still, from viewstate or session (requires serializable objects) however its then not bound to the control.

  • btw Frans you already noticed MS changed their guidelines for private member naming?



    _order := orders



    Weakly typed, I love it... saves a significant amount of &quot;useless&quot; typed collection classes all containing substantial duplicate code.

  • No I didn't know they've changed their guidelines, the underscore is not allowed anymore? Well, I keep it, it's very useful :)

  • Frans,



    I've seen the video of whidbey where you can bind everything to simple classes without doing the stuff you decribed.

    I guess the designer is generating this via reflection for you. I hope they didn't rewrite the whole 'binding' code (again).



    Great article, and a shame MS itself doesn't document this better. I've spent hours dissecting generated typed datasets...



    Marcel

  • Marcel, teh code I describe is not required to perform databinding, it is required to control the databinding process, which property is readonly etc.

  • Yeah, I know. But if you wan't to build your own framework it's better to control the process yourself instead of adding of lot of code to hack the default way.

  • You can't control it in another way, you HAVE TO implement this interface to control it. I therefore don't really understand your sentence: &quot;But if you wan't to build your own framework it's better to control the process yourself instead of adding of lot of code to hack the default way. &quot;

  • Frans,

    I meant: I totally agree with you. I'm building a application framework which uses databinding.

    With implementing ITypedList you can control the databinding.



    Trying the default way, with typed datasets (or untyped) or datasets (or tables), is not very useful. I want a layer between the binding and the database access code.

    Maybe it's difficult for me to explain in short. I could not found much info about the databinding process. I could found a lot about using it, but not about the process itself.



    I'm still wondering if in 'whidbey' things will be changed.



    Marcel

  • Frans,



    First I wanted to congratulate you on an excellent article and an excellent Data Access Layer.



    My questions are as follows:

    1. Isn't subclassing an ArrayList bulkier than a typed collection? I know this approach saves code, but so what? Most people (I hope) auto-generate their typed collections and the extra executable size of typed collections doesn't really make a difference considering computers have a gillion gigs of RAM nowadays.



    2. I agree that attributes are an extremely convenient way of specifying presentation information, but doesn't this violate the seperation of tiers?



    Thanks,

    Jafar

  • FOr illustration purposes I wanted to use a collection, so the ArrayList was the best approach. Any collection class would be ok



    Separation of tiers is achieved on a semantical and logical level not necessarily on a class-based level.

  • I've been using this method for a while and I've hit a wall: How do you change the DisplayName property of the PropertyDescriptor? This method isn't much good unless you can customize the names of the columns which appear in a bound grid. Should I subclass the Descriptor?

  • Where the descriptors are created, you can also create instances of your own subclass of a descriptor or set the DisplayName property based on a value from a table for example.

  • I subclassed the PropertyDescriptor and overrided the DisplayName property but the datagrid (WinForms) does not use it in the column heading. Using watches I double and triple checked to ensure that the subclass of the PropertyDescriptor's DisplayName was correct before being fed to the datagrid. Unfortunately this did not work and it just displayed the property name in the datagrid column heading. I can only conclude that for some idiotic reason it always uses the Name property instead of DisplayName. Is there any way around this?



    Using the excellent tool .NET Reflector I waded through the System.Data namespace IL code and determined that they used a subclass of PropertyDescriptor called DataColumnPropertyDescriptor. It doesn't override DisplayName though, instead deferring to the default implementation provided in its MemberDescriptor superclass. It just stores the member name passed to its constructor in a variable and spits it out when the DisplayName property is called. In short, I can't figure why the DataColumnPropertyDescriptor can display a different caption than the column name and I can't. Does my class need to implement IComponent perhaps?

  • I don't think IComponent has anything to do with it. However, take into account that a grid binds to a DataViewRow, not a DataRow. Perhaps the differences are located there.

  • Good observation. I noted that the DataRowView implements the ICustomTypeDescriptor interface. Turns out that this interface implements many of the functions implemented by the ITypedList interface including the all-important GetProperties method. &quot;Ah-hah&quot; I thought. So I implemented the interface and moved the code that generated the property descriptors from the collection to the object and ran the program. I got the exact same result. The column names were still using the Name property instead of the DisplayName property. Keep in mind that I confirmed using watches that the display name is correct before I send the collection of property descriptors to the grid. I also know that the grid is using my property descriptors because I was successfully able to alter the order of the columns. To make matters worse, in DataRowView this method cannot be decompiled because it is in native code.



    I don't mean to treat you like technical support but I can't think of anyone more qualified to help me explain how to bind a business object to a grid. :-) Is there an alternate way of accomplishing this that I am missing? I know that including the Serialization Attribute in a class allows the grid to display the properties of a plain vanilla class but there doesn't appear to be way of controlling the process more finely than defining which properties are displayed or not. Surely there must be _some_ way of avoiding the System.Data namespace altogether?

  • I also discovered that the HiddenForDataBinding attribute is not necessary. You can use the Browsable attribute in the System.ComponentModel namespace to ensure that a property does not show up in a grid.

  • Browsable(false) doesn't hide it in the webgrid...



    I don't know what causes the behaviour, but isn't it so that column captions are set using tablestyles? How would someone set a column caption with solely a datatable?

  • Any thoughts on whether it would be possible to return the ComplexDatabindingArrayList from a web service and get it to bind to a control.



    So far, I haven't had any luck yet getting it to work.

  • Like some other readers, I am trying to bind a strongly typed collection to a WinForm datagrid. I am having problems applying styles to the datagrid. When I say having problems, I mean it is not doing it at all.



    I am already using the hidden attribute to mask columns that I do not want to display, but when I try using code such as :



    DataGridTableStyle myDataGridTableStyle = new DataGridTableStyle();

    myDataGridTableStyle.GridColumnStyles.Clear();

    DataGridTextBoxColumn myDataGridTextBoxColumn = new DataGridTextBoxColumn();

    myDataGridTextBoxColumn.MappingName =&quot;Version&quot;;

    myDataGridTextBoxColumn.HeaderText = &quot;Steve&quot;;

    myDataGridTextBoxColumn.Width = 150;

    myDataGridTableStyle.GridColumnStyles.Add(myDataGridTextBoxColumn);

    this.dataGrid1.TableStyles.Add(myDataGridTableStyle);

    myDataSource = Tellermate.DataAccess.DAL.ApplicationVersionData.GetApplicationVersionList( connectionString );



    this.dataGrid1.DataSource = myDataSource;



    It does not change the header to &quot;Steve&quot; event though &quot;Version&quot; is one of the public properties exposed. Am I missing something really stupid here?

  • I looked for a decent article about ITypedList in MSDN, and i couldn't find one

    This is very well done..it helped me a lot

  • Excellent!



    I'm trying for something a tad more ambitious. I've included an ArrayList of properties (derived from PropertyDescriptor) which are defined at run-time and replace standard property declarations.



    I'm not sure how to identify which Customer object in the collection is being queried at any given call to GetItemProperties. I believe this is necessary to relay the appropriate Customer property values to the bound control...



    Anyone have a suggestion?

  • Well, I managed to find the solution myself - for anyone interested, the relay occurs in the PropertyDescriptor instead for GetValue/SetValue. They both include a component parameter which is exactly what I was missing...

Comments have been disabled for this content.