You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WinForms DataGridView同列行级多控件切换实现方案问询

DataGridView同列行级多控件切换可行方案

初始方案错误分析

直接替换单元格对象的方式存在核心问题:

  • cllValueField = cboConfigured仅修改局部变量引用,并未实际替换DataGridView行内的单元格,需改为dgv.Rows(cboFeature.RowIndex).Cells(valueColumn.Index) = cboConfigured
  • CellValueChanged会在编辑过程中多次触发,建议改用CellEndEdit确保用户选择完成后再处理
  • DataError多因值类型不匹配、ComboBox选中值不在数据源范围内导致,需严格保证值与控件类型的一致性

方案一:利用CellBeginEdit动态切换编辑控件(无需自定义列)

通过监听单元格编辑开始/结束事件,动态替换编辑控件并同步值,实现轻量型的控件切换。

核心代码

' 编辑开始时切换控件
Private Sub dgv_CellBeginEdit(sender As Object, e As DataGridViewCellCancelEventArgs) Handles dgv.CellBeginEdit
    If e.ColumnIndex <> valueColumn.Index Then Return

    Dim row = dgv.Rows(e.RowIndex)
    Dim featureID = Convert.ToInt32(row.Cells(featureIDColumn.Index).Value)
    Dim matches = From r In dtbFeatureList Where r.Field(Of Integer)("ID") = featureID
    If Not matches.Any Then Return

    Dim controlType = matches.First().Field(Of String)("FieldControl")
    Dim editControl As Control = Nothing

    Select Case controlType
        Case "Checkbox"
            Dim chk As New CheckBox()
            chk.Checked = Convert.ToBoolean(row.Cells(valueColumn.Index).Value)
            chk.Dock = DockStyle.Fill
            editControl = chk
        Case "Textbox"
            Dim txt As New TextBox()
            txt.Text = row.Cells(valueColumn.Index).Value?.ToString()
            txt.Dock = DockStyle.Fill
            editControl = txt
        Case "Combobox"
            Dim cbo As New ComboBox()
            Dim dvOptions As New DataView(dtbFeatureValueList)
            dvOptions.RowFilter = $"[FeatureID] = {featureID}"
            cbo.DataSource = dvOptions.ToTable()
            cbo.ValueMember = "Value"
            cbo.DisplayMember = "ValueText"
            cbo.SelectedValue = row.Cells(valueColumn.Index).Value
            cbo.Dock = DockStyle.Fill
            editControl = cbo
    End If

    If editControl IsNot Nothing Then
        dgv.EditingControl = editControl
    End If
End Sub

' 编辑结束后同步值到单元格
Private Sub dgv_CellEndEdit(sender As Object, e As DataGridViewCellEventArgs) Handles dgv.CellEndEdit
    If e.ColumnIndex <> valueColumn.Index Then Return

    Dim editControl = dgv.EditingControl
    Dim cell = dgv.Rows(e.RowIndex).Cells(valueColumn.Index)

    Select Case editControl.GetType()
        Case GetType(CheckBox)
            cell.Value = DirectCast(editControl, CheckBox).Checked
        Case GetType(TextBox)
            cell.Value = DirectCast(editControl, TextBox).Text
        Case GetType(ComboBox)
            cell.Value = DirectCast(editControl, ComboBox).SelectedValue
    End Select

    dgv.InvalidateCell(cell)
End Sub

' 非编辑状态下自定义显示样式
Private Sub dgv_CellFormatting(sender As Object, e As DataGridViewCellFormattingEventArgs) Handles dgv.CellFormatting
    If e.ColumnIndex <> valueColumn.Index OrElse e.RowIndex = dgv.NewRowIndex Then Return

    Dim row = dgv.Rows(e.RowIndex)
    Dim featureID = Convert.ToInt32(row.Cells(featureIDColumn.Index).Value)
    Dim matches = From r In dtbFeatureList Where r.Field(Of Integer)("ID") = featureID
    If Not matches.Any Then Return

    Dim controlType = matches.First().Field(Of String)("FieldControl")

    Select Case controlType
        Case "Checkbox"
            e.Value = If(Convert.ToBoolean(e.Value), "√", "×")
            e.FormattingApplied = True
        Case "Combobox"
            Dim dvOptions As New DataView(dtbFeatureValueList)
            dvOptions.RowFilter = $"[FeatureID] = {featureID} AND [Value] = '{e.Value}'"
            If dvOptions.Count > 0 Then
                e.Value = dvOptions(0)("ValueText")
                e.FormattingApplied = True
            End If
    End Select
End Sub

方案二:修复自定义列实现

若需更封装的实现,需完善自定义单元格的编辑控件初始化、值同步与绘制逻辑:

自定义单元格类

