WinForm编程数据视图之DataGridView浅析
学习C#语言的朋友们肯定或多或少地接触到了WinForm编程,在C#语言的可视化IDE中(如VS.NET中)使用设计器可以让我们轻松地完成窗体、按钮、标签、图片框等等控件的组合,我们可以轻易地做出界面友好的WinForm应用程序。我们还可以通过WinForm程序中强大的事件处理机制来使我们的应用程序更加丰满。
当然,我们这次不谈窗体、不谈按钮……
我们谈谈DataGridView(数据网格视图)。
作为真正与用户进行信息交互的界面,很大程度上需要向用户完整地展示数据,而在WinForm相关的强大控件中就有用于数据显示的。数量很多,但作为列表视图显示的控件,首推ListView和DataGridView。在显示多行多列的数据时,两者往往可以互相替换。
由于笔者认为DataGridView的界面不如ListView友好,所以是尽量使用ListView而非DataGridView的,而最近却喜欢使用DataGridView了,主要原因当属它强大的数据处理。
它可以智能处理绑定到的数据源,而且可作为数据源绑定的类型也非常之多。
DataGridView 类支持标准的 Windows 窗体数据绑定模型。 这意味着数据源可以是实现下列接口之一的任何类型:
IList 接口,包括一维数组。
IListSource 接口,例如,DataTable 和 DataSet 类。
IBindingList 接口,例如,BindingList(Of T) 类。
IBindingListView 接口,例如,BindingSource 类。
详情请参见 http://msdn.microsoft.com/zh-cn/library/system.windows.forms.datagridview.datasource.aspx(DataGridView.DataSource 属性)
对于我们平时的编程来讲,可能将DataTable和List<T>绑定到DataGridView的DataSource是比较多的情况。
但其实我想说的一点是(个人观点,不保证正确,真的):如果是DataTable,最好用DataTable的DataView;如果是IList<T>,最好用BindingList <T>。
首先我们看看DataGridView里的重要属性:
①AutoGenerateColumns 属性(请参见http://msdn.microsoft.com/zh-cn/library/system.windows.forms.datagridview.autogeneratecolumns.aspx)
这个属性十分重要,重要的原因之一:在VS的设计器属性里找不到(不知道为什么它会被加上不出现在属性列表里的特性……)。这个属性的作用很简单也很重要:指定DataGridView是否自动增加列。
如果您希望通过手动添加列指定将要显示到数据网格视图绑定对象的属性,您必须将此属性设置为true;
比如下面的例子:
我希望在数据网格视图内显示自定义对象“员工的信息”:
员工类:
1 using System;
2
3 namespace dgvEG
4 {
5 // 员工类
6 public class SE : ICloneable
7 {
8 // 员工信息
9 public string Name { get; set; }
10 public int Age { get; set; }
11 public int WorkNo { get; set; }
12 public Gender Sex { get; set; }
13 public SignRecord Record { get; set; }
14
15 // 构造方法
16 public SE() { }
17
18 public SE(int workNo, string name, int age, Gender sex)
19 {
20 this.WorkNo = workNo;
21 this.Name = name;
22 this.Age = age;
23 this.Sex = sex;
24 this.Record = new SignRecord();
25 }
表示性别的枚举:
1 using System;
2
3 namespace dgvEG
4 {
5 /// <summary>
6 /// 性别
7 /// </summary>
8 public enum Gender
9 {
10 Male = 0, // 男
11 Female = 1 // 女
12 }
13 }
员工的带卡记录(即签到签退的记录):
1 using System;
2
3 namespace dgvEG
4 {
5 /// <summary>
6 /// 打卡记录
7 /// </summary>
8 public class SignRecord
9 {
10 // 签到时间
11 public DateTime SignInTime { get; set; }
12 // 签退时间
13 public DateTime SignOutTime { get; set; }
14 }
15 }
如果我直接将“员工对象”的集合座作为数据源绑定到DataGridView的DataSource,并且也没有在DataGridView上进行任何过滤的操作,那么结果将是如此:
上图有几点问题,我们一个个地来解决,首先是列名,我们肯定希望是中文的,如此,我们就需要手动指定列名。
如果您使用VS作为开发工具,可以参考我的做法:
这样是不是行了呢?我们再次运行程序,却得到下面的结果:
……估计没人愿意这样,我们再回到绑定数据源的代码:
1 // 定义测试数据
2 SE se1 = new SE(10001, "测试1号", 19, Gender.Female);
3 SE se2 = new SE(10002, "测试2号", 20, Gender.Male);
4 SE se3 = new SE(10003, "测试3号", 21, Gender.Female);
5 SE se4 = new SE(10004, "测试4号", 22, Gender.Male);
6 SE se5 = new SE(10005, "测试5号", 23, Gender.Female);
7 SE se6 = new SE(10006, "测试6号", 24, Gender.Male);
8
9 // 利用测试数据制作数据源
10 List<SE> list = new List<SE>();
11 list.AddRange(new SE[] { se1, se2, se3, se4, se5, se6 });
12
13 // 绑定数据源
14 dataGridView1.DataSource = new BindingList<SE>(list);
看起来没什么问题,这段代码可以放到例如窗体加载事件里正确执行。
从上面的图片不难看出:我们手动添加的列是固有的,只是由于DataGridView对数据源的强大处理,它会自动添加列以正常且完整地显示数据源的信息。
那么我们不让它自动增加列不就OK了?
在绑定数据源之前加上一句代码,如下:
1 // ……
2 // ……
3 // 指定数据网格视图不自动增加列
4 dataGridView1.AutoGenerateColumns = false;
5 // 绑定数据源
6 dataGridView1.DataSource = new BindingList<SE>(list);
不出所料,您看到的结果会是下图:
请您不要急,仔细看看,项数是正确的!说明数据是没问题的,问题在我们希望用自己的方式显示数据,那么DataGridView的自动分析数据就不会执行,我们就必须指定哪一行显示对象的那一个属性。接下的第二个重要属性就是如此的作用:
②DataPropertyName属性(请参见http://msdn.microsoft.com/zh-cn/library/system.windows.forms.datagridviewcolumn.datapropertyname.aspx)
值得注意的是:这个属性是DataGridView的列所拥有的,非DataGridView本身。故名思意,就是用来指定DataGridView中哪一列显示数据源对象的哪一个属性。您可以通过VS的设计器方便地进行这个属性的设置,如下图:
进行了如上图的设置,我们就能得到如下图的正确结果了:
同样的,在将数据表绑定到DataGridView时也可以如此操作。
现在,我们试着增加难度,将员工对应打卡记录的签到信息显示出来。不少会举一反三的朋友可能会进行如下操作:
事实证明,这样是不行的……
这种做法是不被支持的。
另外一个问题其实我们也希望一并解决:性别,我们肯定希望是显示中文的男和女。怎么做呢?其实有一个事件可以做:
③RowsAdded 事件(参见http://msdn.microsoft.com/zh-cn/library/system.windows.forms.datagridview.rowsadded(VS.80).aspx)(不保证有更简易的做法)
这个事件是当当前DataGridView中添加一行或多行数据时发生的。
它的用法如下:
1 // DataGridView.RowsAdded事件使用
2 private void dataGridView1_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
3 {
4 // string str = "";
5 for (int i = e.RowIndex; i < e.RowIndex + e.RowCount; i++)
6 {
7 // str += dataGridView1.Rows[i].Cells[2].Value.ToString() + " ";
8 }
9 // MessageBox.Show(str);
10 }
值得注意的是:该事件并不是向DataGridView中添加一行被执行一次,有可能第一次添加一项,第二次添加三项、或者一项、或者剩余的全部……
而且并不是第一次添加过第二次就不会添加……
总之就是没有规律……
那么我们可以通过从e.RowIndex(当前添加的第一行的索引)到 e.RowIndex + e.RowCount (已经添加项数)的次数的循环,并且通过变化的e.RowIndex的值取得真正被添加的行进行操作。
看看我们如何将Female和Male替换为对应中文的:
1 private void dataGridView1_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
2 {
3 // 如果能通过对象拿到性别,可以采用如下做法显示对应中文
4
5 // 取消对列属性的绑定
6 Column4.DataPropertyName = null;
7
8 for (int i = e.RowIndex; i < e.RowIndex + e.RowCount; i++)
9 {
10 // 通过行(或项)的绑定对象拿到性别
11 dataGridView1.Rows[i].Cells[Column4.Index].Value = // 判断该如何显示,此处的DataBoungItem相当于Tag是在绑定数据源时自动绑定的(当然,它是只读的……)
12 (dataGridView1.Rows[i].DataBoundItem as SE).Sex == Gender.Female ? "女" : "男";
13 }
14 }
善于利用控件的属性和事件会让我们的开发事半功倍!