Winforms 正在寻找DataGridView控件无法绑定到分层(OO)数据的解决方法

Winforms 正在寻找DataGridView控件无法绑定到分层(OO)数据的解决方法,winforms,data-binding,controls,Winforms,Data Binding,Controls,DataGridView控件似乎只能绑定到平面数据源(所有属性都是primative类型)。我的数据是分层的。例如: interface INestedObj { string Prop3 { get; } } interface IParentObj { public string Prop1 { get; } public string Prop2 { get; } public INestedObj NestedObj { get; } } 鉴于

DataGridView控件似乎只能绑定到平面数据源(所有属性都是primative类型)。我的数据是分层的。例如:

    interface INestedObj
{
    string Prop3 { get; }
}

interface IParentObj
{
    public string Prop1 { get; }
    public string Prop2 { get; }
    public INestedObj NestedObj { get; }
}
鉴于此,如何绑定到实现IParentObj的对象?最终你会面临这样的问题:

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
我正在寻求建议和/或解决办法


TIA

您可能可以为“NestedObj.Prop3”添加一个未绑定的列并手动处理其值。要填充列,请处理DataGridView的CellFormatting事件,从当前行获取DataBoundItem,然后从当前行获取Prop3。要更新数据源,请处理CellValidated事件以更新DataBoundItem

可能有比我提到的事件更合适的事件可供使用,但你明白了。

你可以从
INestedObj
中公开属性进行绑定,但解决方案非常凌乱,所有支持数据绑定的WinForms控件都使用
TypeDescriptor
来确定它们绑定到的对象上存在哪些属性。通过
TypeDescriptionProvider
CustomTypeDescriptor
,您可以覆盖默认行为,从而添加/隐藏属性-在这种情况下,隐藏
NestedObj
属性并将其替换为嵌套类型上的所有属性

我要展示的技术有两个(大的)警告:

  • 由于您使用的是接口(而不是具体的类),因此必须在运行时添加自定义类型描述符
  • 自定义类型描述符需要能够创建
    IParentObj
    的具体实例,因此它必须知道一个这样的类,该类具有默认构造函数
  • (请原谅代码太长)

    首先,您需要一种从嵌套类型包装
    PropertyDescriptor
    的方法,以便可以从父类型访问它:

    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
            );
        }
    }
    
    然后,您需要编写一个自定义类型描述符来公开嵌套类型的属性:

    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;
        }
    }
    
    …然后需要一种从上面公开描述符的方法:

    public class ParentObjDescriptionProvider : TypeDescriptionProvider {
        public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, 
            object instance) {
            return new ParentObjDescriptor();
        }
    }
    
    最后,在运行时(在绑定到
    DataGridView
    之前),必须将类型描述提供程序与
    IParentObj
    接口相关联。您无法在编译时执行此操作,因为无法将
    TypeDescriptionProviderAttribute
    放置在接口上

    TypeDescriptor.AddProvider(new ParentObjDescriptionProvider(), typeof(IParentObj));
    
    我通过将一个
    DataGridView
    绑定到一个
    IParentObj[]
    来测试这一点,低看,它为
    Prop1
    Prop2
    NestedObj.Prop3
    创建了列


    不过你得问问自己。。。这真的值得所有这些努力吗?

    在漫长的一天结束后,我想到了一个简单的解决方案

    我使用Linq查询和投影创建了一个匿名类型,在DataGridView中显示正确的信息

    var query = from pt in parentObjCollection
      select new {Prop1=pt.Prop1, Prop2=pt.Prop2, NestedObj.Prop3=pt.NestedObj.Prop3};
    
    我必须为DataPropertyName属性提供适当的值(NestedObj.Prop3),以获得要在网格中显示的值


    当我有更多的时间时,我将尝试实施Bradley的解决方案。

    我发现的最简单的方法是创建一个Self属性。请参阅此解决方案:

    哇,谢谢你,布拉德利。你得到了完整性奖杯。昨晚我找到了一个不同的(更简单?)解决方案,我现在就发布。这是一个非常优雅的解决方案,但是你必须牺牲一些东西,比如更改通知(如果你的对象首先支持它,我不知道)。最后一件您可能想了解的事情是
    ITypedList
    接口,集合可以实现该接口以公开自定义属性。经过反思,它比我发布的
    TypeDescriptorProvider
    解决方案略显粗糙。