将无属性实体绑定到 Windows 窗体控件
如何避免实体对象的属性编码

引言
将没有属性的实体绑定到 Windows 窗体控件。
背景
大多数开发者不喜欢为实体的每个字段编写属性(尽管在我所知道的所有框架中,属性是强制性的)。 所有 Window Forms 控件的数据绑定机制都需要这些属性。 标准数据绑定也不支持绑定嵌入对象属性(例如 Person.address.City
)。 因为我厌倦了编写冗余代码(属性和虚拟对象,以便能够绑定嵌入对象属性),所以诞生了这个小型框架的想法。
基础实体 BaseEntiy
提供了框架数据绑定机制所需的所有方法。 实体只定义私有或公共字段。 GenericEntityList<T>
和 GenericEntityPropertyDescriptor
类是将所有属性调用转换为 BaseEntity
类的 GetValue<T>
和 SetValue<T>
方法的唯一标准数据绑定类的扩展。
目前,该框架仅支持 DataGridView
的数据绑定。
Using the Code
让我们从使用框架的一个简单示例开始。 创建一个新项目(类库),设置对 *GenericEntity.dll* 的引用并创建一个类,例如 Person。 Person
类有五个私有成员:No、Name、Surname、Birthdate 和 Address。 为了支持框架的数据绑定机制,Person
类必须继承自 BaseEntity
。 使用私有成员时,设置 PropertyVisibility
属性来控制可见性。
public class Person:BaseEntity
{
[PropertyVisibility(true)]
private Int32 no;
[PropertyVisibility(true)]
private String name;
[PropertyVisibility(true)]
private String surname;
[PropertyVisibility(true)]
private DateTime dateOfBirth;
[PropertyVisibility(true)]
private Address address;
}
现在创建第二个项目(Windows 应用程序),设置对 *GenericEntity.dll* 和 *SampleLib.dll* 的引用,创建一个 Form
并向 Form
添加一个 DataGridView
控件。
要将 Person
的 Collection
绑定到 DataGridView
,请将 GenericEntityList<Person>
设置为 DataGridView
的数据源。
Person person = new Person();
person.SetValue<Int32>("no", 1);
person.SetValue<String>("name", "Donald");
person.SetValue<String>("surname", "Duck");
person.SetValue<DateTime>("dateOfBirth", new DateTime(1934, 6, 9));
Address address = new Address();
address.SetValue<String>("city", "Ducktown");
address.SetValue<String>("street", "Duck Str. 1");
person.SetValue<Address>("address", address);
GenericEntityList<Person> persons = new GenericEntityList<Person>();
persons.Add(person);
PersonsDataGridView.DataSource = persons;
Person
的所有公共或可见标记成员都会显示在 DataGridView
中。 还会显示嵌入的 BaseEntity
类的所有可见标记成员。 GenericEntityList<T>
支持添加新行和排序。
关注点
反射和泛型是 .NET 2.0 框架最有价值的特性。 我的小型数据绑定框架结合了两者,无需编写冗余属性即可处理数据绑定机制。 让我们看一下 GenericEntityList<T>
类。 GetItemProperties
方法将数据源实体的所有公共属性发布到绑定类。 为了支持绑定没有属性的实体,GenericEntityList<T>
收集实体的所有公共或可见标记的私有成员,并将此 PropertyDescriptorCollection
发布到绑定类。
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
T entity = items[0];
IList<GenericEntityPropertyDescriptor> genericProperties = GetVisibleProperties(
entity.GetType(), null);
PropertyDescriptor[] propertyDescriptors =
new PropertyDescriptor[genericProperties.Count];
int index = 0;
foreach (GenericEntityPropertyDescriptor genericProperty in genericProperties)
{
propertyDescriptors[index] = genericProperty;
index++;
}
return new PropertyDescriptorCollection(propertyDescriptors);
}
internal IList<GenericEntityPropertyDescriptor> GetVisibleProperties(Type entityType,
String rootPropertyName)
{
FieldInfo[] fields = GetFieldInfos(entityType);
IList<GenericEntityPropertyDescriptor> propertyList =
new List<GenericEntityPropertyDescriptor>();
foreach (FieldInfo fieldInfo in fields)
{
if (FieldIsVisible(fieldInfo))
{
String propertyName = fieldInfo.Name;
if (rootPropertyName != null)
propertyName = rootPropertyName + "." + fieldInfo.Name;
if (fieldInfo.FieldType.BaseType.Equals(typeof(BaseEntity)))
{
Type subEntityType = fieldInfo.FieldType;
foreach (
GenericEntityPropertyDescriptor propertyDescriptor in
GetVisibleProperties(subEntityType, propertyName))
{
propertyList.Add(propertyDescriptor);
}
}
else
{
propertyList.Add(
new GenericEntityPropertyDescriptor(propertyName, items[0]));
}
}
}
return propertyList;
}
internal Boolean FieldIsVisible(FieldInfo info)
{
if (!info.IsPublic)
{
PropertyVisibilityAttribute[] attributes =
(PropertyVisibilityAttribute[])info.GetCustomAttributes(typeof(
PropertyVisibilityAttribute), true);
if (attributes == null)
return false;
if (!attributes[0].IsVisible)
return false;
}
return true;
}
为了支持排序,需要自定义 Comparer
。 在此框架中,Comparer
是一个泛型类 GenericEntityComparer<T>
,其中 T 必须继承自 BaseEntity
。 Constructor
期望两个参数,String fieldName
和 Type fieldType
。 虽然 GenericEntityList<T>
必须创建 GenericEntityComparer<T>
的实例,但必须通过反射来完成创建。 要创建泛型类的实例,需要通过方法 MakeGenericType
将 Types
列表传递给泛型类型的泛型类型参数。 GenericEntityComparer<T>
类型的 GetConstructor
方法也需要 Constructor
参数的 Types
。 要创建实例,请使用 Constructor
参数的值调用 ConstructorInfo
的 Invoke
方法。
internal IComparer<T> GetComparer(PropertyDescriptor property)
{
Type genericType = typeof(GenericEntityComparer<>);
genericType = genericType.MakeGenericType(new Type[1]{typeof(T)});
ConstructorInfo constructorInfo = genericType.GetConstructor(
new Type[2]{typeof(String), typeof(Type)});
object[] parameters = new object[2];
parameters[0] = property.Name;
parameters[1] = items[0].GetFieldType(property.Name);
return (IComparer<T>)constructorInfo.Invoke(parameters);
}
IComparer<T>
的 Compare
实现使用 BaseEntity
的泛型方法 GetValue<T>
。 由于该方法是泛型的,因此使用反射来调用它。
public int Compare(T x, T y)
{
MethodInfo methodInfo = x.GetType().GetMethod("GetValue");
MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(
x.GetFieldType(fieldName));
object[] parameters = new object[1];
parameters[0] = fieldName;
IComparable xComparable = ((IComparable)genericMethodInfo.Invoke(x, parameters));
IComparable yComparable = ((IComparable)genericMethodInfo.Invoke(y, parameters));
return xComparable.CompareTo(yComparable);
}
该实现假设 IComparable
Type
以一种不雅的方式进行比较,但我还没有找到避免容易出错的强制转换的方法。