VB.NET桌面应用:如何在DataGridView表头下方添加筛选文本框行
我刚好折腾过这个需求,给你两个亲测好用的VB.NET WinForms实现方案,完美解决你说的“隐藏首行数据”的问题:
方案一:自定义表头单元格嵌入筛选控件(推荐)
这个方法直接在标准表头的下方嵌入筛选控件,完全不占用数据行的位置,从根源避免了首行被遮挡的问题。
步骤1:创建带筛选功能的自定义表头单元格
先写一个继承自DataGridViewColumnHeaderCell的类,把TextBox作为筛选控件集成进去:
Public Class FilterableHeaderCell Inherits DataGridViewColumnHeaderCell Private WithEvents filterTextBox As New TextBox() Protected Overrides Sub Paint(graphics As Graphics, clipBounds As Rectangle, cellBounds As Rectangle, rowIndex As Integer, dataGridViewElementStates As DataGridViewElementStates, value As Object, formattedValue As Object, errorText As String, cellStyle As DataGridViewCellStyle, advancedBorderStyle As DataGridViewAdvancedBorderStyle, paintParts As DataGridViewPaintParts) ' 先绘制默认的表头样式 MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, dataGridViewElementStates, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts) ' 计算筛选文本框的位置:紧贴表头下方,宽度和列一致 Dim filterRect As New Rectangle(cellBounds.Left, cellBounds.Bottom, cellBounds.Width, 20) filterTextBox.Bounds = filterRect ' 确保文本框被添加到DataGridView的控件集合里 If Not DataGridView.Controls.Contains(filterTextBox) Then DataGridView.Controls.Add(filterTextBox) End If End Sub ' 文本框内容变化时触发筛选逻辑 Private Sub filterTextBox_TextChanged(sender As Object, e As EventArgs) Handles filterTextBox.TextChanged If DataGridView.DataSource Is Nothing Then Return ' 用DataView的RowFilter实现筛选,这里做模糊匹配 Dim dv As DataView = DirectCast(DataGridView.DataSource, DataView) ' 注意转义单引号,避免语法错误 Dim safeText As String = filterTextBox.Text.Replace("'", "''") dv.RowFilter = $"[{OwningColumn.Name}] LIKE '%{safeText}%'" End Sub End Class
步骤2:给DataGridView列绑定自定义表头
在窗体的Load事件里,把需要筛选的列的表头替换成我们自定义的单元格,同时调整顶部边距给筛选控件留空间:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' 先绑定你的数据源(比如DataTable、BindingSource) dgvData.DataSource = YourDataSource ' 遍历所有列,替换表头单元格 For Each col As DataGridViewColumn In dgvData.Columns col.HeaderCell = New FilterableHeaderCell() Next ' 调整左上角表头的高度,给筛选控件腾出空间 dgvData.TopLeftHeaderCell.Size = New Size(dgvData.TopLeftHeaderCell.Width, dgvData.TopLeftHeaderCell.Height + 20) ' 调整行表头的内边距,避免数据行被遮挡 dgvData.RowHeadersDefaultCellStyle.Padding = New Padding(0, 20, 0, 0) End Sub
方案二:添加固定的筛选行(作为独立行)
如果觉得自定义表头太复杂,也可以添加一个专门的筛选行放在最顶部,和数据行做样式区分:
步骤1:添加筛选行并绑定筛选控件
在DataGridView的RowAdded事件里插入筛选行,给每个单元格添加TextBox:
Private Sub dgvData_RowAdded(sender As Object, e As DataGridViewRowEventArgs) Handles dgvData.RowAdded ' 只在第一次加载时创建筛选行 If dgvData.Rows.Count = 1 Then Dim filterRow As New DataGridViewRow() filterRow.CreateCells(dgvData) ' 设置筛选行的样式,比如灰色背景,和数据行区分开 filterRow.DefaultCellStyle.BackColor = Color.LightGray ' 给每个单元格添加筛选文本框 For i As Integer = 0 To dgvData.Columns.Count - 1 Dim tb As New TextBox() tb.Dock = DockStyle.Fill ' 绑定文本变化事件 AddHandler tb.TextChanged, AddressOf FilterTextBox_TextChanged ' 把列索引存到Tag里,方便后续定位 tb.Tag = i dgvData.Controls.Add(tb) ' 设置文本框位置对应筛选行的单元格 tb.Bounds = dgvData.GetCellDisplayRectangle(i, 0, True) Next ' 插入到第一行 dgvData.Rows.Insert(0, filterRow) ' 设置筛选行为只读,防止用户编辑行内容 filterRow.ReadOnly = True End If End Sub ' 多列筛选的逻辑处理 Private Sub FilterTextBox_TextChanged(sender As Object, e As EventArgs) Dim tb As TextBox = DirectCast(sender, TextBox) Dim colIndex As Integer = CInt(tb.Tag) Dim colName As String = dgvData.Columns(colIndex).Name ' 组合所有筛选条件 Dim filterConditions As New List(Of String)() For Each ctrl As Control In dgvData.Controls If TypeOf ctrl Is TextBox Then Dim filterTb As TextBox = DirectCast(ctrl, TextBox) Dim idx As Integer = CInt(filterTb.Tag) Dim name As String = dgvData.Columns(idx).Name If Not String.IsNullOrEmpty(filterTb.Text) Then Dim safeText As String = filterTb.Text.Replace("'", "''") filterConditions.Add($"[{name}] LIKE '%{safeText}%'") End If End If Next ' 应用筛选 If dgvData.DataSource IsNot Nothing Then Dim dv As DataView = DirectCast(dgvData.DataSource, DataView) dv.RowFilter = String.Join(" AND ", filterConditions) End If End Sub
额外处理:避免控件错位
记得在ColumnWidthChanged事件里重新调整筛选控件的位置,防止列宽改变后控件错位:
Private Sub dgvData_ColumnWidthChanged(sender As Object, e As DataGridViewColumnEventArgs) Handles dgvData.ColumnWidthChanged ' 重新设置对应列的筛选文本框位置 For Each ctrl As Control In dgvData.Controls If TypeOf ctrl Is TextBox Then Dim tb As TextBox = DirectCast(ctrl, TextBox) Dim colIndex As Integer = CInt(tb.Tag) tb.Bounds = dgvData.GetCellDisplayRectangle(colIndex, 0, True) End If Next End Sub
关键注意点
- 不管用哪种方案,都要处理
RowFilter里的特殊字符(比如单引号),避免筛选语法错误。 - 方案一的自定义表头更优雅,不会干扰数据行的操作,是我个人更推荐的方案。
- 如果你的数据源是
BindingSource,可以直接用它的Filter属性,和DataView.RowFilter用法类似。
内容的提问来源于stack exchange,提问作者Tleilax




