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

自定义DataGridView多选下拉列已保存值重新加载后不显示问题

自定义DataGridView多值选择下拉框列显示异常问题

我自定义了一个DataGridView列,可展示下拉框实现多值选择,选中值会以分隔符拼接展示。
带下拉框和选中值的单元格示意图
目前功能整体运行正常,但已保存的值不会在单元格中展示:选中值后实时展示正常,重新加载后单元格变为空白。
我猜测是遗漏了初始化Me.Value的事件处理程序。

列类代码

Imports System.Data
Imports System.ComponentModel

Public Class dgvCheckedComboBoxColumn
    Inherits DataGridViewColumn

    Private m_dt As DataTable
    <Browsable(True), Category("Daten"), Description("Only DataTable allowed!")>
    Public Shadows Property DataSource() As DataTable
        Get
            Return m_dt
        End Get
        Set(value As DataTable)
            m_dt = value
        End Set
    End Property

    Private m_DisplayMember As String
    <Browsable(True), Category("Daten"), Description("The column that contains the displayed value in the dropdown")>
    Public Shadows Property DisplayMember() As String
        Get
            Return m_DisplayMember
        End Get
        Set(value As String)
            m_DisplayMember = value
        End Set
    End Property

    Private m_ValueMember As String
    <Browsable(True), Category("Daten"), Description("ValueMember")>
    Public Shadows Property ValueMember As String
        Get
            Return m_ValueMember
        End Get
        Set(value As String)
            m_ValueMember = value
            'Primary-Key von DataSource entspricht Value-Member
            If Me.DataSource IsNot Nothing Then
                m_dt.PrimaryKey = New DataColumn() {m_dt.Columns(value)}
            End If
        End Set
    End Property

    Private m_CellDisplayMember As String
    <Browsable(True), Category("Daten"), Description("Displayed Cell-Value")>
    Public Property CellDisplayMember As String
        Get
            Return If(String.IsNullOrEmpty(m_CellDisplayMember), m_DisplayMember, m_CellDisplayMember)
        End Get
        Set(value As String)
            m_CellDisplayMember = value
        End Set
    End Property

    Private m_DropDownWidth As Integer
    <DefaultValue(150), Browsable(True), Category("Visual"), Description("DropDown-Width")>
    Public Property DropDownWidth As Integer
        Get
            Return m_DropDownWidth
        End Get
        Set(value As Integer)
            m_DropDownWidth = value
        End Set
    End Property

    Private m_UseValidation As Boolean
    <DefaultValue(False), Browsable(True), Category("Data"), Description("Only allow values that exist in datasource")>
    Public Property UseValidation As Boolean
        Get
            Return m_UseValidation
        End Get
        Set(value As Boolean)
            m_UseValidation = value
        End Set
    End Property

    Public Sub New()
        MyBase.New(New dgvCheckedComboBoxCell())
        Me.CellTemplate = New dgvCheckedComboBoxCell
        Me.ReadOnly = True
    End Sub

    Public Overrides Function Clone() As Object
        Dim copy As dgvCheckedComboBoxColumn = TryCast(MyBase.Clone(), dgvCheckedComboBoxColumn)
        copy.DataSource = m_dt
        copy.ValueMember = m_ValueMember
        copy.DisplayMember = m_DisplayMember
        copy.CellDisplayMember = m_CellDisplayMember
        copy.DropDownWidth = m_DropDownWidth
        copy.UseValidation = m_UseValidation

        Return copy
    End Function
End Class

自定义单元格代码

Imports System.Data

Public Class dgvCheckedComboBoxCell
    Inherits DataGridViewTextBoxCell

    Private m_ucCheckList As UserControl

    Public Sub New()
        MyBase.New()


    End Sub

#Region " Properties"
    Private m_CheckedValues As DataTable
    ''' <summary>
    ''' Contains the selected values from current cell 
    ''' </summary>
    ''' <returns></returns>
    Public Property CheckedValues As DataTable
        Get
            Dim col As dgvCheckedComboBoxColumn = CType(Me.OwningColumn, dgvCheckedComboBoxColumn)
            If m_CheckedValues Is Nothing AndAlso col.DataSource IsNot Nothing Then
                Dim dt As DataTable = DirectCast(col.DataSource, DataTable).Clone
                m_CheckedValues = dt
            End If
            Return m_CheckedValues
        End Get
        Set(value As DataTable)
            m_CheckedValues = value
        End Set
    End Property

    ''' <summary>
    ''' Contains the selected values separated by String by ValueMember
    ''' </summary>
    ''' <returns></returns>
    Private ReadOnly Property SelectedValue As String
        Get
            Dim col = DirectCast(Me.OwningColumn, dgvCheckedComboBoxColumn)
            Try
                Return String.Join(",", CheckedValues?.Rows.Cast(Of DataRow).Select(Function(r) r.Item(col.ValueMember).ToString))
            Catch ex As Exception
                Return ""
            End Try
        End Get
    End Property

    Private ReadOnly Property SelectedText As String
        Get
            Dim col = DirectCast(Me.OwningColumn, dgvCheckedComboBoxColumn)
            Try
                Return String.Join(",", CheckedValues?.Rows.Cast(Of DataRow).Select(Function(r) r.Item(col.CellDisplayMember).ToString))
            Catch ex As Exception
                Return ""
            End Try
        End Get
    End Property
#End Region


