Winform下让你的DataGridView控件支持点语法(即显示list中的子对象属性)

 前言:

不想看前言的直接去看正文吧!另外文末有彩蛋。

 

DataGridView可以支持多种数据源格式,比如DataTable和List。

DataTable没啥特殊的,本身就是一张二维的表,可以和DataGridView行列对应。

但是List不太一样,举个栗子,有一个UserList用户列表,格式是这样的:

class UserModel{
    Id
    Name,
Gender, Age, JobModel }
class JobModel{ JobId, JobName, UserId }

对于上面这种,User中带有一个Job实体的数据结构,Job类中的三个属性在DataGridView中是无法直接显示的。

于是你想当然地会在DataGridView中这样绑定:JobModel.JobName

但是,现实是很骨感的:

工作名称那一栏并没有被匹配到,在最右侧则有一个JobModel的实例则直接被显示了出来。

这显然不是我们想要的结果。

所以,我们要做的就是让DataGridView支持点语法。

那么正文开始!

 

正文:

好吧,其实正文真的很简单。

只要在DataGridView控件的CellFormatting事件中加入以下代码即可。

其中dgvLinqDemo改成你自己的控件名就好啦:

if ((dgvLinqDemo.Rows[e.RowIndex].DataBoundItem != null) &&
    (dgvLinqDemo.Columns[e.ColumnIndex].DataPropertyName.Contains(".")))
{
    string[] nameAndProp = dgvLinqDemo.Columns[e.ColumnIndex].DataPropertyName.Split(new char[] { '.' });
    object pObj = dgvLinqDemo.Rows[e.RowIndex].DataBoundItem;
    for (int i = 0; i < nameAndProp.Length - 1; i++)
    {
        pObj = GetObject(pObj, nameAndProp[i]);
        if (pObj == null)
        {
            e.Value = string.Empty;
            break;
        }
        if (i == nameAndProp.Length - 2)
        {
            PropertyInfo objectProperty = pObj.GetType().GetProperty(nameAndProp[i + 1]);
            e.Value = objectProperty.GetValue(pObj, null).ToString();
        }
    }
}

private object GetObject(object pObj, string nameAndProp)
{
    if (pObj == null)
    {
        return null;
    }
    PropertyInfo objProp = pObj.GetType().GetProperty(nameAndProp);
    return objProp.GetValue(pObj, null);
}

 

后话:

正文讲完了,如果你想知道原理的话,可以看我下面的大篇幅的注释说明。

大致的思路是通过拆分点语法的字符串来通过反射获取下一级的对象或值。

 1 //CellFormatting中的代码
 2 if ((dgvLinqDemo.Rows[e.RowIndex].DataBoundItem != null) &&
 3     (dgvLinqDemo.Columns[e.ColumnIndex].DataPropertyName.Contains(".")))
 4 {
 5     //对具有点语法的字段进行分割
 6     //比如JobModel.SkillModel.SkillName
 7     //分割成JobModel,SkillModel和SkillName
 8     string[] nameAndProp = dgvLinqDemo.Columns[e.ColumnIndex].DataPropertyName.Split(new char[] { '.' });
 9 
10     object pObj = dgvLinqDemo.Rows[e.RowIndex].DataBoundItem;
11     //i<nameAndProp.Length-1是因为,只需要循环属性名长度-1次就可以了。
12     //比如,对于JobModel.JobName,在上一步中已经获取了JobModel实体
13     //那么在for循环中的代码只需要执行一遍,即i<nameAndProp.Length-1次,即可获取JobName的属性
14     for (int i = 0; i < nameAndProp.Length - 1; i++)
15     {
16         pObj = GetObject(pObj, nameAndProp[i]);
17         //以JobModel.JobName为例,它只在i=0的时候进来执行一次并获取属性值
18         //那么这里就只能为nameAndProp.Length - 2才能顺利获取到属性值
19         if (i == nameAndProp.Length - 2)
20         {
21             //下面代码中的i+1可以保证它获取的是最后的属性值
22             //即:JobModel.JobName的时候取的是JobName的值
23             //或者JobModel.SkillModel.SkillName的时候取得是SkillName的值
24             PropertyInfo objectProperty = pObj.GetType().GetProperty(nameAndProp[i + 1]);
25             e.Value = objectProperty.GetValue(pObj, null).ToString();//取出字段值
26         }
27     }
28 }
29 
30 /// <summary>
31 /// 通过当前对象和子属性名来获取子对象的实例
32 /// 比如传入UserModel对象和"JobModel"字符串来获取JobModel的实例
33 /// </summary>
34 /// <param name="pObj"></param>
35 /// <param name="nameAndProp"></param>
36 /// <returns></returns>
37 private object GetObject(object pObj, string nameAndProp)
38 {
39     if (pObj == null)
40     {
41         return null;
42     }
43     PropertyInfo objProp = pObj.GetType().GetProperty(nameAndProp);
44     return objProp.GetValue(pObj, null);
45 }

 

优化:

讲解讲完了,原理也懂了。

但是你发现如果你有好多个DataGridView,就需要写好多CellFormatting代码。

有一百个DataGridView就要写一百次!这显然太蠢了!

所以我们把这些支持点语法的代码封装成一个新的控件。

这样我们只要直接把自定义的控件拖进Winform界面就可以不写任何一行代码就能直接使用点语法啦!

1.创建类库,就像这样:

2.为DataGridViewPro项目添加引用,就像这样:

3.将自动创建的class1.cs改名为DataGridViewPro.cs,然后代码写成样:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Forms;

namespace DataGridViewPro
{
    public class DataGridViewPro : DataGridView
    {
        public DataGridViewPro()
        {
            InitializeComponent();
        }

        private void InitializeComponent()
        {
            this.CellFormatting += CellFormattingPro;
        }

        private void CellFormattingPro(object sender, DataGridViewCellFormattingEventArgs e)
        {
            if ((this.Rows[e.RowIndex].DataBoundItem != null) &&
                (this.Columns[e.ColumnIndex].DataPropertyName.Contains(".")))
            {
                string[] nameAndProp = this.Columns[e.ColumnIndex].DataPropertyName.Split(new char[] { '.' });
                object pObj = this.Rows[e.RowIndex].DataBoundItem;
                for (int i = 0; i < nameAndProp.Length - 1; i++)
                {
                    pObj = GetObject(pObj, nameAndProp[i]);
                    if (pObj == null)
                    {
                        e.Value = string.Empty;
                        break;
                    }
                    if (i == nameAndProp.Length - 2)
                    {
                        PropertyInfo objectProperty = pObj.GetType().GetProperty(nameAndProp[i + 1]);
                        e.Value = objectProperty.GetValue(pObj, null).ToString();
                    }
                }
            }
        }

        private object GetObject(object pObj, string nameAndProp)
        {
            if (pObj == null)
            {
                return null;
            }
            PropertyInfo objProp = pObj.GetType().GetProperty(nameAndProp);
            return objProp.GetValue(pObj, null);
        }
    }
}

ok,大功告成,把它编译成dll放入你的项目里直接使用带有点语法特性的DataGridViewPro吧!

最后,放上我亲手制作的彩蛋DataGridViewPro.dll,下载引入项目即刻使用!

 

 

参考链接:

WinForm创建自定义控件

为C#自定义控件添加自定义事件

如何重写自定义控件里的事件

posted on 2018-07-13 16:29  chenyangsocool  阅读(1219)  评论(0编辑  收藏  举报