VB.NET学习笔记:自定义控件之扩展DataGridViewColumnHeaderCell类增加CheckBox全选复选框

测试环境: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窗体应用程序项目。
新建项目-windows窗体应用程序
为项目添加新项,取名DataGridViewCheckBoxColumnHeaderCellEx,类库。如下图所示整个项目包含一个类库和一个窗体。
项目包含2项
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的帮助,表示感谢!

猜你喜欢

转载自blog.csdn.net/zyjq52uys/article/details/85272588