简体   繁体   English

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

[英]Data Binding with Complex / Nested Objects (C#)

I am posting this with as much explanation as I can since I had a hard time finding generic information on this subject and wanted to share my findings with the SO community. 由于我很难找到关于这个主题的通用信息并希望与SO社区分享我的发现,因此我发布了尽可能多的解释。

Data binding to a collection of complex objects in C# does not normally allow for reading data from the nested object(s) within the class. 在C#中绑定到复杂对象集合的数据通常不允许从类中的嵌套对象读取数据。 An example of this is a member of an instance of class A is an object of class B . 一个例子是class A的实例的成员是class B的对象。 If you need properties from the inner object ( B in this case) when a collection / binding source is used as a Data Source you are out of luck without additional work or access to the original class for modification. 如果在将集合/绑定源用作数据源时需要来自内部对象的属性(在本例中为B ),则无需额外工作或访问原始类进行修改即可。

The question is "How can you use data from inner classes when databinding to a UI object, without access to modify the original class?" 问题是“在数据绑定到UI对象时如何使用内部类中的数据,而不能修改原始类?”

The data in the inner classes can absolutely be used in data-binding mapping, but not by default. 内部类中的数据绝对可以用于数据绑定映射,但默认情况下不能。 The best way to handle this is by setting up PropertyDescriptors and TypeDescriptors . 处理此问题的最佳方法是设置PropertyDescriptorsTypeDescriptors The way I am going to explain below is in a mostly generic implementation, but will allow for data-binding access to the inner objects without requiring any modification of the original classes or extensions to implement interfaces. 我将在下面解释的方式是一个大多数通用的实现,但将允许数据绑定访问内部对象,而无需任何修改原始类或扩展来实现接口。 This is great if you are not the author of the classes you are using or if your are using ORM mapped classes. 如果您不是正在使用的类的作者,或者您正在使用ORM映射类,那么这很好。

There are 4 parts to implement this solution: 实现此解决方案有4个部分:

  1. Extension of the PropertyDescriptor class to access the inner objects PropertyDescriptor类的扩展以访问内部对象
  2. A CustomTypeDescriptor implementation CustomTypeDescriptor实现
  3. A TypeDescriptonProvider implementation TypeDescriptonProvider实现
  4. The attaching of the newly created provider to the type we need access the data. 将新创建的提供程序附加到我们需要访问数据的类型。

PART 1 - Extending the PropertyDescriptor class: 第1部分 - 扩展PropertyDescriptor类:

In order to access the inner components we need to get their PropertyDescriptor s, which are essentially metadata used to access the class's public properties. 为了访问内部组件,我们需要获取它们的PropertyDescriptor ,它们本质上是用于访问类的公共属性的元数据。 This can be done by extending PropertyDescriptor to access child properties. 这可以通过扩展PropertyDescriptor来访问子属性来完成。 Additionally, this where you implement how to read and write back to these objects, or set them to read only (as I did). 此外,您可以在此处实现如何读取和写回这些对象,或将它们设置为只读(就像我一样)。

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*/
    }
}

Part 2 - Implementing a CustomTypeDescriptor : 第2部分 - 实现CustomTypeDescriptor

The CustomTypeDesciptor is what creates the metadata tags to allow the binding of data from the inner objects. CustomTypeDesciptor是创建元数据标签以允许绑定内部对象的数据的内容。 Essentially, we will be creating 'descriptor strings' that link to the Type's properties for the inner objects and then adding them onto the parent object. 本质上,我们将创建“描述符字符串”,链接到内部对象的Type属性,然后将它们添加到父对象。 The format used for the inner objects will be the following "className_property" where classname is the Type of the inner object. 用于内部对象的格式将是以下"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;
    }
}

At this point we now have our 'descriptor strings' for setting the bindings to the innre objects. 此时我们现在有了'描述符字符串'来设置绑定到innre对象。 The inner properties of MyClass can be called like "MyOtherClass_Property1" and the other properties can called like usual with their variable names "Property1" MyClass的内部属性可以像"MyOtherClass_Property1"一样调用,其他属性可以像往常一样调用它们的变量名"Property1"

Part 3 - Implementing a TypeDescriptonProvider : 第3部分 - 实现TypeDescriptonProvider

This is the last custom piece that we will need to create. 这是我们需要创建的最后一个自定义部分。 A TypeDescriptionProvider is the piece that the data-bound object will use to determine what the properties of an object are and is what is used to actually call our CustomTypeDescriptor class whenever the descriptors are needed. TypeDescriptionProvider是数据绑定对象将用于确定对象属性的部分,是用于在需要描述符时实际调用CustomTypeDescriptor类的部分。 This is also the one class that utilizes generics, but is not actually a generic class since we must connect it to our outer object (aka the data-type of the collection being used). 这也是使用泛型的一个类,但实际上并不是泛型类,因为我们必须将它连接到我们的外部对象(也就是所使用的集合的数据类型)。

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;
    }
}

The generic class 'T' is used to designate the Type of the inner object property that we will need to link to our parent object. 泛型类'T'用于指定我们需要链接到父对象的内部对象属性的Type You will see how this works in the next step. 您将在下一步中看到它的工作原理。

Part 4 - Attaching our Provider to the Parent Type: 第4部分 - 将我们的提供者附加到父类型:

Now that we have created the infrastructure to access the data stored in inner properties we must tell the system to use our customized provider when looking up our TypeDescriptors . 现在我们已经创建了访问内部属性中存储的数据的基础结构,我们必须告诉系统在查找我们的TypeDescriptors时使用我们的自定义提供程序。 This is done using the static method: 这是使用静态方法完成的:

TypeDescriptor.AddProvider(provider,type)

This should be done for each inner Type , where we need access to the inner as properties. 这应该针对每个内部Type ,我们需要访问内部属性。 The adding of the provider should be done BEFORE binding the data to the bound object, such as when setting the DataSource property of the UI object for example. 添加提供程序应在将数据绑定到绑定对象之前完成,例如在设置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!

Finally, if for some reason you need to remove this provider and revert back to the default you can perform the exact same operation in reverse: 最后,如果由于某种原因需要删除此提供程序并恢复为默认值,则可以反向执行完全相同的操作:

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

See TypeDescriptor Class - MSDN or The MSDN blog that put me on the right track for more information. 请参阅TypeDescriptor类 - MSDNMSDN博客,它让我走在正确的轨道上以获取更多信息。 Additionally, during my research on this I stumbled upon this SO question, which prompted me to post a full explanation since it was really just asking for part 4 of this answer. 此外,在我对此进行研究时,我偶然发现了这个问题,这促使我发布完整的解释,因为它实际上只是要求这个答案的第4部分。 I hope this helps someone out so they don't need to dig into the System.ComponentModel library as much as I unnecessarily had to! 我希望这可以帮助某人,所以他们不需要像我不必要的那样深入了解System.ComponentModel库!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM