Complex Data Binding

Sample Image - ComplexDataBinding.jpg

Introduction

Data binding can be nasty - especially when you cant use the System.Data objects. I have tried to use all the features of databinding to show you. It is pretty small but takes a hell of a long way to get there..

The sample shows a custom collection with objects in it having properties, some of those properties are collections again with objects in them - hierarchical data. Maybe some of the properties cant be edited directly because the grid is not smart enough - like SqlTypes.

The Problem

We have a collection of objects that need to go into a windows datagrid. They may or may not have properties that are collections too.

The Solution

We need to create the follwoing objects:

  • A class implementing ITypedList and IBindingList (your collection)
  • A class that is your data (lots of properties and stuff)
  • A class implementing IComparer (for sorting)
  • A class implementing PropertyDescriptor (for using non grig compatable types, like SqlTypes )
  • A class implementing Attribute (so we know whats in the collection)

For those of you that have tryed this and it didnt work, a couple of issues came up that I will discuss.

ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)

This was the nastiest method interface in the project. The problem has to do with the listAccessors . If the parameter is not null, then the only one that should be processed is the last one. I had to decompile System.Data.DataView.ITypedList.GetItemProperties to find that out. Then I needed to detect our custom collections and return the properties for the underlying data.

SqlPropertyDescriptor : PropertyDescriptor

This one, although straight forward to implement, is a bit tricky in that when calling GetItemProperties and we want to use this PropertyDesciptor instead of the default, we need to replace it in the collection. I wrote this method to do it:

protected PropertyDescriptorCollection GetPropertyDescriptorCollection( ArrayList properties )
{
    if ( properties == null || properties.Count == 0 )
        return new PropertyDescriptorCollection(null);
    ArrayList output = new ArrayList();
    foreach ( PropertyDescriptor p in properties )
    {
        if ( p.Attributes.Matches(new Attribute[]{new BindableAttribute(false)}) ) continue;
        if ( p.PropertyType.Namespace == "System.Data.SqlTypes" )
        {
            // create the base type property descriptor
            output.Add(SqlPropertyDescriptor.GetProperty( p.Name, p.PropertyType ) );
        }
        else
        {
            output.Add(p);
        }
    }
    return new PropertyDescriptorCollection((PropertyDescriptor[])output.ToArray(typeof(PropertyDescriptor)));
}

After that, it was all straight forward.

The SetValue and GetValue of the PropertyDescriptor provide the means to edit SqlTypes like SqlDateTime . After detecting the namespace of the data type, I use SqlPropertDescriptor which implements GetValue() and SetValue() . These methods try to figure everything out for themselves:

public override void SetValue(object component,object value)
{
    try
    {
        PropertyInfo pi = component.GetType().GetProperty(this.Name);
        Object o;
        if ( value == DBNull.Value )
        {
            o = component.GetType().GetField("Null", BindingFlags.Static | BindingFlags.Public | BindingFlags.GetField).GetValue(component);
               
        }
        else
        {
            o = pi.PropertyType.GetConstructor(new Type[]{BaseType}).Invoke(new Object[]{value});
        }
        pi.SetValue(component,o, null);
    }
    catch(Exception ex)
    {
        Debug.WriteLine(ex);
    }
}
public override object GetValue(object component)
{
    try
    {
        object Property = component.GetType().GetProperty(this.Name).GetValue(component,null);
        // handle nulls
        if ( (bool)Property.GetType().GetProperty("IsNull").GetValue(Property,null) ) return DBNull.Value;
        object Value = Property.GetType().GetProperty("Value").GetValue(Property,null);
        return Value;
    }
    catch(Exception ex)
    {
        Debug.WriteLine(ex);
    }
    return null;
}

You might also like...

Comments

Dan Glass

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“XML is like violence - if it's not working for you, you're not using enough of it.”