内部类中的数据绝对可以用于数据绑定映射,但默认情况下不能。处理此问题的最佳方法是设置PropertyDescriptors 和TypeDescriptors。我将在下面解释的方式是大部分通用实现,但将允许对内部对象进行数据绑定访问,而无需对原始类或扩展进行任何修改来实现接口。如果您不是您正在使用的类的作者,或者您正在使用 ORM 映射类,这将非常有用。
实现此解决方案有 4 个部分:
-
PropertyDescriptor 类的扩展以访问内部对象
-
CustomTypeDescriptor 实现
-
TypeDescriptonProvider 实现
- 将新创建的提供程序附加到我们需要访问数据的类型。
第 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 - MSDN 或The MSDN blog that put me on the right track 了解更多信息。此外,在我对此进行研究期间,我偶然发现了this SO 问题,这促使我发布了一个完整的解释,因为它实际上只是要求这个答案的第 4 部分。我希望这对某人有所帮助,这样他们就不需要像我不必要的那样深入研究System.ComponentModel 库!