vb.netwinformsdatagridviewcolumncustomcolumn

Saved value is not displayed in custom datagridview column


So I've made a custom DataGridView-Column which is showing a DropDown for multiselecting different values and displaying them with a separator.

Picture of Cell with dropdown and selectedvalues

It's working fine so far, but the saved value is not displayed in the cell. Displaying the value after selecting them is working fine, but after reloading the cell is empty.

I think I've missed an Eventhandler which initializes "Me.Value"...

Code from Column-Class:

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

Code from CustomCell:

    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

UPDATE I've found the problem. The value was overwritten in the paint-Event. I am going to update the code once I've finished the Bugfix.


Solution

  • So I've fixed it. As already posted the Problem was in the Paint-Method which overwrited my Cell-Values. Instead of this...

    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
    

    I'm now using this...

    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)
    
        If Not String.IsNullOrEmpty(CStr(formattedValue)) And (CStr(formattedValue) <> SelectedText) Then
            'New Value
            InitDBValue(formattedValue, value)
        End If
    
        'formattedValue = SelectedText
        'value = SelectedValue
    
        MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, cellstate, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts)
    End Sub
    
    Private Sub InitDBValue(ByRef formattedValue As Object, ByRef value As Object)
        Dim col = CType(Me.OwningColumn, dgvCheckedComboBoxColumn)
        Dim drNew, drSource As DataRow
        If m_CheckedValues IsNot Nothing Then
            m_CheckedValues.Rows.Clear()
        End If
        For Each str As String In CStr(formattedValue).Split(","c)
            drNew = CheckedValues.NewRow
            drNew(col.ValueMember) = str
            drSource = (From r In col.DataSource.AsEnumerable
                        Where CStr(r(col.ValueMember)) = str
                        Select r).FirstOrDefault
            drNew(col.DisplayMember) = CStr(drSource(col.DisplayMember))
            If col.DisplayMember <> col.CellDisplayMember Then
                drNew(col.CellDisplayMember) = CStr(drSource(col.CellDisplayMember))
            End If
            m_CheckedValues.Rows.Add(drNew)
        Next
        formattedValue = SelectedText
        value = SelectedValue
    End Sub
    

    I know, not super clean but does its job...