具有复杂/嵌套对象的数据绑定(C#)



我发布这篇文章时尽可能多地解释,因为我很难找到关于这个主题的一般信息,并且想与SO社区分享我的发现。

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

问题是"当数据绑定到UI对象时,如何使用内部类的数据,而不访问修改原始类?">

内部类中的数据绝对可以用于数据绑定映射,但默认情况下不能。处理此问题的最佳方法是设置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创建元数据标签,以允许绑定来自内部对象的数据。本质上,我们将创建"描述符字符串",链接到内部对象的Type属性,然后将它们添加到父对象上。内部对象使用的格式如下"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部分-将我们的提供商附加到父类型:

既然我们已经创建了访问存储在内部属性中的数据的基础设施,那么在查找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类MSDN或使我走上正轨的MSDN博客。此外,在我对此进行研究的过程中,我偶然发现了这个SO问题,这促使我发布了一个完整的解释,因为它实际上只是在问这个答案的第4部分。我希望这能帮助到一些人,这样他们就不需要像我不必要的那样深入System.ComponentModel库了!

最新更新