开发者

Looking for work-around for inability of DataGridView control to bind to hierarchical (OO) data

It would seem that the DataGridView control can only bind to data sources that开发者_JAVA技巧 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:

  1. Since you're working with interfaces (and not concrete classes), you have to add the custom type descriptor at runtime.
  2. The custom type descriptor needs to be able to create a concrete instance of 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.


You could probably add an unbound column for "NestedObj.Prop3" and manually handle its value. To get the column populated, handle the CellFormatting event of the DataGridView, get the DataBoundItem from the current row and get the Prop3 from that. To update the data source, handle the CellValidated event to update the DataBoundItem.

There may be more appropriate events to use than the ones I mentioned, but you get the idea.


The easiest way I found is to create a Self property. See this solution: Databinding a combobox column to a datagridview per row (not the entire column)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