Library code snippets

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;
}

Comments

  1. 10 Mar 2007 at 02:31
    Very nice sample! Everything seems to work. I replaced your Fake data with a random int to make things more interesting. There are 20 other sites attempting to show this (including 2 at MSDN http://msdn2.microsoft.com/en-us/library/ms993124.aspx and http://msdn2.microsoft.com/en-us/library/ms993236.aspx ) which don't work. Wish I had tried yours first :(
  2. 01 Jan 1999 at 00:00

    This thread is for discussions of Complex Data Binding.

Leave a comment

Sign in or Join us (it's free).

Dan Glass

Related podcasts

  • Object-Oriented Programming in Ruby

    In this episode, I talk with Scott Bellware about object-oriented programming in Ruby, and Ruby's object model. This is taken from a private conversation, and the audio quality suffers at times. Much thanks to Scott for allowing this to be released.This episode of the Alt.NET Podcast is bro...

Want to stay in touch with what's going on? Follow us on twitter!