=============C#.Net 篇目录==============
译者序:本文是园友在看 《(译)一个通用快速的反射方法调用》 后推荐我看的一片文章,非常感谢,我也从中了解到 .NET数据绑定机制和动态方法特性。
原文:http://www.codeproject.com/Articles/20332/Nested-Property-Binding
源码下载: Download demo project and source - 907.63 KB
介绍
我目前在开发一个可以与 Microsoft SQL Server 一起工作的对象关系映射项目,在这个项目中,我将面对大量涉及程序操作的数据是对象而不是来源于数据库的挑战。如果你曾经尝试过将一个列表对象绑定到DataGrid ,那么我确信你肯定会遇到一个问题:怎么显示不是对象类型本身的属性。
示例:你有一个订单列表,但是你想同时将订单类型的客户名称和帐单地址两个属性一起显示。
这涉及到嵌套属性绑定。很多朋友面对上面这种需求会创建指定对象的视图,但是我通过创建一个ObjectBindingSource
组件实现了一个设计时的解决方案。正如其名,这个组件的数据源是对象而不是DataSet/DataTable。我并不乐意使用DataSet做为绑定方案,因为ObjectBindingSource
组件这已经不会再困扰我了。
下面是这个示例的类结构图。
从上图模型得知,Order
类有一个Customer
和DeliveryAddress
属性,这个简单的订单类组成可以实现在订单上选定一个客户将显示使用嵌套属性绑定的客户详细信息;BillingAddress
是Customer类特有的并且不能编辑;DeliveryAddress
是Order类特有的可以编辑。
System.ComponentModel.ITypedList 接口
// 提供发现可绑定列表架构的功能,其中可用于绑定的属性不同于要绑定到的对象的公共属性。 public interface ITypedList { // 返回表示用于绑定数据的每项上属性的 System.ComponentModel.PropertyDescriptorCollection。 PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors); // 返回列表的名称。 string GetListName(PropertyDescriptor[] listAccessors); }
经过一番搜索,我发现ITypedList是一个简单的检查接口。仅仅提供获取哪些属性属于特定类型。我将要讨论的不是这个接口是如何实现的,因为在网上已有很多相关文章。因此我开始创建我自己的特殊集合组件,如实现 System.ComponentModel.IBindingList 接口等等。但是很快我便清楚的发现我真正想要的是一个像 System.Windows.Forms.BindingSource 这样支持嵌套属性访问器的组件。
扩展标准System.Windows.Forms.BindingSource组件
为了提供设计时数据绑定功能,我们不得不使用到已有的 BindingSource 组件(或至少实现 IBindingList 接口)。鉴于此,我开始扩展标准 BindingSource 并实现嵌套属性的支持。事实证明 BindingSource 已经实现了 ITypedList 接口并且是以虚方法的方式进行实现方便进行特定需求的重写。我为BindingSource 组件扩展了一个属性 BindableProperties ,它用于给开发者标识 BindingSource 中显示哪些属性。然后需要做的是用 BindableProperties 替换掉默认的实现。 ITypedList.GetItemProperties 似乎经常被消费者调用,所以我将CreatePropertyDescriptors方法(创建 propertydescriptors 部分)的代码移出 GetItemProperties 方法并且通过 _createProperties 字段限制只有DataSource或 DataMember改变时才触发CreatePropertyDescriptors 方法。
这里还有另一个属性 AutoCreateObject 稍后会继续讨论。
GetItemProperties 是 ITypedList 接口中非常重要的方法,它先被 BindingSource 实现,然后再被 ObjectBindingSource 组件重写,以提供获取自定义属性描述或嵌套属性描述。
public override PropertyDescriptorCollection GetItemProperties (PropertyDescriptor[] listAccessors) { //Check to see if the descriptors should be recreated if (_createProperties) CreatePropertyDescriptors(); //Check to see if we have a list of descriptors if (_propertyDescriptors.Count > 0) return new PropertyDescriptorCollection(_propertyDescriptors.ToArray()); else //If not populated for some reason, //we just revert to the default implementation return base.GetItemProperties(listAccessors); }
如果我们看一下CreatePropertyDescriptors的代码片段,你会发现它是如此易懂:
foreach (BindableProperty bindableProperty in _bindableProperties) { //Get the original propertydescriptor based on the property path in bindableProperty PropertyDescriptor propertyDescriptor = ReflectionHelper .GetPropertyDescriptorFromPath(itemType, bindableProperty.Name); //Create an attribute array and make room for one more attribute Attribute[] attributes = new Attribute[propertyDescriptor.Attributes.Count + 1]; //Copy the original attributes to the custom descriptor propertyDescriptor.Attributes.CopyTo(attributes, 0); //Create a new attribute preserving information about the original property. attributes[attributes.Length - 1] = new CustomPropertyAttribute(itemType, bindableProperty.Name, propertyDescriptor); //Finally add the new custom property descriptor to the list of property descriptors _propertyDescriptors.Add(new CustomPropertyDescriptor (bindableProperty.Name, propertyDescriptor, attributes,_autoCreateObjects)); }
值得注意的是:自定义属性描述会被创建尽管这个属性是根对象的成员。这是因为 CustomPropertyDescriptor使用的是已编译的 get/set 动态访问器不同于常规的成员反射。我们稍后再看 DynamicAccessor 。
如果你看的仔细,你会注意到这里还有一些特性。当使用嵌套属性绑定时这里只保留有关的真正基础对象和属性的信息。通过查看 CustomPropertyAttribute 你可以轻易找到嵌套属性的来源。如果嵌套属性绑定是使用路径 Order.Customer.BillingAddress.StreetAddress,这个属性名将被转化为 Customer_BillingAddress_StreetAddress ,它看起来更像是 Order类型的一部分(比如一个字段命名)。我们继续探究这个应用程序架构,它在域模型上执行本地化而不是UI组件并且这些信息获取的非常便捷。
使用 IDynamicAccessor 实现快速动态属性访问
DynamicAccessor 的设计思想来源于作者 Herbrandson 。基于 Joel Martinez 发布在 CodeCube.Net 上的类库,我创建了一个类似的类库,适合我的动态属性访问器需求。总体思想是使用 .Net 2.0 框架提供的 DynamicMethodCompiler 特性。对此特性我不进行详细讨论,只谈论动态获取类型上已编译好的set/get访问器以设置和获取值。这些方案被证实对我非常有用,我将其写在core library,它还包含其他如文件上传下载、数据库操作、特性验证和日志操作等和这篇文章无关的功能,但却都是非常易用、通用的功能。
好了,让我们回到设置和获取值的讨论。基类(TypeDescriptor)已经为我们提供了对基础对象 set 和 get 值不可缺少方法,但通过查看相关文章得知 .NET 的TypeDescriptor提供的方法实现实际上是使用反射来设置和获取属性值的。IDynamicAccessor 通过使用 .NET 2.0 的 DynamicMethodCompiler 特性提供动态编译版本的 set/ get 访问器,相比常规反射能获得更快的速度。
如过你查看 ObjectBindingSource 组件属性,你会发现这里还有一个新的属性 AutoCreateObject。这个属性标识 ObjectBindingSource 组件在设置值时是否自动在 propertypath 中创建不存在的对象。比如:
Order.DeliveryAddress.StreetAddress,如果在 Grid 中为 StreetAddress 输入一个值,不保证这是一个有效的 DeliverAddress 。如果 AutoCreateObjects = true ,这个 Address 对象在实际设置值时会被创建并分配给 DeliveryAddress 。为此目的,待创建对象(eg:Address)应该有一个无参构造函数。
public override object GetValue(object component) { object instance = GetNestedObjectInstance(component,_propertyPath,false); if (instance != null) return DynamicAccessorFactory.GetDynamicAccessor (instance.GetType()).GetPropertyValue (instance, _originalPropertyDescriptor.Name); else return null; }
首先我们要获取将要检索值对象实例的一个引用,通过调用 GetNestedObjectInstance 并提供根对象实例和属性路径(example: Customer.Address.StreetAddress)作为参数。第三个参数(bool autoCreate)指示方法是否应该自动创建缺少对象如上文所示,这个参数标识 GetValue 方法中不需要(即直接取 false )。
public override void SetValue(object component, object value) { object instance = GetNestedObjectInstance(component,_propertyPath,_autoCreateObjects); if (instance != null) { DynamicAccessorFactory.GetDynamicAccessor(instance.GetType()). SetPropertyValue(instance, _originalPropertyDescriptor.Name, value); } }
SetValue 使用类似的方式,只是我们会考虑 _autoCreateObjects 选项是否为true。
使用 ObjectBindingSource 组件
使用 ObjectBindingSource 组件的方式和使用 BindingSource 组件大致相同,不同的是你可以编辑 BindableProperties 集合来指定要绑定的属性(即要显示的属性及嵌套属性)。
如果下载使用 ObjectBindingSource 组件,请注意如果你输入一个无效的属性路径这里不会提示此错误;如果输入的是一个有效的属性值,那么 Bindable 属性将忽略 BindingSource 组件对 ITypedList 接口的默认实现。
作者: seesharper
相关链接: