VB.net DataGridView双表头

用3个AI都没搞定.

最后用来园子中博友的代码修改才成功 分享给大家.

Public Class ColumPairDic

    Inherits Dictionary(Of Integer, Dictionary(Of Integer, Integer))

End Class


Friend Class RowPairDic

    Inherits Dictionary(Of Integer, Integer)

End Class


Public Class HeaderInfo

    Public RowCount As Integer
    Public ColCount As Integer
    Public CellNames As String(,)
    Public CellBorders As HeaderCellBorderInfo(,)
    Public Alignments As DataGridViewContentAlignment(,)
    Public CellCrosses As Rectangle(,)
    Public HeaderCellInfoList As List(Of HeaderCellInfo)

    Public Class HeaderCellBorderInfo
        Public Top As Boolean = False
        Public Left As Boolean = False
        Public Right As Boolean = False
        Public Bottom As Boolean = False
    End Class

    Public Sub New(rowCount As Integer, colCount As Integer, headerCellInfoList As List(Of HeaderCellInfo))
        Me.RowCount = rowCount
        Me.ColCount = colCount
        Me.HeaderCellInfoList = headerCellInfoList
        InitHeaderInfo()
    End Sub

    Private Sub InitHeaderInfo()
        CellNames = New String(RowCount - 1, ColCount - 1) {}
        CellBorders = New HeaderCellBorderInfo(RowCount - 1, ColCount - 1) {}
        Alignments = New DataGridViewContentAlignment(RowCount - 1, ColCount - 1) {}
        CellCrosses = New Rectangle(RowCount - 1, ColCount - 1) {}

        For i As Integer = 0 To RowCount - 1
            For j As Integer = 0 To ColCount - 1
                CellNames(i, j) = String.Empty
                CellBorders(i, j) = New HeaderCellBorderInfo()
                Alignments(i, j) = DataGridViewContentAlignment.MiddleCenter
            Next
        Next

        For i As Integer = 0 To RowCount - 1
            CellBorders(i, 0).Left = True
            CellBorders(i, ColCount - 1).Right = True
        Next

        For j As Integer = 0 To ColCount - 1
            CellBorders(0, j).Top = True
            CellBorders(RowCount - 1, j).Bottom = True
        Next

        For Each headerCellInfo As HeaderCellInfo In HeaderCellInfoList
            For i As Integer = headerCellInfo.Row To headerCellInfo.Row + headerCellInfo.RowSpan - 1
                For j As Integer = headerCellInfo.Col To headerCellInfo.Col + headerCellInfo.ColSpan - 1
                    CellNames(i, j) = headerCellInfo.CellName
                    Alignments(i, j) = headerCellInfo.Alignment
                    CellCrosses(i, j) = New Rectangle(headerCellInfo.Col, headerCellInfo.Row, headerCellInfo.ColSpan, headerCellInfo.RowSpan)
                Next
            Next

            For i As Integer = headerCellInfo.Row To headerCellInfo.Row + headerCellInfo.RowSpan - 1
                CellBorders(i, headerCellInfo.Col + headerCellInfo.ColSpan - 1).Right = True
            Next

            For j As Integer = headerCellInfo.Col To headerCellInfo.Col + headerCellInfo.ColSpan - 1
                CellBorders(headerCellInfo.Row + headerCellInfo.RowSpan - 1, j).Bottom = True
            Next
        Next
    End Sub

End Class


Public Class HeaderCellInfo

    Public Row As Integer
    Public Col As Integer
    Public RowSpan As Integer = 1
    Public ColSpan As Integer = 1
    Public CellName As String = String.Empty
    Public Alignment As DataGridViewContentAlignment = DataGridViewContentAlignment.MiddleCenter

    Public Sub New()
    End Sub

    Public Sub New(row As Integer, col As Integer, cellName As String)
        Me.Row = row
        Me.Col = col
        Me.CellName = cellName
    End Sub

    Public Sub New(row As Integer, col As Integer, rowSpan As Integer, colSpan As Integer, cellName As String)
        Me.Row = row
        Me.Col = col
        Me.RowSpan = rowSpan
        Me.ColSpan = colSpan
        Me.CellName = cellName
    End Sub

    Public Sub New(row As Integer, col As Integer, cellName As String, alignment As DataGridViewContentAlignment)
        Me.Row = row
        Me.Col = col
        Me.CellName = cellName
        Me.Alignment = alignment
    End Sub

    Public Sub New(row As Integer, col As Integer, rowSpan As Integer, colSpan As Integer, cellName As String, alignment As DataGridViewContentAlignment)
        Me.Row = row
        Me.Col = col
        Me.RowSpan = rowSpan
        Me.ColSpan = colSpan
        Me.CellName = cellName
        Me.Alignment = alignment
    End Sub

End Class


