VB.Net,在 .NET Framework 4.8 的 WinForm 下(即不是 WPF 的绘图模式、也不是 Core 或 Mono 的开发框架),使用 DataGridView
行模式,还是有个列头表现为高亮显示:
查找各种解决方式:
ColumnHeadersDefaultCellStyle
———— 无效HeaderCell.Style
———— 无效既然有上述"解决方式",说明早期版本是有效的。至于从哪个版本开始无效,就不深究了,反正碰上了如下解决。
只能在 CellPainting
事件中进行自绘了,顺便实现了列头合并功能(不需要多行列头)。
RowDataGridView
用户控件,集成不需要设计,关掉直接改代码。RowDataGridView.Designer.vb
按注释修改<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class RowDataGridView
Inherits System.Windows.Forms.DataGridView '<- 原先是 UserControl
'UserControl 重写释放以清理组件列表。
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub
'Windows 窗体设计器所必需的
Private components As System.ComponentModel.IContainer
'注意: 以下过程是 Windows 窗体设计器所必需的
'可以使用 Windows 窗体设计器修改它。
'不要使用代码编辑器修改它。
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
components = New System.ComponentModel.Container()
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font '<- 编译错误,删除该行
End Sub
End Class
RowDataGridView.vb
Public Class RowDataGridView
Private m_ColHeadersSpan() As String
''' 列头合并
''' 务必在定义 后修改。合并设置仅按次序、不随列定义同步调整。
''' 每列格式:
'''
''' 数值后的注释仅供参考
''' 数值 1: (默认)非合并列头
''' 数值 0: 被合并列头
''' 其他正整数: 合并开始列头
'''
''' ☆ 数值正确性不检查。
''' ☆ 合并不影响自动列宽计算(即标题可能撑开合并开始列)。
Public Property ColHeadersSpan() As String()
Get
If Me.ColumnCount > 0 Then
Dim lastCount As Integer
If m_ColHeadersSpan Is Nothing Then
lastCount = 0
ReDim m_ColHeadersSpan(Me.ColumnCount)
Else
lastCount = m_ColHeadersSpan.Length
ReDim Preserve m_ColHeadersSpan(Me.ColumnCount)
End If
For i As Integer = 0 To Me.ColumnCount - 1
If i < lastCount Then
m_ColHeadersSpan(i) = $"{Val(m_ColHeadersSpan(i))} '{Me.Columns(i).HeaderText}"
Else
m_ColHeadersSpan(i) = $"1 '{Me.Columns(i).HeaderText}"
End If
Next
End If
Return m_ColHeadersSpan
End Get
Set(value As String())
m_ColHeadersSpan = value
End Set
End Property
Private ReadOnly Property ColHeadersSpanValue(ByVal index As Integer) As Integer
Get
Return Val(m_ColHeadersSpan(index))
End Get
End Property
Private Sub RowDataGridView_CellPainting(sender As Object, e As DataGridViewCellPaintingEventArgs) Handles Me.CellPainting
If (Not Me.DesignMode) And (e.RowIndex = -1) And (e.ColumnIndex <> -1) Then
Debug.Print($"{e.ColumnIndex} : {e.Value}")
Dim colSpan As Integer = Me.ColHeadersSpanValue(e.ColumnIndex)
If colSpan > 0 Then
Dim cellRect As New Rectangle(e.CellBounds.X - 1, e.CellBounds.Y, e.CellBounds.Width, e.CellBounds.Height - 1)
' RowHeadersVisible = False 时最左可见列不需要向左合并网格线
If e.CellBounds.X = 1 Then
cellRect.X = 1
cellRect.Width -= 1
End If
' 添加被合并列的宽度
For i As Integer = 1 To colSpan - 1
cellRect.Width += Me.Columns(e.ColumnIndex + i).Width
Next
Dim foreColorBrash As New SolidBrush(e.CellStyle.ForeColor)
Dim backColorBrush As New SolidBrush(e.CellStyle.BackColor)
Dim gridBrush As New SolidBrush(Me.GridColor)
Dim gridLinePen As New Pen(gridBrush)
Try
e.Graphics.FillRectangle(backColorBrush, cellRect)
e.Graphics.DrawRectangle(gridLinePen, cellRect)
If e.FormattedValue IsNot Nothing Then
Dim format As New StringFormat()
Select Case e.CellStyle.Alignment
Case DataGridViewContentAlignment.BottomLeft, DataGridViewContentAlignment.MiddleLeft, DataGridViewContentAlignment.TopLeft
format.Alignment = StringAlignment.Near
Case DataGridViewContentAlignment.BottomCenter, DataGridViewContentAlignment.MiddleCenter, DataGridViewContentAlignment.TopCenter
format.Alignment = StringAlignment.Center
Case Else
format.Alignment = StringAlignment.Far
End Select
Select Case e.CellStyle.Alignment
Case DataGridViewContentAlignment.BottomCenter, DataGridViewContentAlignment.BottomLeft, DataGridViewContentAlignment.BottomRight
format.LineAlignment = StringAlignment.Center
Case DataGridViewContentAlignment.MiddleCenter, DataGridViewContentAlignment.MiddleLeft, DataGridViewContentAlignment.MiddleRight
format.LineAlignment = StringAlignment.Far
Case Else
format.LineAlignment = StringAlignment.Near
End Select
cellRect.Height += 1 ' 使得垂直居中和非自绘比较一致
e.Graphics.DrawString(CStr(e.FormattedValue), e.CellStyle.Font, foreColorBrash, cellRect, format)
End If
Finally
gridLinePen.Dispose()
gridBrush.Dispose()
backColorBrush.Dispose()
foreColorBrash.Dispose()
End Try
End If
e.Handled = True
End If
End Sub
End Class
注:
New()
统一初始化行模式、是否显示行头等。DataGridView*Column
写继承类。但是仅为了一个属性需要给每种列类型写继承类,不如直接加属性在 DataGridView
上。m_ColHeadersSpan
定义成 Integer
数组,然后属性 ColHeadersSpan
加注释变字符数组方便设计器中编辑;但是 Visual Studio 死活不支持。只能加属性 ColHeadersSpanValue
实时解析,反正之前有列头判断,不会频繁调用。Val()
函数容错性高,直接忽略数值之后的内容;不需要字符串拆分后转类型。对于已设计表格,只需要在 窗体.Designer.vb
中把 DataGridView
替换成 RowDataGridView
即可(注意前缀命名空间)。需要列头合并时设计器中修改 ColHeadersSpan
,比如上例表格设为
1 '机能
2 '键1
0 '值1
2 '键2
0 '值2
2 '键3
0 '值3
1 '加锁者
1 '加锁时间
1 '解锁
如果不需要列头合并,把上面 Span 相关的代码删除,不需要看本章。
那么来看看列头合并在水平滚动时的表现:
各种DataGridView列头合并的例子没有考虑到这种BUG吧
找到了原因,只需要在每个被合并列(Span=0
)也进行重绘就能解决
Private Sub SingleDataGridView_CellPainting(sender As Object, e As DataGridViewCellPaintingEventArgs) Handles Me.CellPainting
If (Not Me.DesignMode) And (e.RowIndex = -1) And (e.ColumnIndex <> -1) Then
Dim cellRect As New Rectangle(e.CellBounds.X - 1, e.CellBounds.Y, e.CellBounds.Width, e.CellBounds.Height - 1)
' RowHeadersVisible = False 时最左可见列不需要向左合并网格线
If e.CellBounds.X = 1 Then
cellRect.X = 1
cellRect.Width -= 1
End If
' 修正水平滚动时合并列标题半可见时的显示问题
Dim startColIndex As Integer = e.ColumnIndex
While Me.ColHeadersSpanValue(startColIndex) <= 0
startColIndex -= 1
cellRect.X -= Me.Columns(startColIndex).Width
cellRect.Width += Me.Columns(startColIndex).Width
End While
Dim colSpan As Integer = Me.ColHeadersSpanValue(startColIndex)
' 添加被合并列的宽度(计算startColIndex时已经加了一部分)
For i As Integer = startColIndex + 1 To startColIndex + colSpan - 1
If i > e.ColumnIndex Then
cellRect.Width += Me.Columns(i).Width
End If
Next
Dim foreColorBrash As New SolidBrush(e.CellStyle.ForeColor)
Dim backColorBrush As New SolidBrush(e.CellStyle.BackColor)
Dim gridBrush As New SolidBrush(Me.GridColor)
Dim gridLinePen As New Pen(gridBrush)
Try
e.Graphics.FillRectangle(backColorBrush, cellRect)
e.Graphics.DrawRectangle(gridLinePen, cellRect)
If e.FormattedValue IsNot Nothing Then
Dim format As New StringFormat()
Select Case e.CellStyle.Alignment
Case DataGridViewContentAlignment.BottomLeft, DataGridViewContentAlignment.MiddleLeft, DataGridViewContentAlignment.TopLeft
format.Alignment = StringAlignment.Near
Case DataGridViewContentAlignment.BottomCenter, DataGridViewContentAlignment.MiddleCenter, DataGridViewContentAlignment.TopCenter
format.Alignment = StringAlignment.Center
Case Else
format.Alignment = StringAlignment.Far
End Select
Select Case e.CellStyle.Alignment
Case DataGridViewContentAlignment.BottomCenter, DataGridViewContentAlignment.BottomLeft, DataGridViewContentAlignment.BottomRight
format.LineAlignment = StringAlignment.Center
Case DataGridViewContentAlignment.MiddleCenter, DataGridViewContentAlignment.MiddleLeft, DataGridViewContentAlignment.MiddleRight
format.LineAlignment = StringAlignment.Far
Case Else
format.LineAlignment = StringAlignment.Near
End Select
cellRect.Height += 1 ' 使得垂直居中和非自绘比较一致
e.Graphics.DrawString(CStr(e.FormattedValue), e.CellStyle.Font, foreColorBrash, cellRect, format)
End If
Finally
gridLinePen.Dispose()
gridBrush.Dispose()
backColorBrush.Dispose()
foreColorBrash.Dispose()
End Try
e.Handled = True
End If
End Sub
这其实是对象继承用Inherits
方式而不是Implements
方式带来的先天缺陷。Inherits
在实现继承时很爽(其实就是少写代码而已),但是父类一旦有变动所有继承类的行为会变化。这其实要求基类不变才能保证兼容性;想想有了DataGrid
还要来个DataGridView
,就是因为无法兼容;再看看 .NET Framework 从 1.1 到 4.8.1 那么多的版本,就是做不到低版本程序兼容高版本 Framework。
Implements
方式有不同的接口,按特定接口调用时和其他特性无关。它所谓的缺陷
Protected
G
的时代太无聊了。实现方法增加了一些exe大小;M
时代。CSDN 赶紧把 MarkDown 编辑器的维护人员拖出去鞭笞,不兼容 MarkDown 语法规则:
)。*
变成(
、输入(
变成)
。。。