It would seem that the DataGridView control can only bind to data sources that are flat (all the Properties are primative types). My data is hierarchal. For example:
interface INestedObj
{
string Prop3 { get; }
}
interface IParentObj
{
public string Prop1 { get; }
public string Prop2 { get; }
public INestedObj NestedObj { get; }
}
Given this, how does one bind to an object implementing IParentObj? Eventually you are faced with having to do something like this:
grid.Columns["prop1Col"].DataPropertyName = "Prop1";
grid.Columns["prop2Col"].DataPropertyName = "Prop2";
grid.Columns["prop3Col"].DataPropertyName = "How to display Prop3?";
grid.Columns["prop3Col"].DataPropertyName = "NestedObj.Prop3"; // does not work
I am looking for advice and/or work-arounds.
TIA
You can expose properties from INestedObj
for binding, but the solution is very messy.To give some background, all WinForms controls which support databinding use TypeDescriptor
to determine which properties exist on the objects they're binding to. Through TypeDescriptionProvider
and CustomTypeDescriptor
, you can override the default behaviour and thusly add/hide properties - in this case, hiding the NestedObj
property and replacing it with all of the properties on the nested type.
The technique i'm going to show has 2 (big-ish) caveats:
IParentObj
, therefore it must know one such class which has a default constructor.(Please excuse the lengthy code)
First, you need a way of wrapping a PropertyDescriptor
from the nested type so that it can be accessed from the parent type:
public class InnerPropertyDescriptor : PropertyDescriptor {
private PropertyDescriptor innerDescriptor;
public InnerPropertyDescriptor(PropertyDescriptor owner,
PropertyDescriptor innerDescriptor, Attribute[] attributes)
: base(owner.Name + "." + innerDescriptor.Name, attributes) {
this.innerDescriptor = innerDescriptor;
}
public override bool CanResetValue(object component) {
return innerDescriptor.CanResetValue(((IParentObj)component).NestedObj);
}
public override Type ComponentType {
get { return innerDescriptor.ComponentType; }
}
public override object GetValue(object component) {
return innerDescriptor.GetValue(((IParentObj)component).NestedObj);
}
public override bool IsReadOnly {
get { return innerDescriptor.IsReadOnly; }
}
public override Type PropertyType {
get { return innerDescriptor.PropertyType; }
}
public override void ResetValue(object component) {
innerDescriptor.ResetValue(((IParentObj)component).NestedObj);
}
public override void SetValue(object component, object value) {
innerDescriptor.SetValue(((IParentObj)component).NestedObj, value);
}
public override bool ShouldSerializeValue(object component) {
return innerDescriptor.ShouldSerializeValue(
((IParentObj)component).NestedObj
);
}
}
Then you need to write a custom type descriptor that exposes the properties from the nested type:
public class ParentObjDescriptor : CustomTypeDescriptor {
public override PropertyDescriptorCollection GetProperties(
Attribute[] attributes) {
PropertyDescriptorCollection properties
= new PropertyDescriptorCollection(null);
foreach (PropertyDescriptor outer in TypeDescriptor.GetProperties(
new ParentObj() /* concrete implementation of IParentObj */,
attributes, true)) {
if (outer.PropertyType == typeof(INestedObj)) {
foreach (PropertyDescriptor inner in TypeDescriptor.GetProperties(
typeof(INestedObj))) {
properties.Add(new InnerPropertyDescriptor(outer,
inner, attributes));
}
}
else {
properties.Add(outer);
}
}
return properties;
}
}
...and then you need a way of exposing the descriptor from above:
public class ParentObjDescriptionProvider : TypeDescriptionProvider {
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType,
object instance) {
return new ParentObjDescriptor();
}
}
Finally, at run-time (before you bind to the DataGridView
), you must associate the type description provider with the IParentObj
interface. You can't do this at compile-time because TypeDescriptionProviderAttribute
can't be placed on interfaces...
TypeDescriptor.AddProvider(new ParentObjDescriptionProvider(), typeof(IParentObj));
I tested this by binding a DataGridView
to an IParentObj[]
and, low and behold, it creates columns for Prop1
, Prop2
and NestedObj.Prop3
.
You have to ask yourself, though... is it really worth all that effort?
Here is a simple solution that came to me at the end of a long day.
I used a Linq query and projection to create an anonymous type that displays the proper information in the DataGridView.
var query = from pt in parentObjCollection
select new {Prop1=pt.Prop1, Prop2=pt.Prop2, NestedObj.Prop3=pt.NestedObj.Prop3};
I had to supply the proper value (NestedObj.Prop3) to the DataPropertyName property to get the value to display in the grid.
When I have more time I am going to try and implement Bradley's solution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With