Search code examples
vb.netdatagridviewcell

VB.NET Update Issue in DataGridView Cell for TimePicker: Enter Key Does not Update Displayed Value


I am having a curious issue with overloading a cell of the DataGridView in VB.NET to use a DateTimePicker (allows user to select both a date and a time). I took the Microsoft implementation of a Calendar picker and modified it be both a Calendar and Time picker. When I enter a new date and press the ENTER key, the displayed date in the DataGridView cell is the old date.

It's only when press ENTER again on that key, or click on that cell and click away that the new value is displayed.

This cell does display the proper date-time value if I click on the cell, enter a new date, and click away onto another cell. Then, the displayed date matches what I inputted. It's using the ENTER key that I am having issues with.

I modified the Microsoft overloaded code as follows:

Public Class TimeColumn
    Inherits DataGridViewColumn

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

    Public Overrides Property CellTemplate() As DataGridViewCell
        Get
            Return MyBase.CellTemplate
        End Get
        Set(ByVal value As DataGridViewCell)
            ' Ensure that the cell used for the template is a TimeCell.
            If (value IsNot Nothing) AndAlso _
                Not value.GetType().IsAssignableFrom(GetType(TimeCell)) _
                Then
                Throw New InvalidCastException("Must be a TimeCell")
            End If
            MyBase.CellTemplate = value
        End Set
    End Property

End Class

Public Class TimeCell
    Inherits DataGridViewTextBoxCell

    Public Sub New()
        ' Use the short date format.            
        Me.Style.Format = "MM/dd/yy  h:mm tt"
        'm_isTime = True
    End Sub
    Public Overrides Sub InitializeEditingControl(ByVal rowIndex As Integer, _
        ByVal initialFormattedValue As Object, _
        ByVal dataGridViewCellStyle As DataGridViewCellStyle)

        ' Set the value of the editing control to the current cell value.
        MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, _
            dataGridViewCellStyle)

        Dim ctl As New TimeEditingControl
        ctl = CType(DataGridView.EditingControl, TimeEditingControl)
        ctl.CustomFormat = "MM/dd/yyyy h:mm tt"
        Me.Value = ctl.Value

    End Sub

    Public Overrides ReadOnly Property EditType() As Type
        Get
            ' Return the type of the editing contol that TimeCell uses.
            Return GetType(TimeEditingControl)
        End Get
    End Property

    Public Overrides ReadOnly Property ValueType() As Type
        Get
            ' Return the type of the value that TimeCell contains.            
            Return GetType(DateTime)
        End Get
    End Property

    Public Overrides ReadOnly Property DefaultNewRowValue() As Object
        Get
            ' Use the current date and time as the default value.
            Return DateTime.Now
        End Get
    End Property

End Class

Class TimeEditingControl
    Inherits DateTimePicker
    Implements IDataGridViewEditingControl

    Private dataGridViewControl As DataGridView
    Private valueIsChanged As Boolean = False
    Private rowIndexNum As Integer

    Public Sub New()
        Me.Format = DateTimePickerFormat.Custom
    End Sub
    Public Property EditingControlFormattedValue() As Object _
        Implements IDataGridViewEditingControl.EditingControlFormattedValue

        Get
            Return Me.Value.ToShortDateString()
        End Get

        Set(ByVal value As Object)
            If TypeOf value Is String Then
                Me.Value = DateTime.Parse(CStr(value))
            End If
        End Set

    End Property

    Public Function GetEditingControlFormattedValue(ByVal context _
        As DataGridViewDataErrorContexts) As Object _
        Implements IDataGridViewEditingControl.GetEditingControlFormattedValue

        Dim result, tempDateTime As DateTime
        tempDateTime = Me.Value      

        Return tempDateTime.ToString            
    End Function


    Public Sub ApplyCellStyleToEditingControl(ByVal dataGridViewCellStyle As  _
        DataGridViewCellStyle) _
        Implements IDataGridViewEditingControl.ApplyCellStyleToEditingControl

        Me.Font = dataGridViewCellStyle.Font
        Me.CalendarForeColor = dataGridViewCellStyle.ForeColor
        Me.CalendarMonthBackground = dataGridViewCellStyle.BackColor

    End Sub

    Public Property EditingControlRowIndex() As Integer _
        Implements IDataGridViewEditingControl.EditingControlRowIndex

        Get
            Return rowIndexNum
        End Get
        Set(ByVal value As Integer)
            rowIndexNum = value
        End Set

    End Property



    Public Function EditingControlWantsInputKey(ByVal key As Keys, _
        ByVal dataGridViewWantsInputKey As Boolean) As Boolean _
        Implements IDataGridViewEditingControl.EditingControlWantsInputKey
        Dim lDateTime As DateTime
        ' Let the DateTimePicker handle the keys listed.
        Select key And Keys.KeyCode

            Case Keys.Enter

                Console.WriteLine(DateTime.Now.ToString & "." & DateTime.Now.Millisecond.ToString & ": EditingControlWantsInputKey--Detected [ENTER] key.")
        End Select
        valueIsChanged = True
        Console.WriteLine("EditingControlWantsInputKey (" & Me.Value & ")")
        Dim lDataGridView1 As DataGridView
        lDataGridView1 = Me.dataGridViewControl
        lDataGridView1.BeginEdit(True)
        lDataGridView1.BeginEdit(False)
        Return True


    End Function

    Public Sub PrepareEditingControlForEdit(ByVal selectAll As Boolean) _
        Implements IDataGridViewEditingControl.PrepareEditingControlForEdit

        ' No preparation needs to be done.

    End Sub

    Public ReadOnly Property RepositionEditingControlOnValueChange() _
        As Boolean Implements _
        IDataGridViewEditingControl.RepositionEditingControlOnValueChange

        Get
            Return False
        End Get

    End Property

    Public Property EditingControlDataGridView() As DataGridView _
        Implements IDataGridViewEditingControl.EditingControlDataGridView

        Get
            Return dataGridViewControl

        End Get
        Set(ByVal value As DataGridView)
            dataGridViewControl = value
        End Set

    End Property

    Public Property EditingControlValueChanged() As Boolean _
        Implements IDataGridViewEditingControl.EditingControlValueChanged

        Get
            Return valueIsChanged
        End Get
        Set(ByVal value As Boolean)
            valueIsChanged = value
        End Set

    End Property

    Public ReadOnly Property EditingControlCursor() As Cursor _
        Implements IDataGridViewEditingControl.EditingPanelCursor

        Get
            Return MyBase.Cursor
        End Get

    End Property

    Protected Overrides Sub OnValueChanged(ByVal eventargs As EventArgs)

        ' Notify the DataGridView that the contents of the cell have changed.
        valueIsChanged = True
        Me.EditingControlDataGridView.NotifyCurrentCellDirty(True)
        MyBase.OnValueChanged(eventargs)

    End Sub

End Class

My findings so far:

  1. What's curious is that Public Property EditingControlValueChanged() As Boolean Implements IDataGridViewEditingControl.EditingControlValueChanged does not detect the ENTER key at all! You can see I entered some debugging code for that event to see if it detects the ENTER key being pressed.

  2. The event Public Property EditingControlValueChanged() As Boolean Implements IDataGridViewEditingControl.EditingControlValueChanged does not detect the value change (the boolean valueIsChanged is set to false) until the second time the ENTER key is pressed, at which point it is set to true.

  3. I tried also updating the cell value in the CellEndEdit event for the DataGridView, but that didn't work.

  4. I already updated my Visual Studio Express 2013 in case it was a Micosoft bug.

Any ideas how to get the displayed value to match what was entered upon hitting the ENTER key? Thanks.


Solution

  • My initial comment was definitely wrong. After reproducing your problem, I noticed something funny/frustrating: placing a breakpoint within the EditingControlWantsInputKey method on the following line, allowed the updates to occur as expected (a debugging fix of the problem):

    Select key And Keys.KeyCode
    

    That was beyond annoying and in my frustration, I frantically tested month entries in succession: 1, 2, 3, ..., 12

    That's when I noticed: months 10-12 updated correctly. In fact, entering 01 updated correctly. My subsequent research led me to the comments section of this article, where user Dean Wiles in his comment titled Changes lost using Tab or Enter provides this Microsoft support source which indicates that:

    The ValueChanged event of the [DateTimePicker] is raised only after you type any one of the following:

    • All the digits of a year.
    • All the digits of a day.
    • All the digits of a month.

    And this can be addressed by adding the following vb.net version of his code to your TimeEditingControl class:

    Protected Overrides Function ProcessCmdKey(ByRef msg As Message, keyData As Keys) As Boolean
        Select Case keyData And Keys.KeyCode
            Case Keys.Enter, Keys.Tab
                Me.dataGridViewControl.Focus()
                Exit Select
        End Select
    
        Return MyBase.ProcessCmdKey(msg, keyData)
    End Function