1 何为DataGridView
通过DataGridView控件,可以显示和编辑表格式的数据,而这些数据可以取自多种不同类型的数据源。
DataGridView控件具有很高的的可配置性和可扩展性,提供了大量的属性、方法和事件,可以用来对该控件的外观和行为进行自定义。当你需要在WinForm应用程序中显示表格式数据时,可以优先考虑DataGridView(相比于DataGrid等其它控件)。如果你要在小型网格中显示只读数据,或者允许用户编辑数以百万计的记录,DataGridView将为你提供一个易于编程和良好性能的解决方案。
DataGridView 用来替换先前版本中的DataGrid,拥有较DataGrid更多的功能;但DataGrid仍然得到保留,以备向后兼容和将来使用。如果你要在两者中选择,可以参考下面给出的DataGrid 和DataGridView之间区别的细节信息。
1.1 DataGridView和DataGrid 之间的区别
DataGridView提供了大量的DataGrid所不具备的基本功能和高级功能。此外,DataGridView 的结构使得它较之DataGrid控件更容易扩展和自定义。
下表描述了DataGridView提供而DataGrid未提供的几个主要功能。
DataGridView功能 | 描述 |
多种列类型 |
与DataGrid相比,DataGridView 提供了更多的内置列类型。这些列类型能够满足大部分常见需要,而且比DataGrid中的列类型易于扩展或替换。
|
多种数据显示方式 | DataGrid仅限于显示外部数据源的数据。而DataGridView则能够显示非绑定的数据,绑定的数据源,或者同时显示绑定和非绑定的数据。你也可以在DataGridView中实现virtual mode,实现自定义的数据管理。
|
用于自定义数据显示的多种方式 | DataGridView提供了很多属性和事件,用于数据的格式化和显示。比如,你可以根据单元格、行和列的内容改变其外观,或者使用一种类型的数据替代另一种类型的数据。
|
用于更改单元格、行、列、表头外观和行为的多个选项 | DataGridView使你能够以多种方式操作单个网格组件。比如,你可以冻结行和列,避免它们因滚动而不可见;隐藏行、列、表头;改变行、列、表头尺寸的调整方式;为单个的单元格、行和列提供工具提示(ToolTip)和快捷菜单。
|
唯一的一个DataGrid提供而DataGridView未提供的特性是两个相关表中数据的分层次显示(比如常见的主从表显示)。你必须使用两个DataGridView来显示具有主从关系的两个表的数据。
1.2 DataGridView的亮点
下表着重显示了DataGridView的主要特性,稍后会介绍它们的详细信息。
DataGridView控件特性 | 描述 |
多种列类型 | DataGridView提供有TextBox、CheckBox、Image、Button、ComboBox和Link类型的列及相应的单元格类型。
|
多种数据显示方式 | DataGrid仅限于显示外部数据源的数据。而DataGridView则能够显示非绑定的数据,绑定的数据源,或者同时显示绑定和非绑定的数据。你也可以在DataGridView中实现virtual mode,实现自定义的数据管理。
|
自定义数据的显示和操作的多种方式 | DataGridView提供了很多属性和事件,用于数据的格式化和显示。 此外,DataGridView提供了操作数据的多种方式,比如,你可以: § 对数据排序,并显示相应的排序符号(带方向的箭头表示升降序) § 对行、列和单元格的多种选择模式;多项选择和单项选择 § 以多种格式将数据拷贝到剪贴板,包括text,CSV (以逗号隔开的值) 和 HTML § 改变用户编辑单元格内容的方式
|
用于更改单元格、行、列、表头外观和行为的多个选项 | DataGridView使你能够以多种方式操作单个网格组件。比如,你可以: § 冻结行和列,避免它们因滚动而不可见; § 隐藏行、列、表头; § 改变行、列、表头尺寸的调整方式; § 改变用户对行、列、单元格的选择模式; § 为单个的单元格、行和列提供工具提示(ToolTip)和快捷菜单。 § 自定义单元格、行和列的边框样式。
|
提供丰富的可扩展性的支持 | DataGridView提供易于对网格进行扩展和自定义的基础结构,比如: § 处理自定义的绘制事件可以为单元格、列和行提供自定义的观感; § 继承一个内置的单元格类型以为其提供更多的行为; § 实现自定义的接口以提供新的编辑体验。 |
2 DataGridView的结构
DataGridView及其相关类被设计为用于显示和编辑表格数据式数据的灵活的、可扩展的体系。这些类都位于system.Windows.Forms命名空间,它们的名称也都有共同的前缀"DataGridView"。
2.1 结构元素(Architecture Elements)
主要的DataGridView相关类继承自DataGridViewElement类。
DataGridViewElement类有两个属性,一是DataGridView,该属性提供了对其所属的DataGridView的引用;二是State,该属性表示当前的状态,其值为DataGridViewElementStates枚举,该枚举支持位运算,这意味着可以设置组合状态。
2.2 单元格和组(Cells and Bands)
DataGridView由两种基本的对象组成:单元格(cell)和组(band)。所有的单元格都继承自DataGridViewCell基类。 两种类型的组(或称集合)DataGridViewColumn和DataGridViewRow都继承自DataGridViewBand 基类,表示一组结合在一起的单元格。
DataGridView会与一些类进行互操作,但最常打交道的则是如下三个:DataGridViewCell, DataGridViewColumn,DataGridViewRow。
2.3 DataGridView的单元格 (DataGridViewCell)
单元格(cell)是操作DataGridView的基本单位。Display is centered on cells, and data entry is often performed through cells。可以通过DataGridViewRow 类的Cells 集合属性访问一行包含的单元格,通过DataGridView的SelectedCells集合属性访问当前选中的单元格,通过DataGridView的CurrentCell属性访问当前的单元格。
DataGridViewCell 类图
| Cell 相关类和属性
|
DataGridViewCell是一个抽象基类,所有的单元格类型都继承于此。DataGridViewCell及其继承类型并不是Windows Forms控件,但其中一些宿主于Windows Forms控件。单元格支持的编辑功能通常都由其宿主控件来处理。
DataGridViewCell对象不会像Windows Forms控件那样控制自己的外观和绘制(painting)特征,相反的,DataGridView会负责其包含的单元格的外观。通过DataGridView 控件的属性和事件,你可以深刻地影响单元格的外观和行为。如果你对单元格定制有特殊要求,超出了DataGridView提供的功能,可以继承DataGridViewCell或者它的某个子类来满足这些要求。
2.3.1 DataGridViewCell的工作机制
理解DataGridView结构的一个重要部分是理解DataGridViewCell的工作机制:
单元格的值(A Cell’s Value)
单元格的值是其根本所在。如果单元格所在列不是绑定列,并且所在的DataGridView也不是Virtual Mode,那么它的值就由它本身所持有并维护。对于那些由绑定产生的单元格,它们压根儿就不“知道”该持有什么值,当然也就不会去维护了;当DataGridView需要单元格的值的时候,它会到数据源中查询该单元格应当显示的值。在Virtual Mode下,除了会触发CellValueNeeded事件以获取相应单元格的值外,与数据绑定方式非常相似。在单元格级,所有这些由DataGridViewCell.GetValue() 方法来控制。
默认情况下,单元格的值的类型为object。当一个列被绑定后,会设置它的ValueType属性,它包含的单元格的ValueType也随之更新。而单元格的ValueType对于下一步的格式化非常重要。
格式化显示(Formatting for Display)
注意:当DataGridView需要了解“如何显示这个单元格”时,它需要的是单元格的FormattedValue ,而不是Value。这是一个复杂的过程,因为格式化屏幕上的一些内容通常需要将它转换为字符串。例如,尽管你将单元格的值(Value)设置为整型值155,在显示它的时候仍需要将其格式化。单元格和其所在的列的FormattedValueType 属性决定了显示它时所用的类型。多数列使用字符串类型,而Image和CheckBox类型的单元格/列则使用其它类型。Image类型的单元格和列使用Image作为默认的FormattedValueType,它的内置实现了解如何去显示一个Image。CheckBox类型的单元格/列的FormattedValueType属性则取决于属性ThreeState的值。在单元格级,所有这些由DataGridViewCell.GetFormattedValue()控制。
默认情况下,DataGridView使用TypeConverter将单元格的值(Value)转换为格式化的值(FormattedValue)。DataGridView会基于单元格的ValueType和FormattedValueType属性来获取合时的TypeConverter。
对于一个单元格,FormattedValue会得到多次请求(即会在多个地方用到):绘制单元格的时候,所在列根据单元格内容自动调整大小的时候,甚至是在判断鼠标是否经过单元格内容时。每次需要FormattedValue的时候,DataGridView会触发CellFormatting事件,这时你就有机会修改单元格的格式化显示了。
如果单元格不能获取它的格式化值,它会触发DataError事件。
格式化显示单元格还包含以怎样的首选尺寸显示它。这个首选尺寸是由单元格的FormattedValue,填充区域(padding),附加显示和边框合并而成。
绘制单元格的显示(Painting the Display)
在获得FormattedValue 后,单元格将负责绘制它的内容。单元格决定了绘制过程所使用的正确样式(参见本文档第五章的样式部分)并进行绘制。记住:如果单元格不去绘制自己,那么该单元格将不会有任何内容得到绘制(即单元格的绘制只由它自己负责),行、列不会负责绘制任何内容,因此要确保至少要绘制单元格的背景(background),否则单元格所在的矩形区域仍然是无效的(即未经绘制)。
解析单元格的显示(Parsing the Display)
用户开始与单元格交互后,可能会编辑单元格的值。有一件事要记住,用户编辑的实际上是单元格的FormattedValue。用户提交所编辑的值时,FormattedValue需要转换回单元格的值(Value),这个过程称为解析(parsing)。在单元格级上,所有这些工作由单元格的DataGridViewCell.ParseFormattedValue(int rowIndex)方法控制。
默认情况下,会再次使用TypeConverter来将FormattedValue解析为单元格的真实值,这时会触发DataGridView的CellParsing事件,这时你就有机会修改单元格的解析方式了。.
如果单元格不能得到正确地解析,会触发DataError事件。
2.4 DataGridView的列(DataGridViewColumn)
DataGridView所附带的数据(这些数据可以通过绑定或非绑定方式附加到控件)的结构表现为DataGridView的列。你可以使用DataGridView的Columns集合属性访问DataGridView所包含的列,使用SelectedColumns 集合属性访问当前选中的列。
DataGridViewColumn 类图
| Column 相关类和属性
|
一些主要的单元格类型拥有相应的列类型,这些列类型继承自DataGridViewColumn基类。
2.5 DataGridView的编辑控件(Editing Controls)
支持高级编辑功能的单元格一般都使用一个继承自Windows Forms控件的宿主控件,这些控件同时也实现了IDataGridViewEditingControl接口。
DataGridView Editing Control Class diagram
| Classes that implement Editing Controls
|
下表说明了单元格类型、列类型、编辑控件间的关系:
单元格类型 | 宿主控件 | 列类型 |
DataGridViewButtonCell | n/a | DataGridViewButtonColumn |
DataGridViewCheckBoxCell | n/a | DataGridViewCheckBoxColumn |
DataGridViewComboBoxCell | DataGridViewComboBoxEditingControl | DataGridViewComboBoxColumn |
DataGridViewImageCell | n/a | DataGridViewImageColumn |
DataGridViewLinkCell | n/a | DataGridViewLinkColumn |
DataGridViewTextBoxCell | DataGridViewTextBoxEditingControl | DataGridViewTextBoxColumn |
2.6 DataGridViewRow
DataGridViewRow类用于显示数据源的一行数据。可以通过DataGridView控件的Rows集合属性来访问其包含的行,通过SelectedRows集合属性访问当前选中的行。
DataGridViewRow类图
| Row相关的类和属性
|
你可以继承DataGridViewRow类来实现自己的行类型,虽然多数情况下这并不必要。DataGridView 有几个行相关的事件和属性,用以自定义其包含的DataGridViewRow对象的行为。
如果你将DataGridView的AllowUserToAddRows属性设为true,一个专用于添加新行的特殊行会出现在最后一行的位置上,这一行也属于Rows集合,但它有一些需要你提起注意的特殊功能,要获得这方面的更多信息。
3 列/单元格类型揭密(column/cell types)
DataGridView控件提供了几种列类型用以显示数据,并允许用户修改和添加数据。
当你对DataGridView进行了绑定,并将它的AutoGenerateColumns属性设置为true,它会根据数据源中列的数据类型自动生成列,这些列都使用相应的默认类型(与数据源列数据类型相适应)。
你也可以自行创建列的实例,将它们加入DataGridView的Columns集合中,这些列可用作非绑定列,也可以以手动方式让它们用于绑定数据。手动绑定的列非常有用,比如,自动生成的列都采用与数据源的列相应的默认类型,而你不想用默认列类型。
下表描述了DataGridView 的各种列对应的类:
列类型 | 描述 |
DataGridViewTextBoxColumn | 用于基于文本的值。绑定到数字和字符串值时会自动生成这种类型的列。 |
DataGridViewCheckBoxColumn | 用于显示Boolean和CheckState类型的值,绑定到上述类型值时会自动生成这种类型的列。 |
DataGridViewImageColumn | 用于显示图像。绑定到byte数组,Image对象,图标对象时会自动生成这种类型的列。 |
DataGridViewButtonColumn | 用于在单元格内显示按钮。在绑定时不会自动生成,一般用于非绑定列。 |
DataGridViewComboBoxColumn | 用于在单元格内显示下拉列表。在绑定时不会自动生成,一般地需要手工绑定。 |
DataGridViewLinkColumn | 用于在单元格内显示链接。在绑定时不会自动生成,一般地需要手工绑定。 |
自定义列类型 | 通过继承DataGridViewColumn 类或其子类,你可以创建自己的列类型,以提供自定义的外观、行为和宿主控件。 |
3.1 DataGridViewTextBoxColumn
DataGridViewTextBoxColumn是一种通用的列类型,用于表示基于文本的值,比如数字和字符串。在编辑模式下,会有一个TextBox控件出现在当前活动单元格,用户可以修改单元格的值。
单元格的值在显示时会自动转换为字符串。用户输入或修改的值在提交时则被自动解析为合适的数据类型以创建一个单元格的值。通过处理CellFoamatting和CellParsing事件,你可以自定义这些转换的方式。比如将数据源的日期字段以特定的形式显示,对某些特殊单元格作出特殊的标记。
对一列来说,它包含的单元格值的数据类型由该列的ValueType属性指定。
3.2 DataGridViewCheckBoxColumn
DataGridViewCheckBoxColumn用于显示Boolean或CheckState类型的值。Boolean 值显示为二元(two-state)或三元 (three-state) 的CheckBox,而这取决于该列的ThreeState 属性的值。如果该类型的列绑定到CheckState类型的值,ThreeState属性的默认值为true。
一般情况下,CheckBox类型的单元格要么用于存储数据,就像其它类型的数据一样,要么用于进行一些重要操作。用户点击CheckBox单元格时,如果你希望对此立即做出反应,可以处理CellClick事件,但该事件发生在单元格的值更新之前。如果点击之时就希望获得新值,一种选择是根据当前值计算点击后的值;另一种方法是立即提交值的变化,然后在CellValueChanged事件处理函数中对此作出反应,而要在用户点击单元格时立即提交值的变化,你必须处理CurrentCellDirtyStateChanged事件,在这里,调用CommitEnd方法提交新值。
3.3 DataGridViewImageColumn
DataGridViewImageColumn 类型的列用于显示图像。这种类型的列有三种方法生成:绑定到数据源时自动生成;为非绑定列手动生成;在CellFormatting事件处理函数(该事件发生在单元格显示前)中动态生成。
绑定到数据源时自动生成Image列的方法适用于大量的图像格式,包括.NET中Image类支持的各种格式,还有Access数据库及Northwind范例数据库使用的OLE图片格式。
如果你想提供DataGridViewButtonColumn列的功能,又希望显示自定义的外观,手动生成Image列会很有用。在显示后,你可以处理CellClick事件以处理用户对单元格的点击(模拟按钮列)。
如果你要为计算值或非图片的值提供图片显示,在CellFormatting事件处理函数中动态生成Image列的方法会很有用。比如,你有一个表示风险值的列,它的值可能是”high”、”middle”或”low”,可以为它们显示不同的图标作为警示;或者你有一个名为”Image”的列,它的值时图片文件的位置而不是真实的图片内容,也可以用这种方法。
3.4 DataGridViewButtonColumn
使用DataGridViewButtonColumn 列,可以在单元格内显示按钮。如果你要为用户操作特定行提供一种简单的方式,Button列会很有用,比如排序或在另一个窗体中显示子表记录。
在对DataGridView进行数据绑定时不会自动生成Button列,所以你必须手动创建它们,然后把它们添加到DataGridView控件的Columns集合中。
你可以处理CellClick事件以响应用户的点击动作。
3.5 DataGridViewComboBoxColumn
在DataGridViewComboBoxColumn类型的列中,你可以显示包含下拉列表的单元格。这在仅允许用户输入一些特定值的时候显得很有用,比如在SQL Server示例数据库Northwind中Products表的Category列,它表示产品的种类,这个应只允许选择现有的产品种类,此时就可以使用ComboBox列。
如果你了解如何为ComboBox控件生成下拉列表,就可以用相同的方式为ComboBox列中的所有单元格生成下拉列表。要么通过列的Items集合手动添加,要么通过DataSource,DisplayMember 和ValueMember属性绑定到一个数据源。要了解其中的更多信息,可以参考WinForms中ComboBox空间的用法。
你可以将ComboBox列的单元格的实际值绑定到DataGridView控件本身的数据源(注意不是ComboBox列的数据源),这需要设置该列的DataPropertyName属性(设置某个列的名称)。
ComboBox列不会在数据绑定时自动生成,所以你必须手动创建它们,然后将其添加到Columns集合属性中。另外,你也可以使用设计器,在设计时设置相应的属性,这个过程类似于在设计器中ComboBox控件的使用。
3.5.1 DataError事件和ComboBox列
在使用DataGridViewComboBoxColumn 时,有时会修改单元格的值或启动ComboBox控件的Items集合,这样可能会引发DataError事件。这是ComboBox列的设计使然,ComboBox列的单元格会进行数据验证。在ComboBox列的单元格尝试绘制包含的内容时,它需要将包含的值进行格式化(见第二章第三节),在此转换过程中,它会在ComboBox的Items集合中查找对应的值,如果查找失败,就会引发DataError事件。忽略了DataError事件可能会使单元格不能进行正确的格式化。
3.6 DataGridViewLinkColumn
使用DataGridViewLinkColumn列,你可以显示一列包含超链接的单元格。在显示数据源中的URL值,或者替代按钮列进行一些特殊行为,如打开另一个子记录窗体时会很有用。
Link列也不会在DataGridView数据绑定时自动生成。要使用它,你还得手动创建,然后将它添加到DataGridView控件的Columns集合中。
你可以处理CellContentClick事件来相应用户的点击动作。这个事件不同于CellClick 和CellMouseClick 事件,后两者在用户点击单元格任何位置(而不仅仅时链接)时都会触发。
DataGridViewLinkColumn 类提供了几个属性,用来修改链接的外观,包括点击前,点击时和点击后(类似于网页中的超链接)。
4 操作数据(Working with Data)
多数情况下,使用DataGridView的时候都需要跟数据打交道,这时有很多事情可能需要你去做。你需要验证用户输入的数据,或者需要对数据进行格式化。DataGridView能够以三种模式显示数据:bound、unboundand 和virtual。每种模式都有自己的特性和存在的理由。不管是否是数据绑定模式,在操作数据时,如果发生错误,DataGridView通常会触发DataError事件,理解该事件发生的原因能让你更好地利用它。
4.1 数据输入和验证的相关事件
用户输入数据时-对其所在的行或单元格,你可能希望验证这些数据,在遇到无效数据时通知用户。就像常见的Windows Forms控件,DataGridView的行和单元格也有Validating和Validated事件,验证事件可被取消。用户在单元格/行间移动时会触发Enter和Leave事件。最后,用户在开始编辑单元格时也会触发事件。了解所有这些程序的发生顺序会对你很有帮助。
4.1.1 数据验证相关事件的顺序
下面列出validation,enter/leave和begin/end这些事件的顺序(当EditMode为EditOnEnter时):
当从一个单元格移动至另一单元格(在同一行内):
1) Cell Leave (原来的单元格)
2) Cell Validating/ed (原来的单元格)
3) Cell EndEdit (原来的单元格)
4) Cell Enter (新的单元格)
5) Cell BeginEdit (新的单元格)
当从一行移动到另一行:
1) Cell Leave (原来的单元格),Row leave (原来的行)
2) Cell Validating/ed (原来的单元格)
3) Cell EndEdit (原来的单元格)
4) Row Validating/ed (原来的行)
5) Row Enter (新的行)
6) Cell Enter (新的单元格)
7) Cell BeginEdit (新的单元格)
4.1.2 验证数据
验证用户输入时,如果DataGridView采用非数据绑定模式,通常会对单元格进行验证;而如果采用数据绑定模式,则一般会对行进行验证。这与数据的组织方式密切相关,非数据绑定模式下,一行的单元格间关系一般比较“散”,而绑定模式下,数据源的数据一般以行来组织。但有时在数据绑定模式下会同时进行单元格级和行级的验证。
4.1.2.1 显示错误信息
一旦遭遇了无效的输入数据,你通常需要通知用户。这时有多种方式可以选择,传统的方式是使用信息对话框。DataGridView还能够为行或单元格显示一个错误图标来通知用户输入了无效数据。错误图标带有一个工具提示,它提供了该错误的相关信息:
4.1.2.2 常见问题(FAQ)
4.1.3 在新行中的数据输入(Data Entry in the New Row)
当在程序中使用DataGridView来编辑数据时,你往往希望提供让用户添加新行数据的功能。DataGridView控件支持这个功能,提供了一个用于添加新记录的行,而这一行总是显示为最后一行,并在该行的标题单元格标以星号(*)。 下面的几个小节会讨论一些在程序中使用这个新行时需要考虑的内容。(下面总是以 新行 表示 用于添加新记录的行 )
4.1.3.1 显示新行
使用AllowUserToAddRows属性以指示是否显示新行,其默认值为true。
新行处于网格的最后一行,标题带有星号:
在数据绑定的情况下,当DataGridView控件的AllowUserToAddRows属性和数据源的IBindingList.AllowNew 属性都为true时,新行才会显示,只要两者有一个为false,新行就不会显示。
4.1.3.2 为生成的新行添加默认值
当用户选择新行作为当前行,DataGridView会触发DefaultValuesNeeded事件。在该事件中可以访问新行,并为其生成默认值,为用户输入提供方便。
下面这段代码演示了如何在DefaultValuesNeeded事件中为新行指定默认值。
private void dataGridView1_DefaultValuesNeeded(object sender,
DataGridViewRowEventArgs e)
{
e.Row.Cells["Region"].Value = "WA";
e.Row.Cells["City"].Value = "Redmond";
e.Row.Cells["PostalCode"].Value = "98052-6399";
e.Row.Cells["Region"].Value = "NA";
e.Row.Cells["Country"].Value = "USA";
e.Row.Cells["CustomerID"].Value = NewCustomerId();
}
4.1.3.3 Rows集合与新行的关系
新行包含在DataGridView控件的Rows集合中,又因其总是处于最后一行,下面这行代码会返回新行:
DataGridViewRow row = dataGridView1.Rows[dataGridView1.Rows.Count - 1];
尽管新行也包含在Rows集合中,它与Rows集合中其它行的行为却不相同,表现在两点:
- 不能以编程的方式将新行从Rows集合中移除,如果你尝试这么做,会抛出InvalidOperationException类型的异常。用户也不能删除新行。DataGridViewRowCollection.Clear()方法也不能将新行从Rows集合中移除。
- 不能在新行之后添加行。如果你尝试这么做,会抛出InvalidOperationException 类型的异常。这种特性的结果是,新行总处于DataGridView的最后一行。当新行显示的时候,DataGridViewRowCollection 类中用于添加行的方法-Add,AddCopy以及AddCopies-在内部都调用用于插入的方法。
4.1.3.4 在新行中输入数据
用户开始在新行输入数据之前,新行的IsNewRow属性值为true;一旦用户开始输入,这一行就不再是新行了,DataGridView中会产生一个“新”的新行,看下面示意图:
在添加“新”的新行时,会触发UserAddedRow事件,它的事件处理函数的第二个参数有属性Row,指定了这个“新”的新行。如果用户此时按下Escape键,“新”的新行会被移除,这会触发UserDeletingRow事件,它的事件处理函数的第二个参数的属性Row指定了“新”的新行。
4.1.3.5 自定义新行的可视化效果
新行是基于RowTemplate模板创建的,如果没有指定它的单元格的样式,它们会采用继承的样式。要了解样式继承的更多信息,请参看第五章第一节的内容。
新行中单元格的初始值是由每个单元格的DefaultNewRowValue属性决定的。对于DataGridViewImageCell类型的单元格,其初始值为一个占位图片,其它类型的则为null。你可以重写这个属性以返回自定义值。但也可以在DefaultValuesNeeded事件处理函数中对默认值进行替换,该事件在焦点进入新行时触发。
新行标题的标准图标是箭头或者星号,并没有得到暴露。如果你要自定义这个图标,就需要创建一个自定义的DataGridViewRowHeaderCell 类。
新行的标题的标准图标使用标题单元格DataGridViewCellStyle的ForeColor属性。注意:如果没有足够的空间,图标就不会再显示。
如果为标题单元格设置了字符串值(通过Value属性),但没有足够的控件同时显示文本和图标,那么图标会被首先截掉。
4.1.3.6 新行的排序
在非绑定模式下,新行总是添加在DataGridView的最后一行,即使已经对数据排序。用户需要在添加新行后再次进行排序,以将新记录放在合适的位置;这种行为方式类似于ListView控件。
在绑定模式或虚拟模式(Virtual Mode)下,如果已对数据排序,那么插入数据时的行为取决于数据模型的实现方式。对于ADO.NET,新加的行会被自动排序至合适的位置。
4.1.3.7 关于新行,还要注意:
你不能将新行的Visible属性值设置为false,否则会触发一个InvalidOperationException类型的异常。
新行在创建时总是处于非选中(unselected)状态。
4.1.3.8 Virtual Mode下的新行
如果你正要实现虚拟模式(Virtual Mode),需要考虑数据模型添加新行和回滚添加操作的情况。该功能准确的实现方式取决于数据模型的实现方式及其事务机制,例如,提交的时候是针对单元格还是行。参看本文档后面关于Virtual Mode的主题。
4.2 关于Null值
在使用数据源的时候,比如数据库或业务对象,经常需要处理null值。null值可能是一个实际的null(VB中为Nothing),也可能是一个数据库的”null”值(DBNull.Value),当你遭遇了这些值,就需要考虑如何显示它们。另一方面,很多时候,你还需要向数据源写入null值。使用单元格Style的NullValue属性和DataSourceNullValue 属性,你可以改变DataGridView处理null值的方式。
4.2.1 NullValue属性
DataGridViewCellStyle.NullValue 属性本来要被命名为FormattedNullValue 的,但是后来没来得及作出这个更改。但它能给我们带来一点提示——顾名思义,在格式化时会用到它。如果一个单元格的值为”null”(等于null或DBNull.Value),它会使用你设置的NullValue属性来显示。该属性的默认值取决于所在列的类型,见下图:
DataGridView列类型 | 列的DefaultCellStyle.NullValue值 |
TextBoxColumn | String.Empty (“”) |
ImageColumn | 空的图像( ) |
ComboBoxColumn | String.Empty (“”) |
ButtonColumn | String.Empty (“”) |
LinkColumn | String.Empty (“”) |
CheckBoxColumn | 默认值取决于ThreeState属性的值,如果为true,默认值为CheckState.Indeterminate ,否则为unchecked。 |
有一点要了解,在用户输入数据时也会用到NullValue。例如,若用户向TextBox类型单元格输入了string.Empty,那么会将null作为该单元格的值。 查看下面的DataSourceNullValue属性以了解究竟是输入了什么作为单元格的值。
4.2.2 DataSourceNullValue属性
DataGridViewCellStyle.DataSourceNullValue属性要被命名为ParseNullValue的,如果NullValue属性被命名为FormattedNullValue的话,但最后还是采用了DataSourceNullValue,这样更直观准确。在将null值写入单元格的值时,就会用到DataSourceNullValue属性。在数据绑定情形下,这个null值将被写入数据库或业务对象,此处需要进行控制,因为对于数据库和业务对象来说,null的概念不尽相同。通常你会期望,使用业务对象时将DataSourceNullValue 设置为null,而使用数据库时则将其设置为DBNullValue。DataSourceNullValue的默认值为DBNull.Value。
4.3 DataError事件
将DataError事件独立出来作为一个主题,是因为在操作数据时,经常会遭遇DataError事件。在操作数据时,DataError主要发生在一下情况:不能读/写或转换单元格的数据;在尝试进行某种编辑操作时发生了异常。
编辑操作中的DataError 事件
下面的列表列出了可能会引发DataError事件的编辑操作:
§ 取消编辑(Canceling an edit) | § 刷新一个编辑 (通过调用RefreshEdit方法) |
| § 尝试将单元格的值写入数据源 |
| § 初始化编辑控件/单元格的值(通过设置单元格的FormattedValue属性或调用单元格的InitializeEditingControl方法)
|
|
|
DataError的上下文:
下面的列表显示了不同的DataError上下文环境,然后进一步说明了这些上下文环境合适可能发生:
DataErrorContext | 何时发生 |
Formatting | When attempting to retrieve the cell's formatted value. |
Display | When attempting to paint the cell or calculate the cell's tooltiptext. Note that these operations usually also require getting the cell's formatted value, so the error context is OR'd together. |
PreferredSize | When calculating the preferred size of a cell. This |
RowDeletion | Any exception raised when deleting a row. |
Parsing | When exceptions occur when committing, ending or canceling an edit. Usually OR'd in with other error contexts |
Commit | When exceptions occur when committing an edit. Usually OR'd with other error contexts |
InitialValueRestoration | When exceptions occur while either initializing the editing control/cell's value, or Canceling an edit |
LeaveControl | When exceptions occur while attempting to validate grid data when the grid is losing focus. Usually OR'd with other error contexts. |
CurrentCellChange | When exceptions occur while validating/updating/committing/getting cell content when the current cell changes. Usually OR'd with other error contexts. |
Scroll | When exceptions occur while validating/updating/committing/getting cell content when the current cell changes as a result of scrolling. |
ClipboardContent | When exceptions occur while attempting to get the formatted value of a cell while creating the clipboard content. |
4.4 数据绑定模式(Databound modes)
4.4.1 非绑定模式(Unbound Mode)
如果你要在程序中管理数量相对较小的数据,那么非绑定模式会比较合适。此时你不是像绑定模式中那样将DataGridView控件直接指向一个数据源,而是手动去生成控件。一般需要用到DataGridViewRowCollection.Add 方法(该方法向DGV中添加行)。
非绑定模式在处理静态、只读的数据时特别有用,也可以用在以自己的方式与外部数据源交互的情况,但实际上,如果你希望你的用户与外部的数据源交互,一般还是用绑定模式(bound mode)更好。
4.4.2 绑定模式(Bound Mode)
如果你在程序中管理一些数据,并希望能与数据源自动进行交互,就应该使用绑定模式。此时你可以设置DataSource属性,将数据源绑定到DataGridView控件。如果控件使用了绑定模式,就不需要你去显式地对数据进行读写了。如果AutoGenerateColumns 属性为true,数据源中的每一列都会在DataGridView中生成一个相应的列(根据列的数据类型),如果你希望创建自己的列,可以将该属性设置为false,使用DataPropertyName属性将一列绑定到数据源的一列,这在你不想用自动生成的列类型时很有用。
4.4.2.1 有效的数据源
将数据绑定到DataGridView非常简单、直观,很多情况下,你只需要设置它的DataSource属性。如果使用的数据源包含多个列表(list)或数据表(table),你还需要设置控件的DataMember属性,该属性为字符串类型,用于指定要绑定的列表或数据表。
DataGridView控件支持标准的WinForm数据绑定模型,因此它可以绑定到下面列表中的类的实例:
· 任意实现了IList接口的类,包括一维数组;
· 任意实现了IListSource接口的类,比如DataTable和DataSet;
· 任意实现了IBindingList 接口的类,比如BindingList ;
· 任意实现了IBindingListView接口的类,比如BindingSource 。
列表更改通知(List Change Notification)
当你将数据绑定到列表时,最重要的功能之一便是支持列表更改通知了。这只有在你希望列表(即数据源)发生变化,如添加、修改和删除,DataGridView能够随之更新的时候,该功能才显得重要。只有实现了IBindingList接口的数据源支持更改通知。像数组和集合这样的列表默认情况下不支持更改通知。
在选择数据源时,BindingSource组件应该作为首选,因为它可以绑定到多种类型的数据源,并且能够自动处理很多数据绑定相关的事务。一般情况下,应该将DataGridView绑定到BindingSource组件,并将BindingSource组件绑定真正的数据源(它的作用就像DGV和数据源间的桥梁)。 BindingList<T>类也可以在一个类的基础上创建自定义列表(list)。
对象更改通知(Object Change Notification)
如果你有了一个数据源,那么数据源中的对象就可以实现对public属性的更改通知。这需要你为相应属性提供一个” PropertyNameChanged”事件,或者实现INotifyPropertyChanged接口。INotifyPropertyChanged 是在VS 2005 中新加的接口,可以与BindingList<T>一起使用来创建可绑定的列表(list)。但当你的数据源是BindingSource ,那就不用再额外实现更改通知了。
4.4.3 虚拟模式
使用虚拟模式,你可以实现自己的数据管理操作。在绑定模式下,如果要使用非绑定列,那么要想在对列排序时能够维护非绑定列的值,就需要虚拟模式。但虚拟模式的最主要的用途还是在操作大量数据时优化性能。
你将DataGridView绑定到缓存的数据,然后用代码控制数据行的存取。要保持使用内存量比较小,缓存的数据量应与当前要显示的行数相当。当用户滚动控件看到了新的行时,你的代码就从缓存中请求新的数据,并从内存中清除旧的数据。
如果你正要实现虚拟模式(Virtual Mode),需要考虑数据模型添加新行和回滚添加操作的情况。该功能准确的实现方式取决于数据模型的实现方式及其事务机制,例如,提交的时候是针对单元格还是行。参看本文档后面关于Virtual Mode的主题。
4.4.4 混合模式 – 绑定与非绑定模式
显示在DataGridView中的数据通常来自于某种类型的数据源,但是你可能也希望显示一个数据源之外的列。这种列称为非绑定列。
你可以在绑定模式下添加非绑定列,在你希望显示一个按钮列或者链接列让用户操作一些特定行时这显得很有用,另外也可以用非绑定列显示一些由绑定列计算而得到的值。你可以在CellFormatting事件处理函数中生成计算列的值。不过如果你使用的数据源是DataSet或DataTable,你可能希望使用DataColumn.Expression 属性来创建一个计算列,在这种情况下,在DGV看来,这一列就跟数据源中其它列是一样的。
在绑定模式下根据非绑定列排序是不受支持的。如果你在绑定模式下创建了非绑定列,你必须实现虚拟模式,这样在根据绑定列排序时可以维护非绑定列的值。