Public Class HeaderSetting

    Private Sub New()

    End Sub

    Private _rowCount As Integer
    Public Property RowCount() As Integer
        Get
            Return _rowCount
        End Get
        Set(value As Integer)
            _rowCount = value
        End Set
    End Property

    Private _colCount As Integer
    Public Property ColCount() As Integer
        Get
            Return _colCount
        End Get
        Set(value As Integer)
            _colCount = value
        End Set
    End Property

    Private _headerCellInfoList As New List(Of HeaderCellInfo)
    Public Property HeaderCellInfoList() As List(Of HeaderCellInfo)
        Get
            Return _headerCellInfoList
        End Get
        Set(value As List(Of HeaderCellInfo))
            _headerCellInfoList = value
        End Set
    End Property

End Class


Public Class DataGridViewCellMerge
    Inherits DataGridView

    Public Sub New()
        'InitializeComponent()
        Me.RowHeadersVisible = False
        'Me.ColumnHeadersVisible = False
        Me.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells
        Me.AllowUserToAddRows = False
        Me.MultiSelect = False
        Me.SelectionMode = DataGridViewSelectionMode.RowHeaderSelect
    End Sub


    Private columDic As New ColumPairDic()

    Private _headerInfo As HeaderInfo

    Private _headerInfoXmlStr As String
    <Editor("System.ComponentModel.Design.MultilineStringEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", GetType(UITypeEditor))>
    Public Property HeaderInfoXmlStr() As String
        Get
            Return _headerInfoXmlStr
        End Get
        Set(value As String)
            _headerInfoXmlStr = value
            Try
                Using stringReader As New StringReader(_headerInfoXmlStr)
                    Dim xmlSerializer As New XmlSerializer(GetType(HeaderSetting))
                    Dim headerSetting As HeaderSetting = CType(xmlSerializer.Deserialize(stringReader), HeaderSetting)
                    If headerSetting IsNot Nothing Then
                        HeaderInfo = New HeaderInfo(headerSetting.RowCount,
                                                    headerSetting.ColCount,
                                                    headerSetting.HeaderCellInfoList)


                        Me.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing
                        Me.ColumnHeadersHeight = _headerRowHeight * _headerInfo.RowCount

                    End If
                End Using
            Catch
                ' Ignore deserialization errors
            End Try
        End Set
    End Property


    Private _headerRowHeight As Integer = 25
    Public Property HeaderRowHeight As Integer
        Get
            Return _headerRowHeight
        End Get
        Set(value As Integer)
            If value < 1 Then Throw New ArgumentOutOfRangeException(NameOf(HeaderRowHeight), "高度必须大于0")
            _headerRowHeight = value

            If _headerInfo IsNot Nothing Then
                Me.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing
                Me.ColumnHeadersHeight = _headerRowHeight * _headerInfo.RowCount
                Me.Invalidate() 
            End If
        End Set
    End Property


    Public WriteOnly Property MergedRule() As ColumPairDic
        Set(value As ColumPairDic)
            columDic = value
        End Set
    End Property

    Public WriteOnly Property HeaderInfo() As HeaderInfo
        Set(value As HeaderInfo)
            _headerInfo = value
            If _headerInfo IsNot Nothing Then
                Me.ColumnHeadersHeight = _headerRowHeight * _headerInfo.RowCount
            End If
        End Set
    End Property


    Protected Overrides Sub OnLayout(e As LayoutEventArgs)
        MyBase.OnLayout(e)
        If _headerInfo IsNot Nothing Then
            Me.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing
            Me.ColumnHeadersHeight = _headerRowHeight * _headerInfo.RowCount
        End If
    End Sub

    Protected Overrides Sub OnCellPainting(e As DataGridViewCellPaintingEventArgs)
        Try
            If e.RowIndex = -1 Then
                PaintHeader(e)
            Else
                MyBase.OnCellPainting(e) 
            End If
        Catch ex As Exception
            ' Log error if needed, but don't suppress all
            System.Diagnostics.Debug.WriteLine("Painting error: " & ex.Message)
        End Try

    End Sub


    Private Function GetMergedRows(rowDic As RowPairDic) As List(Of Integer)
        Dim rowList As New List(Of Integer)()
        For Each key As Integer In rowDic.Keys
            Dim startRow As Integer = key
            Dim endRow As Integer = rowDic(key)
            For row As Integer = startRow To endRow
                rowList.Add(row)
            Next
        Next
        Return rowList
    End Function

    Private Sub PaintCell(e As DataGridViewCellPaintingEventArgs)
        e.CellStyle.SelectionForeColor = e.CellStyle.ForeColor
        e.CellStyle.SelectionBackColor = e.CellStyle.BackColor

        Dim normalCell As Boolean = True

        If columDic.ContainsKey(e.ColumnIndex) Then
            Dim rowDic As RowPairDic = Nothing
            columDic.TryGetValue(e.ColumnIndex, rowDic)

            If rowDic IsNot Nothing AndAlso rowDic.Count <> 0 Then
                Dim rowList As List(Of Integer) = GetMergedRows(rowDic)

                If rowList.Contains(e.RowIndex) Then
                    normalCell = False
                    e.PaintBackground(e.ClipBounds, False)

                    ' the cell is the first row of the merged cells
                    If rowDic.ContainsKey(e.RowIndex) Then
                        e.PaintContent(e.ClipBounds)
                    End If

                    ' the cell is a merged cell
                    If rowDic.ContainsValue(e.RowIndex) Then
                        ' draw the line under the cell
                        e.Graphics.DrawLine(New Pen(Me.GridColor), e.CellBounds.Left, e.CellBounds.Bottom - 1,
                                                    e.CellBounds.Right - 1, e.CellBounds.Bottom - 1)
                    Else
                        ' clear the line under the cell
                        e.Graphics.DrawLine(New Pen(e.CellStyle.BackColor), e.CellBounds.Left, e.CellBounds.Bottom - 1,
                                                    e.CellBounds.Right - 2, e.CellBounds.Bottom - 1)
                    End If
                End If
            End If
        End If

        If normalCell Then
            e.PaintBackground(e.ClipBounds, False)
            e.PaintContent(e.ClipBounds)
        End If

        e.Handled = True
    End Sub

    Private Sub PaintHeader(e As DataGridViewCellPaintingEventArgs)
        If _headerInfo Is Nothing Then Return

        Dim rowTops(_headerInfo.RowCount - 1) As Integer
        Dim rowBottoms(_headerInfo.RowCount - 1) As Integer

        Dim totalHeight As Integer = Me.ColumnHeadersHeight
        Dim rowHeight As Integer = totalHeight \ _headerInfo.RowCount

        For i As Integer = 0 To _headerInfo.RowCount - 1
            rowTops(i) = e.CellBounds.Top + i * rowHeight
            rowBottoms(i) = rowTops(i) + rowHeight
        Next

        rowBottoms(_headerInfo.RowCount - 1) = e.CellBounds.Bottom

        For i As Integer = 0 To _headerInfo.RowCount - 1
            PaintHeaderCellBackground(e, _headerInfo.CellBorders(i, e.ColumnIndex),
                                              e.CellBounds.Left, rowTops(i),
                                              e.CellBounds.Right, rowBottoms(i))
        Next

        For i As Integer = 0 To _headerInfo.RowCount - 1
            Dim cross As Rectangle = _headerInfo.CellCrosses(i, e.ColumnIndex)
            Dim x2 As Integer = e.CellBounds.Left
            If x2 < 2 Then x2 += 1

            Dim w2 As Integer = 0
            For col As Integer = cross.X To cross.X + cross.Width - 1
                w2 += Me.Columns(col).Width
                If col < e.ColumnIndex Then
                    x2 -= Me.Columns(col).Width
                End If
            Next

            Dim y As Integer = rowTops(cross.Y)
            Dim height As Integer = rowBottoms(cross.Y + cross.Height - 1) - y

            PaintHeaderCellContent(e, _headerInfo.CellNames(i, e.ColumnIndex),
                                           x2, y, w2, height,
                                           _headerInfo.Alignments(i, e.ColumnIndex))
        Next

        e.Handled = True
    End Sub

    Private Sub PaintHeaderCellBackground(e As DataGridViewCellPaintingEventArgs,
                                                  border As HeaderInfo.HeaderCellBorderInfo,
                                                  left As Integer, top As Integer,
                                                  right As Integer, bottom As Integer)
        Dim brushBack As New SolidBrush(Me.ColumnHeadersDefaultCellStyle.BackColor)
        e.Graphics.FillRectangle(brushBack, left, top, right - left, bottom - top)

        right -= 1
        bottom -= 1
        Dim penGrid As New Pen(Me.GridColor)

        If border.Top Then
            e.Graphics.DrawLine(penGrid, left, top, right, top)
        End If
        If border.Left Then
            e.Graphics.DrawLine(penGrid, left, top, left, bottom)
        End If
        If border.Right Then
            e.Graphics.DrawLine(penGrid, right, top, right, bottom)
        End If
        If border.Bottom Then
            e.Graphics.DrawLine(penGrid, left, bottom, right, bottom)
        End If
    End Sub

    Private Sub PaintHeaderCellContent(e As DataGridViewCellPaintingEventArgs,
                                               colName As String,
                                               x As Integer, y As Integer,
                                               width As Integer, height As Integer,
                                               alignment As DataGridViewContentAlignment)
        Dim drawFormat As New StringFormat()
        drawFormat.Alignment = StringAlignment.Center
        drawFormat.LineAlignment = StringAlignment.Center

        If alignment = DataGridViewContentAlignment.MiddleLeft Then
            drawFormat.Alignment = StringAlignment.Near
        End If

        e.Graphics.DrawString(colName,
                                      Me.Font,
                                      New SolidBrush(e.CellStyle.ForeColor),
                                      New RectangleF(x, y, width, height),
                                      drawFormat)
    End Sub

    ''' <summary>
    ''' Unite Cells联合单元格
    ''' </summary>
    ''' <param name="startRow"> start row index </param>
    ''' <param name="endRow"> end row index </param>
    ''' <param name="columnIndex"> columnIndex </param>
    Public Sub UniteCells(startRow As Integer, endRow As Integer, columnIndex As Integer)
        If startRow > endRow Then Return
        Dim rowPairDic As New RowPairDic()
        rowPairDic.Add(startRow, endRow)
        columDic(columnIndex) = rowPairDic
    End Sub

End Class

  

 

posted on 2026-01-28 09:51  boy8199  阅读(4)  评论(0)    收藏  举报