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 ...
随机推荐
- PHP调试总结
PHP调试总结一,环境方面,比如查看安装扩展是否生效,是总支持某扩展.可以在web目录中建一个phpinfo.php在里面输入<?phpphpinfo();?>在浏览器上访问一下,会输出P ...
- nested exception is com.mysql.jdbc.PacketTooBigException: Packet for query is too large (1044 > 1024
HTTP Status 500 - type Exception report message description The server encountered an internal error ...
- sendEmail的使用
坑!坑!坑!今天又弄了一天如何发送邮件,首先用sendmail,postfix.....等,都试过了,各种报错,然后就从早上弄到现在,在群里问问题的时候,一位好心的大哥说了他用的是sendEmail, ...
- Linux C _exit函数与exit函数的联系与区别
一.联系 1.功能上,_exit和exit函数都是让进程正常退出,即关闭进程所打开的文件描述符,释放已占用内存和其他资源. 二.区别 1._exit函数在头文件unistd.h中声明,而exit在头文 ...
- Open Live Writer的配置
--------siwuxie095 1.首先到官网下载OLW:http://openlivewriter.org/ 2.选择 Other services 3.填写"博客地址". ...
- CentOS7安装mysql5.7.11
开始安装 yum update yum install wget wget http://repo.mysql.com/mysql57-community-release-el7-7.noarch.r ...
- Linux下守护进程初探
守护进程一直后台运行,与终端脱离,一般负责周期性地执行系统任务,服务器应用一般以守护进程进行运行.实现一个守护进程主要分为5部: 1.脱离父进程 2.setsid(可以选择再次fork) 3.umas ...
- mORMot 数据库操作
程序中要使用数据库,首先是引用SynCommons, SynDB单元,根据不同的数据库类型,简单举几个例子: 1 使用Access数据库,引用SynCommons, SynDB,SynOleDb三个单 ...
- $ajax引用DOM
- CoreLocation框架的使用---定位,求两地距离
前言: 在iOS开发中,有关导航,周边的开发,必须基于2个框架: Map Kit :用于地图展示 Core Location :用于地理定位 用户隐私的保护 从iOS 6开始,苹果在保护用户隐私方 ...