I'm running VS2005 and have tried both the beta and CTP nightly build (20060801).
I'm trying to bind an object of type TList<A> to an Infragistics UltraGrid control. I haven't tried using the stock grid control to see if it behaves the same way but plan to as soon as I understand some of the other problems I'm having with adding and deleting.
Here's the issue at hand. Object of type "A" has a BCollection property of type TList<B>. What I want to try to accomplish is to get the grid to render this hierarchy using object data binding. The "A" properties for each item in TList<A> display ok, but when I expand a row to try and view TList<B> the control throws an exception.
After a bit of digging, I found that the ListBase class' implementation of GetItemProperties tries to return properties of TList<B>. This didn't sound right to me since what I really want to show in the grid was B's properties, not TList<B>. I found Frans Bouma's implementation of ITypedList and used his TypeContainedAttribute class to get to B's property descriptors.
Has anyone else run into this? Is this really a bug or would this fix break the way other stock controls perform complex data binding?
Here's the what I did to get it working.
Add the TypeContainedAttribute class definition to EntityHelper.cs:
/// <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
}
Add the type contained attribute to A's BCollection property:
/// <summary>
/// Holds a collection of B objects
/// which are related to this object through the relation FK_B_A
/// </summary>
[BindableAttribute()]
[TypeContained(typeof(B))]
public TList<B> BCollection
{
get { return entityData.BCollection; }
set { entityData.BCollection = value; }
}
Then made the following change to GetItemProperties in ListBase.cs:
if (listAccessors == null || listAccessors.Length == 0)
{
return _propertyCollection;
}
else // Expect only one argument representing a child collection
{
if (_childCollectionProperties == null)
_childCollectionProperties = new Dictionary<string, PropertyDescriptorCollection>();
// 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);
}
string typeName = typeAttribute.TypeContainedInCollection.ToString();
if (_childCollectionProperties.ContainsKey(typeName))
{
return _childCollectionProperties[typeName];
}
else
{
PropertyDescriptorCollection props = EntityHelper.GetBindableProperties(typeAttribute.TypeContainedInCollection);
_childCollectionProperties.Add(typeName, props);
return props;
}
}