【问题标题】:Data Binding with Complex / Nested Objects (C#)与复杂/嵌套对象的数据绑定 (C#)
【发布时间】:2014-02-02 19:35:52
【问题描述】:

由于我很难找到有关此主题的通用信息,因此我在发布此内容时会尽可能多地进行解释,并希望与 SO 社区分享我的发现。

在 C# 中,数据绑定到复杂对象的集合通常不允许从类中的嵌套对象中读取数据。这方面的一个例子是class A 实例的成员是class B 的对象。如果您在将集合/绑定源用作数据源时需要内部对象的属性(在本例中为 B),那么如果没有额外的工作或访问原始类进行修改,那么您就很不走运了。

问题是“如何在数据绑定到 UI 对象时使用内部类中的数据,而无需修改原始类?”

【问题讨论】:

    标签: c# .net user-interface data-binding datasource


    【解决方案1】:

    内部类中的数据绝对可以用于数据绑定映射,但默认情况下不能。处理此问题的最佳方法是设置PropertyDescriptorsTypeDescriptors。我将在下面解释的方式是大部分通用实现,但将允许对内部对象进行数据绑定访问,而无需对原始类或扩展进行任何修改来实现接口。如果您不是您正在使用的类的作者,或者您正在使用 ORM 映射类,这将非常有用。

    实现此解决方案有 4 个部分:

    1. PropertyDescriptor 类的扩展以访问内部对象
    2. CustomTypeDescriptor 实现
    3. TypeDescriptonProvider 实现
    4. 将新创建的提供程序附加到我们需要访问数据的类型。

    第 1 部分 - 扩展 PropertyDescriptor 类:

    为了访问内部组件,我们需要获取它们的PropertyDescriptors,它们本质上是用于访问类的公共属性的元数据。这可以通过扩展PropertyDescriptor 来访问子属性来完成。此外,您还可以在此处实现如何读取和回写这些对象,或将它们设置为只读(就像我所做的那样)。

    class SubPropertyDescriptor : PropertyDescriptor
    {
        private PropertyDescriptor _parent;
        private PropertyDescriptor _child;
    
        public SubPropertyDescriptor(PropertyDescriptor parent, PropertyDescriptor child, string propertyDescriptorName)
            : base(propertyDescriptorName, null)
        {
            _child = child;
            _parent = parent;
        }
        //in this example I have made this read-only, but you can set this to false to allow two-way data-binding
        public override bool IsReadOnly{ get { return true; } }
        public override void ResetValue(object component)  { }
        public override bool CanResetValue(object component){ return false; }
        public override bool ShouldSerializeValue(object component){ return true;}
        public override Type ComponentType{ get { return _parent.ComponentType; } }
        public override Type PropertyType{ get { return _child.PropertyType; } }
        //this is how the value for the property 'described' is accessed
        public override object GetValue(object component)
        {
            return _child.GetValue(_parent.GetValue(component));
        }
        /*My example has the read-only value set to true, so a full implementation of the SetValue() function is not necessary.  
        However, for two-day binding this must be fully implemented similar to the above method. */
        public override void SetValue(object component, object value)
        {
            //READ ONLY
            /*Example:  _child.SetValue(_parent.GetValue(component), value);
              Add any event fires or other additional functions here to handle a data update*/
        }
    }
    

    第 2 部分 - 实现 CustomTypeDescriptor

    CustomTypeDesciptor 创建元数据标签以允许绑定来自内部对象的数据。本质上,我们将创建“描述符字符串”,链接到内部对象的类型属性,然后将它们添加到父对象中。用于内部对象的格式如下"className_property",其中类名是内部对象的Type

    class MyClassTypeDescriptors : CustomTypeDescriptor
    {
        Type typeProp;
    
        public MyClassTypeDescriptors(ICustomTypeDescriptor parent, Type type)
            : base(parent)
        {
            typeProp = type;
        }
        //This method will add the additional properties to the object.  
        //It helps to think of the various PropertyDescriptors are columns in a database table
        public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            PropertyDescriptorCollection cols = base.GetProperties(attributes);
            string propName = ""; //empty string to be populated later
            //find the matching property in the type being called.
            foreach (PropertyDescriptor col in cols)
            {
                if (col.PropertyType.Name == typeProp.Name)
                    propName = col.Name;
            }
            PropertyDescriptor pd = cols[propName];
            PropertyDescriptorCollection children = pd.GetChildProperties(); //expand the child object
    
            PropertyDescriptor[] propDescripts = new PropertyDescriptor[cols.Count + children.Count];
            int count = cols.Count; //start adding at the last index of the array
            cols.CopyTo(propDescripts, 0);
            //creation of the 'descriptor strings'
            foreach (PropertyDescriptor cpd in children)
            {
                propDescripts[count] = new SubPropertyDescriptor(pd, cpd, pd.Name + "_" + cpd.Name);
                count++;
            }
    
            PropertyDescriptorCollection newCols = new PropertyDescriptorCollection(propDescripts);
            return newCols;
        }
    }
    

    此时,我们现在有了用于设置与 innre 对象的绑定的“描述符字符串”。 MyClass 的内部属性可以像"MyOtherClass_Property1" 一样调用,其他属性可以像往常一样使用它们的变量名"Property1" 调用

    第 3 部分 - 实现 TypeDescriptonProvider

    这是我们需要创建的最后一个自定义部分。 TypeDescriptionProvider 是数据绑定对象用来确定对象属性的部分,并且在需要描述符时用于实际调用我们的 CustomTypeDescriptor 类。这也是一个使用泛型的类,但实际上不是泛型类,因为我们必须将它连接到我们的外部对象(也就是正在使用的集合的数据类型)。

    class MyClassTypeDescProvider<T> : TypeDescriptionProvider
    {
        private ICustomTypeDescriptor td;
    
        public DigiRecordBindingTypeDescProvider()
            : this(TypeDescriptor.GetProvider(typeof(MyClass)))
        { }
    
        public MyClassTypeDescProvider(TypeDescriptionProvider parent)
            : base(parent)
        { }
    
        public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
        {
            if (td == null)
            {
                td = base.GetTypeDescriptor(objectType, instance);
                td = new MyClassTypeDescriptors(td, typeof(T));
            }
            return td;
        }
    }
    

    通用类“T”用于指定我们需要链接到父对象的内部对象属性的Type。您将在下一步中看到这是如何工作的。

    第 4 部分 - 将我们的 Provider 附加到父类型:

    现在我们已经创建了访问存储在内部属性中的数据的基础架构,我们必须告诉系统在查找我们的TypeDescriptors 时使用我们自定义的提供程序。这是使用静态方法完成的:

    TypeDescriptor.AddProvider(provider,type)
    

    这应该为每个内部Type 完成,我们需要访问内部作为属性。应在将数据绑定到绑定对象之前添加提供程序,例如设置 UI 对象的DataSource 属性时。

    IQueryable<MyClass> myData = PopulateCollectionWithData();
    TypeDescriptor.AddProvider(new MyClassTypeDescProvider<MyOtherClass>(), typeof(MyClass));
    TypeDescriptor.AddProvider(new MyClassTypeDescProvider<MyThirdClass>(), typeof(MyClass));
    DataGridView1.DataSource = myData; //don't bind directly to a collection if you are doing two-way binding.  Use a BindingSource instead!
    

    最后,如果由于某种原因您需要删除此提供程序并恢复为默认值,您可以反向执行完全相同的操作:

    TypeDescriptor.RemoveProvider(new MyClassTypeDescProvider<MyOtherClass>(), typeof(MyClass));
    TypeDescriptor.RemoveProvider(new MyClassTypeDescProvider<MyThirdClass>(), typeof(MyClass));
    

    请参阅TypeDescriptor Class - MSDNThe MSDN blog that put me on the right track 了解更多信息。此外,在我对此进行研究期间,我偶然发现了this SO 问题,这促使我发布了一个完整的解释,因为它实际上只是要求这个答案的第 4 部分。我希望这对某人有所帮助,这样他们就不需要像我不必要的那样深入研究System.ComponentModel 库!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多