DataGridView绑定复杂实体(属性本身又是实体)
今天我们来讨论下一个实体中某个属性又是实体的类型的集合绑定到DataGridView上的问题。
先来写一个Student类
public class Student { public int StudentNo { get; set; } public string StudentName { get; set; } public string Address { get; set; } public int GradeId { get; set; } public string Gender { get; set; } public string LoginPwd { get; set; } public DateTime Birthday { get; set; } public string Email { get; set; } public string Phone { get; set; } }
然后有一个Result类
public class Result { public Student Student { get; set; } public Subject Subject { get; set; } public DateTime ExamDate { get; set; } public int StudentResult { get; set; } }
我们可以看到Result中有一个属性Student的类型是另外一个实体类Student。
当我们通过List<Result>拿到符合条件的数据后,就可以将List<Result>中的数据绑定到DataGridView上了。但是随之又一个问题产生就,就是DataPropertyName属性应该赋什么值??写Student.StudentName,结果打印出来为空。
今天本人讲两种解决方案:
方案一:通过重写ToString()方法的方案解决
01.如果能保证在实体Result中的Student属性到时候在界面上行只显示Student中的一个属性,例如StudentName,那么我们重写Student的ToString()方法来达到目的。
重写代码如下:
public override string ToString() { return StudentName; }
02.这时候只需要设置DataPropertyName的值为Student,即可,因为设置了Student后,会自动调用它的ToString()方法将StudentName返回。
方案二:在DataGridView控件中,通过在CellFormatting事件中通过反射获取。
01.当数据无法显示在DataGirdView中的时候,其实通过断点调试法,我们发现数据已经在集合中已经有了。只是无法显示在DataGridView控件中而已。那么我们在DataGridView控件的CellFormatting事件中编码实现。故名思意,就是在列加载的时候出发的时间,又称为重写该事件。
代码如下:
//找到绑定字段中带下划线的列 if ((dataGridView1.Rows[e.RowIndex].DataBoundItem != null) && (dataGridView1.Columns[e.ColumnIndex].DataPropertyName.Contains("."))) { //用split方法将其分割成一个数组 string[] names = dataGridView1.Columns[e.ColumnIndex].DataPropertyName.Split('.'); object obj = dataGridView1.Rows[e.RowIndex].DataBoundItem;//获取到当前记录绑定的类型 for (int i = ; i < names.Count(); ++i) { try { //通过反射的方式获取当前列的属性值,如StudentName //第一次循环到Student,第二次拿到的是StudentName var result = obj.GetType().GetProperty(names[i]).GetValue(obj, null); obj = result; e.Value = result.ToString();//拿到对应的值 } catch (Exception) { return; throw; } } }
02.这时候要给DataPropertyName属性赋值为Student.StudentName
解析:这种方式可以处理Student中多列出现在DataGridView中的情况,是比较好的解决方案。而且这样解决方案可以处理Result中引入多个外部实体的属性的情况。
方案三:同方案二一样:msdn提供的一种方式,但是会报错,暂时收录:
if ((dataGridView1.Rows[e.RowIndex].DataBoundItem != null) && (dataGridView1.Columns[e.ColumnIndex].DataPropertyName.Contains("_"))) { string[]nameAndProp= dataGridView1.Columns[e.ColumnIndex].DataPropertyName.Split(new char[] { '_' }); //该行代码会报错,无法将当前行的类型读取转汉城DataRowView ObjectunboundValue= ((DataRowView)dataGridView1.Rows[e.RowIndex].DataBoundItem)[nameAndProp[]]; PropertyInfo objectProperty=unboundValue.GetType().GetProperty(nameAndProp[]); e.Value=objectProperty.GetValue(unboundValue,null).ToString(); }
方案四:msdn解决方案,通过特性实现,How to bind a DataGridView column to a second-level property of a data source,但是本人实验后,无法解决。
暂且收录如下:
This is a frequently asked question. Suppose that we have a class Person which has a property of type Address(another class). The class Address has its own properties. Now we create a collection of Person objects, e.g. List<Person> and would like to bind a DataGridView to the collection.
The following is the code for class Person and Address.
class Person { private string id; private string name; private Address homeAddr; public string ID { get { return id;} set { id = value;} } public string Name { get { return name;} set { name = value;} } public Address HomeAddr { get { return homeAddr;} set { homeAddr = value;} } } class Address { private string cityname; private string postcode; public string CityName { get { return cityname;} set { cityname = value;} } public string PostCode { get { return postcode;} set { postcode = value;} } }
As we all know, DataGridView columns could only be bound to the first-level properties of class Person, e.g. the ID, Name or HomeAddr properties. The following is the code to bind a DataGridView to a collection of Person objects.
List<Person> persons = new List<Person>();
// add some Person objects to the collection
...
dataGridView1.DataSource = persons;
dataGridView1.Columns[0].DataPropertyName = "ID";
dataGridView1.Columns[1].DataPropertyName = "Name";
dataGridView1.Columns[2].DataPropertyName = "HomeAddr";
When a DataGridView column is bound to the HomeAddr property, the name of the class Address will be displayed under the column, i.e. projectname.Address.
If we set the DataPropertyName property of a DataGridView column to "HomeAddr.CityName", an empty text will be displayed under the column, because DataGridView could not find a property called "HomeAddr.CityName" in the class Person.
Is there a way to bind a DataGridView column to the CityName or PostCode property of the HomeAddr property? The answer is YES!
.NET Framework 1.x can implement ICustomTypeDescriptor interface for the class Person. When a Person object in the collection is going to be displayed in a control, data binding calls ICustomTypeDescriptor.GetProperties method to get the object's properties. When implementing ICustomTypeDescriptor.GetProperties method, we could create PropertyDescriptor instances for the second-level properties and return them with the original PropertyDescriptor instances of class Person.
There're two problems here. One problem is that if there's no object in the collection, the ICustomTypeDescriptor.GetProperties method won't be called so that DataGridView couldn't 'see' secondary-level properties of class Person. The other problem is that it requires modifying the class Person because the class Person needs to implement the ICustomTypeDescriptor interface.
To work around the first problem, we could resort to ITypedList interface, i.e. implement the ITypedList interface for the collection. If a data source has implemented the ITypedList interface, data binding calls ITypedList.GetItemProperties to get the properties available for binding. In the ITypedList.GetItemProperties method, we could create an instance of class Person and then call TypeDescriptor.GetProperties(component) method passing the Person instance as the parameter, which in turn calls ICustomTypeDescriptor.GetProperties of the class Person implementation.
.NET Framework 2.0 way is to make use of TypeDescriptionProvider and CustomTypeDescriptor classes, which expand support for ICustomTypeDescriptor. It allows you to write a separate class that implements ICustomTypeDescriptor (for convenience, we could derive the class directly from CustomTypeDescriptor which provides a simple default implementation of the ICustomTypeDescriptor interface) and then to register this class as the provider of description for other types.
Even if a type is added a TypeDesciptionProvider, data binding won't call the custom type descriptor's GetProperties method to get the object's properties. So we still need to implement ITypedList interface for the collection and in the ITypedList.GetItemProperties method, call TypeDescriptor.GetProperties(type) which in turn calls the custom type descriptor's GetProperties method.
To take advantage of this new functionality, we first need to create a TypeDescriptionProvider, which simply derives from TypeDescriptionProvider and overrides its GetTypeDescriptor method. GetTypeDescriptor returns the ICustomTypeDescriptor implementation or the derived CustomTypeDescriptor that TypeDescriptor should use when querying for property descriptors.
When creating providers that are only intended to augment or modify the existing metadata for a type, rather than completely replace it, a recommended approach is to call 'TypeDescriptor.GetProvider' method in the new providers' constructor to get the current provider for the specified type. This base provider can then be used whenever you need to access the underlying type description.
As for PropertyDescriptor, it is an abstract class that derives from another abstract class, MemberDescriptor. MemberDescriptor provides the basic information for each property, and PropertyDescriptor adds functionality related to changing a property's value and determining when that value has changed. To create a PropertyDescriptor on the class Person for each second-level property, we first need to create a custom class that derives from PropertyDescriptor. Pass the original PropertyDescriptor of the second-level property in the new PropertyDescriptor class's constructor, and then use the original PropertyDescriptor whenever we need to query the information of the second-level property.
The following is the code of a custom class that derives from PropertyDescriptor.
public class SubPropertyDescriptor : PropertyDescriptor { private PropertyDescriptor _subPD; private PropertyDescriptor _parentPD; public SubPropertyDescriptor(PropertyDescriptor parentPD,PropertyDescriptor subPD,string pdname) : base(pdname,null) { _subPD = subPD; _parentPD = parentPD; } public override bool IsReadOnly { get { return false; } } 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 _parentPD.ComponentType; } } public override Type PropertyType { get { return _subPD.PropertyType; } } public override object GetValue(object component) { return _subPD.GetValue(_parentPD.GetValue(component)); } public override void SetValue(object component, object value) { _subPD.SetValue(_parentPD.GetValue(component), value); OnValueChanged(component, EventArgs.Empty); } }
The following is the code of a derived CustomTypeDescriptor class.
public class MyCustomTypeDescriptor : CustomTypeDescriptor { public MyCustomTypeDescriptor(ICustomTypeDescriptor parent) : base(parent) { } public override PropertyDescriptorCollection GetProperties() { PropertyDescriptorCollection cols = base.GetProperties(); PropertyDescriptor addressPD = cols["HomeAddr"]; PropertyDescriptorCollection homeAddr_child = addressPD.GetChildProperties(); PropertyDescriptor[] array = new PropertyDescriptor[cols.Count + ]; cols.CopyTo(array, ); array[cols.Count] = new SubPropertyDescriptor(addressPD,homeAddr_child["CityName"],"HomeAddr_CityName"); array[cols.Count + ] = new SubPropertyDescriptor(addressPD, homeAddr_child["PostCode"], "HomeAddr_PostCode"); PropertyDescriptorCollection newcols = new PropertyDescriptorCollection(array); return newcols; } }
The following is the code of the custom TypeDescriptorProvider.
public class MyTypeDescriptionProvider : TypeDescriptionProvider { private ICustomTypeDescriptor td; public MyTypeDescriptionProvider() : this(TypeDescriptor.GetProvider(typeof(Person))) { } public MyTypeDescriptionProvider(TypeDescriptionProvider parent) : base(parent) { } public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) { if (td == null) { td = base.GetTypeDescriptor(objectType, instance); td = new MyCustomTypeDescriptor(td); } return td; } }
At the end, we adorn TypeDescriptionProviderAttribute to the class Person.
[TypeDescriptionProvider(typeof(MyTypeDescriptionProvider))]
class Person
{...}
Then we could bind a DataGridView column to the second-level properties in the class Person as follows:
dataGridView1.Columns[2].DataPropertyName = "HomeAddr_CityName";
dataGridView1.Columns[3].DataPropertyName = "HomeAddr_PostCode";
我们今天的探讨就此结束!前两种方案大家可以尽情使用!
DataGridView绑定复杂实体(属性本身又是实体)的更多相关文章
- 《Entity Framework 6 Recipes》中文翻译系列 (27) ------ 第五章 加载实体和导航属性之关联实体过滤、排序、执行聚合操作
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-9 关联实体过滤和排序 问题 你有一实体的实例,你想加载应用了过滤和排序的相关 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (28) ------ 第五章 加载实体和导航属性之测试实体是否加载与显式加载关联实体
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-11 测试实体引用或实体集合是否加载 问题 你想测试关联实体或实体集合是否已经 ...
- Lind.DDD~实体属性变更追踪器的实现
回到目录 看着这个标题很复杂,大叔把它拆开说一下,实体属性-变更-追踪器,把它拆成三部分大家看起来就容易懂一些了,实体属性:领域实体里有自己的属性,属性有getter,setter块,用来返回和设置属 ...
- sqlalchemy 实体属性提前加载
在flask里需要给视图传送数据,肯定需要把模型的实体属性提前加载,可以使用 sqlalchemy.orm.subqueryload 或 sqlalchemy.orm.joinedload 示例: @ ...
- 关于Entity Framework自动关联查询与自动关联更新导航属性对应的实体注意事项说明
一.首先了解下Entity Framework 自动关联查询: Entity Framework 自动关联查询,有三种方法:Lazy Loading(延迟加载),Eager Loading(预先加载) ...
- .net 读取实体属性和描述注释
.net 读取实体属性和描述注释 class Program { static void Main(string[] args) { TEST test = new TEST(); test.MyNa ...
- SQL反模式学习笔记6 支持可变属性【实体-属性-值】
目标:支持可变属性 反模式:使用泛型属性表.这种设计成为实体-属性-值(EAV),也可叫做开放架构.名-值对. 优点:通过增加一张额外的表,可以有以下好处 (1)表中的列很少: (2)新增属性时,不需 ...
- 【手撸一个ORM】第二步、封装实体描述和实体属性描述
一.实体属性描述 [MyProperty.cs] Name,属性名称 PropertyInfo,反射获取的属性信息,后面很多地方需要通过该属性获取对应的实体类型,或调用SetValue进行赋值 Fie ...
- C# 反射 设置实体属性
C# 反射 设置实体属性 http://blog.csdn.net/cestarme/article/details/6548126 C#反射设置属性值和获取属性值 http://www.cnblog ...
随机推荐
- MongoDB-JAVA-Driver 3.2版本常用代码全整理(3) - 聚合
MongoDB的3.x版本Java驱动相对2.x做了全新的设计,类库和使用方法上有很大区别.例如用Document替换BasicDBObject.通过Builders类构建Bson替代直接输入$命令等 ...
- JQuery this 和 $(this) 详解
this this 在面向对象语言中,指代调用上下文对象,在js中,也是一模一样的概念,所以不管这个this出现在什么地方,只要追踪到当前调用对象是什么,那么this是什么也就一目了然了. 先看一个 ...
- hdu 5666 (大数乘法) Segment
题目:这里 题意:在线段x+y=q与坐标轴围成的三角形中,求有多少个坐标为整数的点,答案模上p. 很容易就想到最后答案就是((q-1)*(q-2))/2然后模上p就是了,但是这个数字比较大,相乘会爆l ...
- Add sharing to your app via UIActivityViewController
http://www.codingexplorer.com/add-sharing-to-your-app-via-uiactivityviewcontroller/ April 4, 2014 Ev ...
- c语言中动态数组的建立
一维动态数组的创建,这个比较简单,直接上代码 #define _CRT_SECURE_NO_DEPRECATE #include<stdio.h> #include<stdlib.h ...
- HDU2243_考研路茫茫――单词情结
给出一些词根,问你有多少种长度为L的串包含至少一个词根. 去年就在敲这个题了,今年才敲出来,还是内牛满面之中... 要求包含至少一个的情况,需要求出所有的情况,减去一个都没有的情况就可以了. 对于给出 ...
- text-indent
<div class="top wd"> <div class="con fl "><a href="#"&g ...
- Linux -- Centos 下配置LNAMP 服务器环境
1.Mysql centos 7 下mysql被替换掉,如有需要请看另一篇: centos 6.5下: yum install mysql mysql-server mysql-devel 启动mys ...
- 解决Android中多次点击启动多个相同界面的问题
在Android开发过程中我们经常会碰到这样的问题,当用户点击一个View启动一个新的Activity的时候,如果快速地多次点击就会启动多个相同的界面.虽然说很少会有用户这么玩自己的手机,但是一旦出现 ...
- HashMap源码解析
本文转载摘录自http://www.importnew.com/20386.html Java为数据结构中的映射定义了一个接口java.util.Map,此接口主要有四个常用的实现类,分别是HashM ...