新的 DataGridView 是 NET 中的一个新控件是针对 NET x 中功能较差的标准 DataGrid 控件而设计的Matthew MacDonald 在本文中论述了许多改进包括DataGridView 支持大量自定义和细致的格式设置灵活的大小调整和选择更好的性能以及更丰富的事件模型
net Framework 的前两个版本(NET 和 NET )在数据绑定方面留下了明显的空白尽管开发人员拥有一个灵活的可配置模型可以将几乎所有的控件链接到几乎所有的数据源但却没有一种有效的方法来显示完整的信息表可以实现此目的的唯一工具就是 DataGrid 控件这种控件非常适合于处理简单的演示但是不太适合实际代码
填补这项空白是 NET 的主要目标之一并且 Microsoft 已经用全新的网格控件 DataGridView 来实现此目标DataGridView 有两个指导原则首先其目标是支持常见任务(如主控/详细列表验证和数据格式设置)而不需要您编写许多代码更重要的是设计过程中始终考虑了扩展性因此您可以集成所需的专用功能而不必采用低级别的复杂编程
基本数据绑定
熟悉 DataGridView 的最佳方法就是实际尝试一下无需配置任何属性就像 DataGrid 一样您可以使用 DataSource 属性来绑定 DataTable 对象(或从 DataTable 派生的对象)
Dim ds As DataSet = GetDataSet()DataGridViewDataSource = dsTables(Customers)
与 DataGrid 不同的是DataGridView 一次只能显示一个表如果绑定整个 DataSet则不会显示任何数据除非您使用要显示的表名设置了 DataMember 属性
DataGridViewDataSource = dsDataGridViewDataMember = Customers
基本的 DataGridView 显示遵循以下几项简单的规则
?
为数据源中的每个字段创建一列
?
使用字段名称创建列标题列标题是固定的这意味着用户在列表中向下移动时列标题不会滚动出视图
?
支持 Windows XP 视觉样式您会注意到列标题具有新式的平面外观并且当用户将鼠标移到其上时会突出显示
datagridview 还包括几个您可能不会立即注意到的默认行为
?
允许就地编辑用户可以在单元格中双击或按 F 来修改当前值唯一的例外是将 DataColumnReadOnly 设置为 True 的字段(如当前示例中的 OrderID 字段)
?
支持自动排序用户可以在列标题中单击一次或两次基于该字段中的值按升序或降序对值进行排序默认情况下排序时会考虑数据类型并按字母或数字顺序进行排序字母顺序区分大小写
?
允许不同类型的选择用户可以通过单击并拖动来突出显示一个单元格多个单元格或多个行单击 DataGridView 左上角的方块可以选择整个表
?
支持自动调整大小功能用户可以在标题之间的列分隔符上双击使左边的列自动按照单元格的内容展开或收缩
美化 DataGridView
datagridview 的默认外观仅仅比 DataGrid 略有改进但是使用几项快速调整功能您可以将其显着改进
其中的一个问题就是列无法自动展开以适合其包含的数据您可以使用 DataGridViewAutoSizeColumns() 方法以及 DataGridViewAutoSizeColumnCriteria 枚举中的某个值来解决此问题您可以选择根据标题文本当前显示的行或表中的所有行的的宽度来调整列宽
根据标题或此列的某一行中 最长一段文本的宽度调整 列宽DataGridViewAutoSizeColumns( _DataGridViewAutoSizeColumnCriteriaHeaderAndRows)
请记住此方法必须在绑定数据后调用否则不会产生任何效果你可能还需要在用户编辑数据后使用它(可能在响应 DataGridViewCellValueChanged 等事件时)
如果不增加列宽则可以更改行高默认情况下列中的文本会跨越多行如果您使用 DataGridViewAutoSizeRows() 方法则行会根据其中的内容调整高度使用此方法前您可能希望增加列宽尤其是在字段包含大量文本时例如以下代码片段使说明列的列宽增加为原列宽的四倍然后调整行高以容纳其内容
DataGridViewColumns(Description)Width *= DataGridViewAutoSizeRows( _DataGridViewAutoSizeRowsModeHeaderAndColumnsAllRows)
图 比较了自动调整 DataGridView 大小的各种方法
另一个合理的更改是清理每一列中显示的标题文本例如标题order Date比字段名称OrderDate看上去更为专业这项更改很容易进行您只需从 DataGridViewColumns 集合中检索相应的 DataGridViewColumn并修改其 HeaderText 属性
DataGridViewColumns(OrderID)HeaderText = Order ID
使用 DataGridView 选择单元格
默认情况下datagridview 允许自由选择用户可以突出显示单元格或单元格组可以一次突出显示所有单元格(通过单击网格右上角的方块)还可以突出显示一行或多行(通过在行标题列中单击)根据选择模式用户甚至能够通过选择列标题来选择一列或多列通过使用 DataGridViewSelectionMode 枚举中的某个值来设置 DataGridViewSelectionMode 属性可以控制此行为如下所述
?
CellSelect用户可以选择单元格但不能选择整个行或标题如果 DataGridViewMultiSelect 为 True则用户可以选择多个单元格
?
FullColumnSelect单击列标题只能选择整个列如果 DataGridViewMultiSelect 为 True则用户可以选择多个列使用此模式时单击列标题不会对网格进行排序
?
FullRowSelect单击行标题只能选择整个行如果 DataGridViewMultiSelect 为 True则用户可以选择多个行
?
ColumnHeaderSelect用户可以使用 CellSelect 或 FullColumnSelect 选择模式使用此模式时单击列标题不会对网格进行排序
?
RowHeaderSelect用户可以使用 CellSelect 或 FullRowSelect 选择模式这是默认的选择模式
通过 DataGridView可以使用以下三个属性方便地检索选定的单元格SelectedCellsSelectedRows 和 SelectedColumns无论使用的是哪种选择模式SelectedCells 都始终返回 DataGridViewCell 对象的集合另一方面如果使用行标题选择了整个行则 SelectedRows 只返回信息而如果使用列标题选择了整个列则 SelectedColumns 也只返回信息
例如以下代码片段将检查选定的整个行只要找到一行它就会在消息框中显示 CustomerID 列中的相应值
For Each SelectedRow As DataGridViewRow In _DataGridViewSelectedRowsMessageBoxShow( _SelectedRowCells(CustomerID)Value)Next
使用 CurrentCell 或 CurrentCellAddress 属性检索对当前单元格的引用也同样简单使用 DataGridView 时您会注意到当前单元格被一个矩形围住看起来像是一个用黑色虚线绘制的方框这就是用户当前所在的位置
currentcelladdress 属性是只读的但是您可以使用 CurrentCell 以编程方式更改当前位置完成此操作后DataGridView 将被滚动以使当前位置可见
移至第十一行的第四个单元格DataGridViewCurrentCell = _DataGridViewRows()Cells()
DataGridView 对象
到目前为止您已经了解了如何与当前选定的一组行单元格和列进行交互datagridview 提供了两个关键集合用于处理整个数据集这两个集合分别是 Columns(DataGridViewColumn 对象的集合)和 Rows(DataGridViewRow 对象的集合每个对象都引用一组 DataGridViewCell 对象的集合)图 显示了对象模型
一般而言您将使用 DataGridViewColumn 对象来配置列显示属性格式设置及标题文本而使用 DataGridViewRow 和 DataGridViewCell 对象从绑定的记录中检索实际数据在 DataGridViewCell 中修改数据时处理方式与用户编辑的方式相同引发相应的 DataGridView 更改事件并且修改底层的 DataTable
现在您已经了解了 DataGridView 对象模型因此可以轻松地创建遍历该表的代码要选择行列或单元格只需找到对应的 DataGridViewRowDataGridViewColumn 或 DataGridViewCell 对象并将 IsSelected 属性设置为 True
以下示例以编程方式选择 OrderID 字段的值小于 的所有行
For Each Row As DataGridViewRow In DataGridViewRowsIf RowCells(OrderID)Value < ThenRowSelected = TrueEnd IfNext
值得一提的是有几个从 DataGridViewColumn 派生的不同的类这些类可以控制在单元格中显示和编辑值的方式NET 包括五个预先创建的 DataGridView 列类DataGridViewButtonColumnDataGridViewCheckBoxColumnDataGridViewComboBoxColumnDataGridViewImageColumn 和 DataGridViewTextBoxColumn
DataGridView 样式
设计 DataGridView 时面临的挑战之一就是创建一个格式设置系统该系统要能够足够灵活地应用不同级别的格式设置而对于非常大的表又要保持高效从灵活性角度来看最好的方法是允许开发人员分别配置每个单元格但是从效率角度来看这种方法可能是有害的包含数千行的表中具有好几万个单元格维护每个单元格的不同格式肯定会浪费很多内存
为解决此问题datagridview 通过 DataGridViewCellStyle 对象来实现多层模型DataGridViewCellStyle 对象表示单元格的样式并且包括如颜色字体对齐换行和数据格式等详细信息您可以创建一个 DataGridViewCellStyle 来指定整个表的默认格式此外还可以指定列行和各个单元格的默认格式格式设置的越细致创建的 DataGridViewCellStyle 对象越多该解决方案的可伸缩性也就越小但是如果您主要使用基于列和基于行的格式设置并且只是偶尔设置各个单元格的格式则与 DataGrid 相比DataGridView 不需要太多内存
datagridview 应用格式设置时将遵循以下优先顺序(从最高到最低)
DataGridViewCellStyle DataGridViewRowDefaultCellStyle DataGridViewAlternatingRowsDefaultCellStyle DataGridViewRowsDefaultCellStyle DataGridViewColumnDefaultCellStyle DataGridViewDefaultCellStyle
重要的是要清楚样式对象不是以全有/全无的方式应用的datagridview 会检查每个属性例如假设您的单元格使用 DataGridViewCellStyle 属性来应用自定义字体但没有设置自定义前景色结果该字体设置将覆盖任何其他样式对象中的字体信息但如果层次结构中下一个样式对象的前景色不为空则将从该对象继承前景色(在这种情况下为 DataGridViewRowDefaultCellStyle)
datagridviewcellstyle 定义了两种格式设置数据和外观数据格式设置描述显示数据绑定值之前如何对其进行修改这种格式设置通常包括使用格式设置字符串将数字或日期值转换为文本要使用数据格式设置只需使用 DataGridViewCellStyleFormat 属性设置格式定义或自定义格式字符串即可
例如以下代码片段对 UnitCost 字段中的所有数字进行格式设置以将它们显示为货币值保留两位小数并加上在区域设置中定义的相应货币符号
DataGridViewColumns(UnitCost) _DefaultCellStyleFormat = C
外观格式设置包括颜色和格式等表面细节例如以下代码右对齐 UnitCost 字段应用粗体并将单元格的背景更改为黄色
DataGridViewColumns(UnitCost) _DefaultCellStyleFont = _New Font(DataGridViewFont FontStyleBold)DataGridViewColumns(UnitCost) _DefaultCellStyleAlignment = _DataGridViewContentAlignmentMiddleRightDataGridViewColumns(UnitCost) _DefaultCellStyleBackColor = ColorLightYellow
其他与格式设置相关的属性包括 ForeColorSelectionForeColorSelectionBackColorWrapMode(控制文本在空间允许时是跨越多行还是直接截断)及 NullValue(将替代 Null 值的值)
datagridview 还包括一个设计器用于在设计时配置列样式只需从Properties(属性)窗口中选择DataGridView Properties(DataGridView 属性)链接或者从各种预先创建的样式设置中选择AutoFormat(自动套用格式)
自定义单元格格式
单元格格式设置的第一种方法是设置更高级别的 DataGridViewDataGridViewColumn 和 DataGridViewRow 属性但是有时您需要为特定单元格单独设置样式例如您可能需要在列中的数据大于或小于某个值时标记该列中的数据例如突出显示项目计划列表中已过去的到期日期或者在销售分析中突出显示负收益率在这两种情况下您需要对单独的单元格进行格式设置
了解 DataGridView 对象模型后您可能想要遍历特定列中的单元格集合以寻找要突出显示的值这种方法是可行的但不是最好的方法关键问题是如果用户编辑了数据或者如果代码更改了绑定的数据源不会对单元格的突出显示情况进行相应的更新
幸运的是datagridview 针对此目的提供了 CellFormatting 事件CellFormatting 只在显示单元格值之前引发通过该事件可以基于单元格的内容来更新单元格样式以下示例检查特定的客户并相应地标记单元格
Private Sub DataGridView_CellFormatting( _ByVal sender As SystemObject _ByVal e As SystemWindowsForms _DataGridViewCellFormattingEventArgs) _Handles DataGridViewCellFormatting 检查该列是否正确If DataGridViewColumns(eColumnIndex)Name = _CustomerID Then 检查该值是否正确If eValue = ALFKI TheneCellStyleForeColor = ColorRedeCellStyleBackColor = ColorYellowEnd IfEnd IfEnd Sub
样式不是影响网格外观的唯一细节您还可以隐藏列在不同位置之间移动列并冻结列使这些列在用户滚动到右端时仍然可见这些功能都是通过 DataGridViewColumn 类的属性提供的如下所述
?
DisplayIndex设置列在 DataGridView 中显示的位置例如DisplayIndex 值为 的列将自动显示在最左边的列中如果多个列具有相同的 DisplayIndex则先显示最先出现在该集合中的列因此如果您使用 DisplayIndex 将一列向左移动则可能还需要设置最左边的列的 DisplayIndex以将其向右移动最初DisplayIndex 与 DataGridViewColumns 集合中 DataGridViewColumn 对象的索引相匹配
?
Frozen如果为 True则该列将始终可见并且固定在表的左侧即使用户为查看其他列而滚动到右侧亦如此
?
HeaderText设置将在列标题中显示的文本
?
Resizable 和 minimumwidth将 Resizable 设置为 False以防止用户调整列宽或者将 MinimumWidth 设置为允许的最小像素数目
?
Visible要隐藏列请将此属性设置为 False
按钮列
datagridview 提供的一种列是 DataGridViewButtonColumn这种列在每一项旁边显示一个按钮您可以响应此按钮的单击事件并使用它启动其他操作或显示新的表单
以下示例使用按钮文字details创建简单的按钮列
创建按钮列Dim Details As New DataGridViewButtonColumn()DetailsName = Details 关闭数据绑定并显示静态文本(但是您可以通过设置 DataPropertyName 属性来使用该表中的属性)DetailsDisplayTextAsFormattedValue = FalseDetailsText = Details 清除标题DetailsHeaderText = 添加该列DataGridViewColumnsInsert( _DataGridViewColumnsCount Details)
图 显示了包含新列的网格以下代码会对任何行中的按钮单击事件做出反应并显示相应的记录信息
Private Sub DataGridView_CellClick( _ByVal sender As SystemObject _ByVal e As SystemWindowsForms _DataGridViewCellEventArgs) _Handles DataGridViewCellClickIf DataGridViewColumns(eColumnIndex)Name = _Details ThenMessageBoxShow(You picked & _DataGridViewRows(eRowIndex) _Cells(CustomerID)Value)End IfEnd Sub
比较现实的方案是在此时创建并显示一个新窗口并将有关选定记录的信息传递到这个新窗口以便查询并显示完整的信息
图像列
datagridview 提供的另一种列是 DataGridViewImageColumn这种列将在单元格边框内显示一个图片您可以设置 DataGridViewImageColumnLayout 属性以便配置图片在单元格中的显示方式是将单元格扩展到适当的大小还是将直接剪裁太大的图像
使用 DataGridViewImageColumn 的方法有两种首先您可以采用与 DataGridViewButtonColumn 相同的方式手动创建并添加该列如果您需要显示 DataSet 本身不提供的相关图像信息则此列非常有用例如您可能需要获取文件名(如 ProductPicgif)从网络驱动器读取相应的文件然后在网格中显示该文件为完成此操作您需要对 DataGridView 事件(如 CellFormatting)做出反应以便读取相应行的图片获取图像数据并使用该列中的 Value 属性将其插入
如果 DataSet 包含不需要任何手动操作的二进制图片数据那么事情会变得更加简单例如 SQL Server Pubs 数据库中的 pub_info 表该表包含公司徽标绑定至此表时您不需要执行任何额外步骤DataGridView 将自动识别出您正在使用图像并会创建所需的 DataGridViewImageColumn(有关这项技术的示例请参阅本文的下载内容)
编辑 DataGridView
众所周知datagrid 的用户输入功能很不灵活您几乎没有办法自定义单元格验证方式及错误报告方式而另一方面DataGridView 允许您通过对在编辑过程的所有阶段中引发的大量不同事件做出反应来控制其行为
默认情况下当用户用鼠标双击单元格或按 F 键时DataGridView 单元格将进入编辑模式您还可以通过将 DataGridViewEditCellOnEnter 属性设置为 True对 DataGridView 进行配置以便当用户移到该单元格后该单元格立即切换到编辑模式您还可以使用 DataGridView 的 BeginEdit()CancelEdit()CommitEdit() 和 EndEdit() 方法通过编程方式开始和停止单元格编辑用户编辑单元格时行标题将显示一个铅笔状的编辑图标
用户可以通过按 Esc 键来取消编辑如果将 EditCellOnEnter 属性设置为 True则单元格仍将处于编辑模式但是所有更改都将被放弃要提交更改用户只需移到新的单元格或将焦点切换到其他控件如果您的代码可以移动当前单元格的位置则此代码也会提交更改
为防止单元格被编辑可以设置 DataGridViewCellDataGridViewColumnDataGridViewRow 或 DataGridView 的 ReadOnly 属性(取决于您是要防止对该单元格进行更改对该列中的所有单元格进行更改对该行中的所有单元格进行更改还是要防止对该表中的所有单元格进行更改)DataGridView 还提供了 CellBeginEdit 事件用于取消尝试的编辑
处理错误
默认情况下datagridviewtextboxcolumn 允许用户输入任何字符包括当前单元格中可能不允许使用的那些字符例如用户可以在数字字段中键入非数字字符也可以指定与 DataSet 中定义的 ForeignKeyConstraint 或 UniqueConstraint 沖突的值DataGridView 采用不同的方式来处理这些问题
?
如果可以将编辑的值转换为所需的数据类型(例如用户在数字列中键入了文本)则用户将不能提交更改或导航到其他行而必须取消更改或编辑值
?
如果编辑的值与 DataSet 中的约束沖突则用户通过导航到其他行或按 Enter 键提交更改后更改将被立即取消
这些处理方式适用于大多数情况但是如果需要您也可以通过响应 DataGridViewDataError 事件来参与错误处理该事件是在 DataGridView 侦听到来自数据源的错误时引发的
验证输入
验证是一项与错误处理稍有不同的任务通过错误处理您可以处理由 DataSet 报告的问题而通过验证您可以捕获您自己定义的错误情况例如 DataSet 中允许的数据在应用程序中却没有意义
当用户通过导航到新的单元格提交更改时datagridview 控件将引发 CellValidating 和 CellValidated 事件这些事件之后是 RowValidating 和 RowValidated 事件您可以响应这些事件检查用户输入的值是否正确并执行所需的任何后期处理如果值无效通常您会通过两种方式来做出响应即取消更改和单元格导航(通过将 EventArgs 对象的 Cancel 属性设置为 True)或者设置某种错误文本来提醒用户可以将错误文本置于其他控件中也可以使用相应的 DataGridViewRow 和 DataGridViewCell 的 ErrorText 属性在 DataGrid 中显示错误文本
?
设置 DataGridViewCellErrorText 时单元格中将显示感歎号图标将鼠标悬停在此图标上将显示错误消息
?
设置 DataGridViewRowErrorText 时行左侧的行标题中将显示感歎号图标将鼠标悬停在此图标上将显示错误消息
通常您会结合使用这两种属性并设置行和单元格中的错误消息以下示例检查 CompanyName 字段中太长的文本输入如果发现有问题的值则会将错误符号(红色的感歎号)添加到单元格中并显示描述该问题的工具提示文本
Private Sub DataGridView_CellValidating( _ByVal sender As SystemObject _ByVal e As SystemWindowsForms _DataGridViewCellValidatingEventArgs) _Handles DataGridViewCellValidatingIf DataGridViewColumns(eColumnIndex)Name = _CompanyName ThenIf CType(eFormattedValue String)Length > _ ThenDataGridViewRows( _eRowIndex)Cells(eColumnIndex) _ErrorText = _The company name is too longEnd IfEnd IfEnd Sub
使用列表列约束选择
使用验证可以捕获任何错误情况但是这种方法不一定是最好的因为它允许用户输入无效的内容然后在事实出现后尝试改正无效输入更好的解决方案是使用户在一开始就无法输入任何无效的内容
一个常见例子就是您需要将列限制在预定义值列表的范围内在此示例中对于用户而言最简单的办法是从列表中选择正确的值而不要手动键入值最大的优势在于您可以使用 DataGridViewComboBoxColumn 非常方便地实现此设计
可以使用 Items 集合手动为 DataGridViewComboBoxColumn 添加项列表就像使用 ListBox 一样此外还可以将 DataGridViewComboBoxColumn 绑定到其他数据源在这种情况下您可以使用 DataSource 属性来指定数据源并使用 DisplayMember 属性指示列中应显示的值以及使用 ValueMember 属性指定底层列值应使用的值
为了演示这种情况来看看下一个有关 Products 表的示例此表中的每一条记录都通过其 CategoryID 字段链接至 Categories 表中的记录要更改产品类别用户需要记住正确的 ID并将其输入到 CategoryID 字段中更好的解决方案是使用与 Categories 表绑定的 DataGridViewComboBoxColumn此列将使用 CategoryName 作为显示值但是会将 CategoryID 作为真正的底层值此列仍将通过 DataPropertyName 属性与 Products 表绑定在一起这意味着当用户从列表中选择新的 Category 时产品记录的 CategoryID 字段将自动更改
以下是配置此表所需的代码
删除自动生成的 CategoryID 列DataGridViewColumnsRemove(CategoryID) 为 CategoryID 创建列表列Dim List As New DataGridViewComboBoxColumn()ListDisplayIndex = ListHeaderText = Category 此列绑定至 ProductsCategoryID 字段ListDataPropertyName = CategoryID 该列表将从 Categories 表获得数据ListDataSource = dsTables(Categories)ListDisplayMember = CategoryNameListValueMember = CategoryID 添加该列DataGridViewColumnsAdd(List)
小结
本文概要介绍了最值得期待的 NET 新控件之一DataGridView与 DataGrid 不同的是DataGridView 适用于各种不同的现实情况——无论是要处理数据绑定和用户编辑还是仅涉及静态文本显示都可以采用 DataGridView简而言之本文让您近距离了解了 NET Framework 提供的一体化数据解决方案以及 Windows 窗体开发人员升级至 NET 的最引人注目的原因之一
有关 Visual Studio NET Developer 和 Pinnacle Publishing 的详细信息请访问它们的 Web 站点
注意这不是 Microsoft Corporation 的 Web 站点Microsoft 对其内容不承担任何责任
本文转载自 年 月份的 Visual Studio NET Developer除非另有说明否则版权所有 Pinnacle Publishing Inc保留所有权利Visual Studio NET Developer 是 Pinnacle Publishing Inc 独立发行的刊物未经 Pinnacle Publishing Inc 事先同意不得以任何形式使用或复制本文的任何部分(评论文章中的简短引用除外)如需与 Pinnacle Publishing Inc 联系请致电