I need to bind a DataGridView to a BindingList<TOuter>. But I don't want to
bind to properties of TOuter, instead TOuter is a wrapper around 2 other
objects of type TInner1 and TInner2. I want to bind to selected public
properties of TInner1 and TInner2, without writing code in TOuter which
exposes those "inner" properties. In essence TInner1 and TInner2 are like
table rows I want to join, and create a new type for them which exposes some
of their public properties, and bind the DataGridView to a BindingList<> of
that new type. Further, I don't want to use automatically constructed columns
- I want to manually add the columns to the DataGridView so I can control the
types, widths and other attributes of those columns. I'd like to use
reflection to explore TInner1 and TInner2 to find all their public properties,
and somehow "push" them out to somewhere the DataGridView can see. I have not
been able to solve this problem. I've tried implementing
ICustomTypeDescriptor on TOuter to return a PropertyDescriptorCollection of
the "inner" properties I want to expose, using static TypeDescriptor methods
to do most of the work. ICustomTypeDescriptor.GetProperties() is called, but
ICustomTypeDescriptor.GetPropertyOwner() is not, and the properties of the
TInner1 and TInner2 never seem to be accessed (it acts like the grid is
unbound). I also tried subclassing BindingList and implementing ITypedList on
it to expose the PropertyDescriptorCollection, no joy. It seems to me that
DataGridView does not use the ICustomTypeDescriptor and/or ITypedList
interfaces the way I think it does, I'm implementing them wrong, or manually
creating the columns is bypassing some of the normal property binding logic.
Does anyone have some advice or an explanation for why my attempts have
failed?
Marc Gravell - 30 Sep 2008 09:10 GMT
This is quite complex to do. I would recommend TypeDescriptionProvider
instead of implementing ICustomTypeDescriptor, since this can apply in a
few additional scenarios. Anyway, here's a simplified version of the
code... I have cheated a little by using a PropertyInfo for the two
branches - it is possible to get a PropertyDescriptor for these, but you
need a few more tricks to do it...
I've used C# 3.0 just for the example - the main code should work in C#
2.0 too.
Marc
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Forms;
static class Program {
static void Main()
{
Application.EnableVisualStyles();
BindingList<Tuple> list = new BindingList<Tuple>() {
new Tuple { Foo = { Name = "Fred"}, Bar = { DateOfBirth =
DateTime.Today}},
new Tuple { Foo = { Name = "Jo"}, Bar = { DateOfBirth =
DateTime.Today.AddDays(-24)}}
};
using(Form form = new Form())
using (DataGridView grid = new DataGridView())
{
grid.Dock = DockStyle.Fill;
form.Controls.Add(grid);
grid.DataSource = list;
Application.Run(form);
}
}
}
class Foo
{
public string Name { get; set; }
}
class Bar
{
public DateTime DateOfBirth { get; set; }
}
[TypeDescriptionProvider(typeof(TupleDescriptionProvider))]
class Tuple
{
public Tuple()
{
Foo = new Foo();
Bar = new Bar();
}
public Foo Foo { get; private set; }
public Bar Bar { get; private set; }
}
internal class TupleDescriptionProvider : TypeDescriptionProvider
{
public override ICustomTypeDescriptor GetTypeDescriptor(Type
objectType, object instance)
{
return TupleTypeDescriptor.Default;
}
}
internal class TupleTypeDescriptor : CustomTypeDescriptor
{
private TupleTypeDescriptor() { }
public static readonly TupleTypeDescriptor Default = new
TupleTypeDescriptor();
public override PropertyDescriptorCollection GetProperties()
{
List<PropertyDescriptor> props = new List<PropertyDescriptor>();
Wrap(props, typeof(Tuple).GetProperty("Foo"),
TypeDescriptor.GetProperties(typeof(Foo)));
Wrap(props, typeof(Tuple).GetProperty("Bar"),
TypeDescriptor.GetProperties(typeof(Bar)));
return new PropertyDescriptorCollection(props.ToArray(), true);
}
public override PropertyDescriptorCollection
GetProperties(Attribute[] attributes)
{
return GetProperties();
}
static void Wrap(List<PropertyDescriptor> list, PropertyInfo
parent, PropertyDescriptorCollection properties)
{
foreach (PropertyDescriptor child in properties)
{
list.Add(new WrappedDescriptor(parent, child));
}
}
}
internal class WrappedDescriptor : PropertyDescriptor
{
private readonly PropertyInfo parent;
private readonly PropertyDescriptor child;
public WrappedDescriptor(PropertyInfo parent, PropertyDescriptor child)
: base(GetName(parent,child), GetAttribs(child))
{
this.parent = parent;
this.child = child;
}
static string GetName(PropertyInfo parent, PropertyDescriptor child)
{
return parent.Name + "_" + child.Name;
}
static Attribute[] GetAttribs(PropertyDescriptor property)
{
Attribute[] attribs = new Attribute[property.Attributes.Count];
property.Attributes.CopyTo(attribs, 0);
return attribs;
}
public override bool ShouldSerializeValue(object component)
{
return child.ShouldSerializeValue(parent.GetValue(component,
null));
}
public override object GetValue(object component)
{
return child.GetValue(parent.GetValue(component, null));
}
public override void SetValue(object component, object value)
{
child.SetValue(parent.GetValue(component, null), value);
}
public override void ResetValue(object component)
{
child.ResetValue(parent.GetValue(component, null));
}
public override bool CanResetValue(object component)
{
return child.CanResetValue(parent.GetValue(component, null));
}
public override Type PropertyType
{
get { return child.PropertyType; }
}
public override Type ComponentType
{
get { return parent.ReflectedType; }
}
public override bool IsReadOnly
{
get { return child.IsReadOnly; }
}
}