由于我很难找到有关此主题的一般信息,并希望与SO社区分享我的发现,因此我尽可能多地解释这个问题。
数据绑定到C#中的复杂对象集合通常不允许从类中的嵌套对象读取数据。例如,class A
的实例成员是class B
的对象。如果您需要来自内部对象(在这种情况下为B
)的属性,当集合/绑定源用作数据源时,如果没有额外的工作或访问原始类进行修改,您将无法运气。
问题是“在数据绑定到UI对象时如何使用内部类中的数据,而不能修改原始类?”
答案 0 :(得分:7)
内部类中的数据绝对可以用于数据绑定映射,但默认情况下不能。处理此问题的最佳方法是设置PropertyDescriptors
和TypeDescriptors
。我将在下面解释的方式是在主要是泛型实现中,但是允许对内部对象进行数据绑定访问,而无需修改原始类或扩展来实现接口。如果您不是正在使用的类的作者,或者您正在使用ORM映射类,那么这很好。
实施此解决方案有4个部分:
PropertyDescriptor
类以访问内部对象CustomTypeDescriptor
实施TypeDescriptonProvider
实施 第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 - MSDN或The MSDN blog that put me on the right track。另外,在我对此进行研究时,我偶然发现了this SO问题,这促使我发布完整的解释,因为它实际上只是要求这个答案的第4部分。我希望这可以帮助别人,所以他们不需要像我不必要的那样深入了解System.ComponentModel
库!