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窗體應用程式專案。
為專案新增新項,取名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的幫助,表示感謝!