Public Class DataGridViewCustomConfigurableCell
    Inherits DataGridViewTextBoxCell

    Public Property FeatureID As Integer
    Public Property ControlType As String

    Public Overrides Sub InitializeEditingControl(rowIndex As Integer, initialFormattedValue As Object, dataGridViewCellStyle As DataGridViewCellStyle)
        MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle)

        Dim host = TryCast(DataGridView.EditingControl, DataGridViewTextBoxEditingControl)
        If host Is Nothing Then Return

        host.Controls.Clear()
        Dim editControl As Control = Nothing

        Select Case ControlType
            Case "Checkbox"
                Dim chk As New CheckBox()
                chk.Checked = Convert.ToBoolean(Value)
                chk.Dock = DockStyle.Fill
                AddHandler chk.CheckedChanged, AddressOf OnEditControlValueChanged
                editControl = chk
            Case "Textbox"
                Dim txt As New TextBox()
                txt.Text = Value?.ToString()
                txt.Dock = DockStyle.Fill
                AddHandler txt.TextChanged, AddressOf OnEditControlValueChanged
                editControl = txt
            Case "Combobox"
                Dim cbo As New ComboBox()
                Dim dvOptions As New DataView(dtbFeatureValueList)
                dvOptions.RowFilter = $"[FeatureID] = {FeatureID}"
                cbo.DataSource = dvOptions.ToTable()
                cbo.ValueMember = "Value"
                cbo.DisplayMember = "ValueText"
                cbo.SelectedValue = Value
                cbo.Dock = DockStyle.Fill
                AddHandler cbo.SelectedValueChanged, AddressOf OnEditControlValueChanged
                editControl = cbo
        End If

        If editControl IsNot Nothing Then
            host.Controls.Add(editControl)
        End If
    End Sub

    Private Sub OnEditControlValueChanged(sender As Object, e As EventArgs)
        Dim editControl = TryCast(sender, Control)
        If editControl Is Nothing Then Return

        Select Case editControl.GetType()
            Case GetType(CheckBox)
                Value = DirectCast(editControl, CheckBox).Checked
            Case GetType(TextBox)
                Value = DirectCast(editControl, TextBox).Text
            Case GetType(ComboBox)
                Value = DirectCast(editControl, ComboBox).SelectedValue
        End Select
    End Sub

    Public Overrides Sub Paint(graphics As Graphics, clipBounds As Rectangle, cellBounds As Rectangle, rowIndex As Integer, cellState 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, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts)

        If ControlType = "Checkbox" Then
            Dim chkRect = New Rectangle(cellBounds.X + 5, cellBounds.Y + (cellBounds.Height - 16) \ 2, 16, 16)
            ControlPaint.DrawCheckBox(graphics, chkRect, If(Convert.ToBoolean(value), ButtonState.Checked, ButtonState.Normal))
        End If
    End Sub
End Class

自定义列类

Public Class DataGridViewCustomConfigurableColumn
    Inherits DataGridViewColumn

    Public Sub New()
        MyBase.New(New DataGridViewCustomConfigurableCell())
    End Sub

    Public Overrides Property CellTemplate As DataGridViewCell
        Get
            Return MyBase.CellTemplate
        End Get
        Set(value As DataGridViewCell)
            If value IsNot Nothing AndAlso Not TypeOf value Is DataGridViewCustomConfigurableCell Then
                Throw New InvalidCastException("必须使用DataGridViewCustomConfigurableCell作为单元格模板")
            End If
            MyBase.CellTemplate = value
        End Set
    End Property
End Class

绑定数据与更新控件

Private Sub UpdateCustomColumnCells()
    For Each row As DataGridViewRow In dgv.Rows
        If row.IsNewRow Then Continue For
        Dim featureID = Convert.ToInt32(row.Cells(featureIDColumn.Index).Value)
        Dim matches = From r In dtbFeatureList Where r.Field(Of Integer)("ID") = featureID
        If Not matches.Any Then Continue For

        Dim controlType = matches.First().Field(Of String)("FieldControl")
        Dim cfgCell = DirectCast(row.Cells(cfgValueColumn.Index), DataGridViewCustomConfigurableCell)
        cfgCell.FeatureID = featureID
        cfgCell.ControlType = controlType
        cfgCell.Value = row.Cells(valueColumn.Index).Value
    Next
    dgv.Refresh()
End Sub

' Feature列值变化时更新自定义列
Private Sub dgv_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles dgv.CellValueChanged
    If e.ColumnIndex = cboFeatureColumn.Index AndAlso e.RowIndex <> dgv.NewRowIndex Then
        UpdateCustomColumnCells()
    End If
End Sub

内容的提问来源于stack exchange,提问作者Alan O'Brien

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 09:40:33