Introduction
Data binding can be nasty - especially when you can't use the System.Data
objects. I have tried to use all the features of data binding to show them to 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 - i.e., hierarchical data. Maybe some of the properties can't 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 following 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 grid compatible types, like SqlTypes
) - A class implementing
Attribute
(so we know what's in the collection)
For those of you that have tried this and it didn't 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 had 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" )
{
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);
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;
}
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.