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
andIBindingList
(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