使用复杂/嵌套对象进行数据绑定(C#)

时间:2014-01-14 16:32:01

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

由于我很难找到有关此主题的一般信息,并希望与SO社区分享我的发现,因此我尽可能多地解释这个问题。

数据绑定到C#中的复杂对象集合通常不允许从类中的嵌套对象读取数据。例如,class A的实例成员是class B的对象。如果您需要来自内部对象(在这种情况下为B)的属性,当集合/绑定源用作数据源时,如果没有额外的工作或访问原始类进行修改,您将无法运气。

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

1 个答案:

答案 0 :(得分:7)

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

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

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

    为了访问内部组件,我们需要获取它们的PropertyDescriptor,它们本质上是用于访问类的公共属性的元数据。这可以通过扩展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是创建元数据标签以允许绑定内部对象的数据的原因。本质上,我们将创建“描述符字符串”,链接到内部对象的Type属性,然后将它们添加到父对象。用于内部对象的格式将是以下"className_property",其中classname是内部对象的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部分 - 将我们的提供者附加到父类型:

    现在我们已经创建了访问内部属性中存储的数据的基础架构,我们必须告诉系统在查找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库!