测试环境:windows 7和Microsoft Visual Studio 2015
点击下载本文源码
VB.NET虽然提供了大量控件供我们使用,但很多控件仅提供最基础的功能。比如用DataGridView控件可以非常方便显示或操作数据库数据,我们可以在首列添加DataGridViewCheckBoxColumn列进行全选或全不选操作,但Datagridview控件并没有提供我们平时用的全选或取消全选的复选框,他的表头就只有这一列的名称,这样会影响用户的使用体验。这就需要我们对DataGridViewCheckBoxColumn列的列头进行扩展,创建符合使用要求的自定义控件。
一、自定义控件介绍
VB.NET中的窗体(WinFrom)自定义控件大致有三种形式:
1、组合控件(CompositeControls):继承自UserControl类,将目前现有的控件根据需要组合到一起形成一个新的控件。
2、扩展控件(ExtendedControls):继承自.NET类库中已有的控件,添加一些新的属性和方法来扩展原有控件。
3、自定义控件(CustomControls):继承自Control类,不提供控件特定的功能或图形界面,控件的绘制全部由用户定义。
二、扩展DataGridViewColumnHeaderCell类
可以在DataGridViewCheckBoxColumn列的列头拉一个CheckBox来实现全选全不选,但CheckBox的位置不好调控。最好的方法是扩展DataGridViewColumnHeaderCell类,在列头绘制一个复选框checkbox控件,通过定义checkbox鼠标单击事件来实现行的全选或取消全选。
效果如下图所示:
1、新建一个windows窗体应用程序项目。
为项目添加新项,取名DataGridViewCheckBoxColumnHeaderCellEx,类库。如下图所示整个项目包含一个类库和一个窗体。
2、用下面代码替换类库DataGridViewCheckBoxColumnHeaderCellEx.vb中自动生成的代码。
Option Strict On
Option Infer Off
Option Explicit On
'Imports System
'Imports System.Collections.Generic
'Imports System.Text
'Imports System.Windows.Forms
'Imports System.Drawing
Imports System.Windows.Forms.VisualStyles
Imports System.Runtime.InteropServices
#Region "扩展表头单元格"
Public Class DataGridViewCheckBoxColumnHeaderCellEx
Inherits DataGridViewColumnHeaderCell
' 委托处理DataGridViewCheckBoxClickedHandler事件
Public Delegate Sub DataGridViewCheckBoxClickedHandler(ByVal columnIndex As Integer, ByVal isCheckedAll As Boolean)
Public Event OnCheckBoxClicked As DataGridViewCheckBoxClickedHandler
#Region "属性"
''' <summary>
''' 标识本列是否全选,方便在Cell中获取
''' </summary>
''' <returns></returns>
Public ReadOnly Property IsCheckedAll As Boolean
Get
Return CheckedAllState = CheckState.Checked
End Get
End Property
Private m_checkedAllState As CheckState = CheckState.Unchecked
''' <summary>
''' 全选按钮状态
''' </summary>
''' <returns></returns>
Public Property CheckedAllState As CheckState
Get
Return m_checkedAllState
End Get
Set(ByVal value As CheckState)
m_checkedAllState = value
End Set
End Property
''' <summary>
''' 用于标识当前的checkbox状态
''' </summary>
Protected m_checkboxState As CheckBoxState = CheckBoxState.UncheckedNormal
''' <summary>
''' 是否是鼠标经过的这种hot状态,不是即为normal
''' </summary>
Protected m_isHot As Boolean = False
''' <summary>
''' checkbox按钮区域
''' </summary>
Protected m_chkboxRegion As Rectangle
''' <summary>
''' 相对于本Headercell的位置
''' </summary>
Protected m_absChkboxRegion As Rectangle
#End Region
#Region "重绘"
''' <summary>
''' 在本Headercell绘制CheckBox控件
''' </summary>
''' <param name="graphics"></param>
''' <param name="clipBounds"></param>
''' <param name="cellBounds"></param>
''' <param name="rowIndex"></param>
''' <param name="dataGridViewElementState"></param>
''' <param name="value"></param>
''' <param name="formattedValue"></param>
''' <param name="errorText"></param>
''' <param name="cellStyle"></param>
''' <param name="advancedBorderStyle"></param>
''' <param name="paintParts"></param>
Protected Overloads Overrides Sub Paint(ByVal graphics As Graphics, ByVal clipBounds As Rectangle, ByVal cellBounds As Rectangle,
ByVal rowIndex As Integer, ByVal dataGridViewElementState As DataGridViewElementStates, ByVal value As Object,
ByVal formattedValue As Object, ByVal errorText As String, ByVal cellStyle As DataGridViewCellStyle,
ByVal advancedBorderStyle As DataGridViewAdvancedBorderStyle, ByVal paintParts As DataGridViewPaintParts)
MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, dataGridViewElementState, "", "", errorText, cellStyle, advancedBorderStyle, paintParts)
Me.m_chkboxRegion = GetSmallRectOfRectangle(cellBounds, CheckBoxRenderer.GetGlyphSize(graphics, CheckBoxState.UncheckedNormal), m_absChkboxRegion)
Me.RenderCheckBox(graphics)
End Sub
''' <summary>
''' 按当前的checkbox状态重绘checkbox
''' </summary>
''' <param name="graphics"></param>
Protected Sub RenderCheckBox(ByVal graphics As Graphics)
If m_isHot Then
RenderCheckBoxHover(graphics)
Else
RenderCheckBoxNormal(graphics)
End If
CheckBoxRenderer.DrawCheckBox(graphics, m_chkboxRegion.Location, m_checkboxState)
End Sub
''' <summary>
''' 标识checkbox状态(normal)
''' </summary>
''' <param name="graphics"></param>
Protected Sub RenderCheckBoxNormal(ByVal graphics As Graphics)
Select Case m_checkedAllState
Case CheckState.Unchecked
Me.m_checkboxState = CheckBoxState.UncheckedNormal
Case CheckState.Indeterminate
Me.m_checkboxState = CheckBoxState.MixedNormal
Case CheckState.Checked
Me.m_checkboxState = CheckBoxState.CheckedNormal
End Select
End Sub
''' <summary>
''' 标识checkbox状态(hot)
''' </summary>
''' <param name="graphics"></param>
Protected Sub RenderCheckBoxHover(ByVal graphics As Graphics)
Select Case m_checkedAllState
Case CheckState.Unchecked
Me.m_checkboxState = CheckBoxState.UncheckedHot
Case CheckState.Indeterminate
Me.m_checkboxState = CheckBoxState.MixedHot
Case CheckState.Checked
Me.m_checkboxState = CheckBoxState.CheckedHot
End Select
End Sub
''' <summary>
''' 获取本Headercell的Rectangle及其中的checkbox控件的Rectangle
''' </summary>
''' <param name="rectangle"></param>
''' <param name="smallSize">checkbox控件的Size</param>
''' <param name="absRectangle">checkbox控件的Rectangle</param>
''' <returns></returns>
Protected Shared Function GetSmallRectOfRectangle(ByVal rectangle As Rectangle, ByVal smallSize As Size, <Out> ByRef absRectangle As Rectangle) As Rectangle
Dim rect As Rectangle = New Rectangle()
absRectangle = New Rectangle()
absRectangle.Size = smallSize
absRectangle.X = CType(((rectangle.Width - smallSize.Width) / 2), Integer)
absRectangle.Y = CType((rectangle.Height - smallSize.Height) / 2, Integer)
rect.Size = smallSize
rect.X = absRectangle.X + rectangle.X
rect.Y = absRectangle.Y + rectangle.Y
Return rect
End Function
#End Region
#Region "事件"
Protected Overrides Sub OnMouseMove(ByVal e As DataGridViewCellMouseEventArgs)
MyBase.OnMouseMove(e)
If IsInCheckRegion(e.Location) Then m_isHot = True
Me.DataGridView.InvalidateCell(Me)
End Sub
Protected Overrides Sub OnMouseLeave(ByVal rowIndex As Integer)
MyBase.OnMouseLeave(rowIndex)
m_isHot = False
Me.DataGridView.InvalidateCell(Me)
End Sub
Protected Overrides Sub OnMouseDown(ByVal e As DataGridViewCellMouseEventArgs)
MyBase.OnMouseDown(e)
m_isHot = IsInCheckRegion(e.Location)
Me.DataGridView.InvalidateCell(Me)
End Sub
Protected Overloads Overrides Sub OnMouseClick(ByVal e As DataGridViewCellMouseEventArgs)
Dim value As Boolean = False
If IsInCheckRegion(e.Location) Then
Select Case m_checkedAllState
Case CheckState.Unchecked
m_checkedAllState = CheckState.Checked
value = True
Case CheckState.Indeterminate
m_checkedAllState = CheckState.Checked
value = True
Case CheckState.Checked
m_checkedAllState = CheckState.Unchecked
value = False
End Select
Me.Value = value
'引发事件
RaiseEvent OnCheckBoxClicked(e.ColumnIndex, value)
Me.DataGridView.InvalidateCell(Me)
End If
MyBase.OnMouseClick(e)
End Sub
''' <summary>
''' 是否在checkbox按钮区域
''' </summary>
''' <param name="p"></param>
''' <returns></returns>
Protected Function IsInCheckRegion(ByVal p As Point) As Boolean
Return Me.m_absChkboxRegion.Contains(p)
End Function
#End Region
''' <summary>
''' checkbox列的单元格改变事件
''' </summary>
''' <param name="columnIndex"></param>
Friend Sub OnCheckBoxCellCheckedChange(ByVal columnIndex As Integer)
If columnIndex <> Me.ColumnIndex Then Return
Dim existsChecked As Boolean = False, existsNoChecked As Boolean = False
For Each row As DataGridViewRow In Me.DataGridView.Rows
existsChecked = existsChecked Or CType(row.Cells(columnIndex).EditedFormattedValue, Boolean)
existsNoChecked = existsNoChecked Or Not CType(row.Cells(columnIndex).EditedFormattedValue, Boolean)
Next
Dim oldState As CheckState = Me.CheckedAllState
If existsChecked Then
If existsNoChecked Then
Me.CheckedAllState = CheckState.Indeterminate
Else
Me.CheckedAllState = CheckState.Checked
End If
Else
Me.CheckedAllState = CheckState.Unchecked
End If
If oldState <> Me.CheckedAllState Then Me.DataGridView.InvalidateCell(Me)
End Sub
End Class
#End Region
代码重点解读:
(1)、新DataGridViewCheckBoxColumnHeaderCellEx类,继承自DataGridViewColumnHeaderCell。
Public Class DataGridViewCheckBoxColumnHeaderCellEx
Inherits DataGridViewColumnHeaderCell
End Class
心得:通常组合控件用“用户控件”;;自定义控件用“组件类”或“自定义控件”;;扩展没有单独界面控件用“类”,如本例,DataGridViewColumnHeaderCell类不能单独显示界面,只能在DataGridView控件显示时一同显示,所以扩展该类的DataGridViewCheckBoxColumnHeaderCellEx类只能用“类”;而扩展有单独界面控件需用“组件类”或“自定义控件”。其中奥妙你只要试试就知道了。
(2)、委托和事件
' 委托处理DataGridViewCheckBoxClickedHandler事件
Public Delegate Sub DataGridViewCheckBoxClickedHandler(ByVal columnIndex As Integer, ByVal isCheckedAll As Boolean)
Public Event OnCheckBoxClicked As DataGridViewCheckBoxClickedHandler
在鼠标单击时触发该事件。
Protected Overloads Overrides Sub OnMouseClick(ByVal e As DataGridViewCellMouseEventArgs)
'引发事件
RaiseEvent OnCheckBoxClicked(e.ColumnIndex, value)
End Sub
在窗体引用DataGridViewCheckBoxColumnHeaderCellEx类时订阅事件。
'订阅事件
AddHandler checkbox1.OnCheckBoxClicked, AddressOf Checkbox_OnCheckboxClicked
(3)、一个函数过程返回2个值。如下代码Return rect返回本Headercell的Rectangle,absRectangle返回绘制的checkbox控件的Rectangle。
''' <summary>
''' 获取本Headercell的Rectangle及其中的checkbox控件的Rectangle
''' </summary>
''' <param name="rectangle"></param>
''' <param name="smallSize">checkbox控件的Size</param>
''' <param name="absRectangle">checkbox控件的Rectangle</param>
''' <returns></returns>
Protected Shared Function GetSmallRectOfRectangle(ByVal rectangle As Rectangle, ByVal smallSize As Size, <System.Runtime.InteropServices.Out> ByRef absRectangle As Rectangle) As Rectangle
Dim rect As Rectangle = New Rectangle()
absRectangle = New Rectangle()
absRectangle.Size = smallSize
absRectangle.X = CType(((rectangle.Width - smallSize.Width) / 2), Integer)
absRectangle.Y = CType((rectangle.Height - smallSize.Height) / 2, Integer)
rect.Size = smallSize
rect.X = absRectangle.X + rectangle.X
rect.Y = absRectangle.Y + rectangle.Y
Return rect
End Function
三、调用DataGridViewCheckBoxColumnHeaderCellEx类
用如下代码替换Form1.vb里自动生成的代码。
Public Class Form1
'定义一眉头Checkbox1从类DataGridViewCheckBoxColumnHeaderCellEx构造而来
Private checkbox1 As New DataGridViewCheckBoxColumnHeaderCellEx
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'新列增加一个控件Checkbox1
Me.DataGridView1.Columns(0).HeaderCell = checkbox1
Me.DataGridView1.Rows.Add(5)
Me.DataGridView1.AllowUserToAddRows = False
'订阅事件
AddHandler checkbox1.OnCheckBoxClicked, AddressOf Checkbox_OnCheckboxClicked
End Sub
Private Sub Checkbox_OnCheckboxClicked(ByVal columnIndex As Integer, ByVal isCheckedAll As Boolean)
Me.DataGridView1.EndEdit() '结束编辑操作
For Each Row As DataGridViewRow In Me.DataGridView1.Rows
Row.Cells(columnIndex).Value = isCheckedAll
Next
End Sub
''' <summary>
''' 单击列单元格的内容事件
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
Private Sub DataGridView1_CellContentClick(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellContentClick
Me.DataGridView1.EndEdit() '结束编辑操作
Me.checkbox1.OnCheckBoxCellCheckedChange(e.ColumnIndex)
End Sub
End Class
代码重点解读:
在处理单击列头实现全选或取消全选操作、单击单元格操作时,必须结束单元格的编辑状态,否则无法正确获取修改后的单元格内容,造成处于编辑状态的单元格无法改变勾选状态或在单击单元格时列头的显示与实际不相符。
Me.DataGridView1.EndEdit() '结束编辑操作
在参考的文章里看到还要新建一个datagridviewCheckboxHeaderEventArgs类,继承自EventArgs类,用在在checkbox单击事件中提供类头checkbox的选择状态。我的项目里没有用上,具体有什么作用不详,望能得到指点。
'定义包含列头checkbox选择状态的参数类
Class datagridviewCheckboxHeaderEventArgs
Inherits EventArgs
Private checkedState As Boolean = False
Public Property CheckedState As Boolean
Get
Return checkedState
End Get
Set(ByVal value As Boolean)
checkedState = value
End Set
End Property
End Class
本文参考以下文章:
1、通过绘制在datagridview控件列头添加一个checkbox控件
2、开源DataGridView扩展(1) 扩展支持全选的CheckBox列。
学习过程得到网友uruseibest的帮助,表示感谢!