#Region " Cell-Events"  
    Protected Overrides Sub OnMouseEnter(rowIndex As Integer)
        MyBase.OnMouseEnter(rowIndex)
    End Sub

    Protected Overrides Sub OnEnter(rowIndex As Integer, throughMouseClick As Boolean)
        MyBase.OnEnter(rowIndex, throughMouseClick)
    End Sub

    Protected Overrides Sub OnLeave(rowIndex As Integer, throughMouseClick As Boolean)
        If DataGridView.Controls.Contains(m_ucCheckList) Then
            m_ucCheckList.Visible = False
        End If
        Me.Value = Me.SelectedValue
        MyBase.OnLeave(rowIndex, throughMouseClick)
    End Sub

    Protected Overrides Sub OnMouseClick(e As DataGridViewCellMouseEventArgs)
        MyBase.OnMouseClick(e)
        OpenDropDown()
    End Sub

    Private Sub OpenDropDown(Optional CursorLocation As Point = Nothing)
        Try
            If Me.DataGridView.Columns.Count = 0 Or RowIndex = -1 Then
                Exit Sub
            End If

            Dim col As dgvCheckedComboBoxColumn = CType(Me.OwningColumn, dgvCheckedComboBoxColumn)
            If col.DataSource Is Nothing OrElse CType(col.DataSource, DataTable).Rows.Count = 0 Then Exit Sub

            If m_ucCheckList Is Nothing Then
                m_ucCheckList = New UserControl
                m_ucCheckList.Padding = New Padding(5, 0, 0, 0)
                m_ucCheckList.BorderStyle = BorderStyle.FixedSingle
            End If
            If Not IsNothing(CursorLocation) Then
                Dim ctrl = DataGridView.GetChildAtPoint(CursorLocation, GetChildAtPointSkip.Disabled)
            End If
            SetDefaultLocationOfListBox()

            Dim chk As CheckBox
            m_ucCheckList.Controls.Clear()
            For Each row As DataRow In DirectCast(col.DataSource, DataTable).Rows
                chk = New CheckBox
                chk.Text = row(col.DisplayMember).ToString
                chk.Tag = row
                chk.Checked = (From dr In CheckedValues.AsEnumerable
                               Where CStr(dr(col.ValueMember)) = CStr(row(col.ValueMember))
                               Select True).FirstOrDefault
                chk.Dock = DockStyle.Top
                AddHandler chk.CheckedChanged, AddressOf ChkList_CheckedChanged
                m_ucCheckList.Controls.Add(chk)
            Next
            With m_ucCheckList
                If CType(col.DataSource, DataTable).Rows.Count > 20 Then
                    .Height = 250
                Else
                    .Height = CType(col.DataSource, DataTable).Rows.Count * m_ucCheckList.Controls(0).Height
                End If
                .Width = col.DropDownWidth
                If .Height < 24 Then
                    .Height = 24
                End If
                .Show()
                .BringToFront()
                .Refresh()
            End With

        Catch ex As Exception

        End Try
    End Sub

    Private Sub SetDefaultLocationOfListBox()
        Dim defaultPoint As Point
        Try
            defaultPoint = GetCurrentCellPosition()

            m_ucCheckList.Location = defaultPoint
            If Not Me.DataGridView.Controls.Contains(m_ucCheckList) Then
                Me.DataGridView.Controls.Add(m_ucCheckList)
            End If

            m_ucCheckList.Visible = True
        Catch ex As Exception

        End Try
    End Sub

    Private Function GetCurrentCellPosition() As Point
        Try
            If Me.DataGridView IsNot Nothing AndAlso DataGridView.CurrentCell IsNot Nothing Then
                Dim iRowHeight As Integer = DataGridView.Rows(DataGridView.CurrentCell.RowIndex).Height
                Dim rCellRectangle As Rectangle = DataGridView.GetCellDisplayRectangle(DataGridView.CurrentCell.ColumnIndex, DataGridView.CurrentCell.RowIndex, False)

                Return New Point(rCellRectangle.X, rCellRectangle.Y + iRowHeight)
            End If
        Catch ex As Exception

        End Try
    End Function

    Protected 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)
        
'formattedValue = SelectedText
            'value = SelectedValue

        MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, cellstate, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts)
    End Sub

    Private Sub ChkList_CheckedChanged(sender As Object, e As EventArgs)
        Dim chk As CheckBox = CType(sender, CheckBox)
        Dim row As DataRow = CType(chk.Tag, DataRow)
        Dim col = CType(Me.OwningColumn, dgvCheckedComboBoxColumn)
        If chk.Checked Then
            Dim dr = CheckedValues.NewRow()
            dr(col.ValueMember) = row(col.ValueMember)
            dr(col.DisplayMember) = row(col.DisplayMember)
            If col.CellDisplayMember <> col.DisplayMember Then
                dr(col.CellDisplayMember) = row(col.CellDisplayMember)
            End If
            m_CheckedValues.Rows.Add(dr)
        Else
            Dim CheckedRow = (From dr In CheckedValues.AsEnumerable
                              Where CStr(dr(col.ValueMember)) = CStr(row(col.ValueMember))
                              Select dr).FirstOrDefault
            If CheckedRow IsNot Nothing Then
                CheckedValues.Rows.Remove(CheckedRow)
            End If
        End If
    End Sub

    Protected Overrides Sub OnKeyPress(e As KeyPressEventArgs, rowIndex As Integer)
        MyBase.OnKeyPress(e, rowIndex)
    End Sub

#End Region
End Class

更新:我已经找到问题根源,值在Paint事件中被覆盖了,完成Bug修复后我会更新对应代码。


内容的提问来源于stack exchange,提问作者Stevy G.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 08:24:02