1

I have a .KeyPress event handler which is supposed to limit/control the keys which can be entered in a specific TextBox (more precisely, any of the textboxes in a specific DataGridViewTextBoxColumn)

Private Sub dgv_EditingControlShowing(sender As Object, e As DataGridViewEditingControlShowingEventArgs) Handles dgv.EditingControlShowing
    If dgv.CurrentCell.ColumnIndex = myColumn.Index And Not e.Control Is Nothing Then
        DirectCast(e.Control, TextBox).CharacterCasing = CharacterCasing.Upper
        DirectCast(e.Control, TextBox).MaxLength = 10
        AddHandler DirectCast(e.Control, TextBox).KeyPress, AddressOf controlKeyPress
    End If
End Sub

Private Sub controlKeyPress(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyPressEventArgs)
    Dim charEnum As Integer = CUInt(Microsoft.VisualBasic.Asc(e.KeyChar))
    Dim tb As TextBox = DirectCast(sender, TextBox)
    Select Case charEnum
        Case 8
            ' Always permit the keying of backspace (no suppression)

        Case 42
            ' Permit the keying of asterisk (42) but only if it is the first character (otherwise, suppress the key press)
            If Not tb.SelectionStart = 0 Then e.KeyChar = ""

        Case 46
            ' Permit the keying of period (46) but only if it is not the first character and the first character is not an asterisk (otherwise, suppress the key press)
            If tb.SelectionStart = 0 OrElse tb.Text.FirstOrDefault = "*" Then e.KeyChar = ""

        Case 65 To 90, 97 To 122
            ' Permit the keying of upper-case alpha (65-90) and lower-case alpha (97-122) as long as the first character is not an asterisk (otherwise, suppress the key press)
            If tb.Text.FirstOrDefault = "*" Then e.KeyChar = ""

        Case Else
            ' All other characters, suppress the key press (set the KeyChar to nothing)
            e.KeyChar = ""
    End Select
End Sub

What's weird is, the same handler seems to be getting applied to other TextBox controls in the DataGridView, but in a different column (i.e. not in myColumn) Which is strange because I have a specific condition in the EditingControlShowing event that specifies that the handler should only be applied if the .ColumnIndex of the control matches that of the column to which it should apply (i.e. If dgv.CurrentCell.ColumnIndex = myColumn.Index) So I'm not sure why the same handler is being applied to a TextBox that's not in myColumn?

Also, it doesn't appear to be consistent - when I initially load the DGV, the other textboxes have no restrictions on them (as expected); when I go to edit a row, and the handler is applied to myColumn (as expected), the same handler also seems to be applied immediately to any other textboxes in the same row (but in debugging, I can't seem to trap where this happens, I can only trap the application of the event handler to the correct control)

I'm not sure if I should have a RemoveHandler call somewhere - and if so, where, because I can't find the point at which the handler is being applied erroneously in the first place?

I tried this but it doesn't seem to have any effect (again, while debugging, when I click in a TextBox in myOtherColumn, it does hit that line, but the restriction is still imposed anyway?)

Private Sub dgv_EditingControlShowing(sender As Object, e As DataGridViewEditingControlShowingEventArgs) Handles dgv.EditingControlShowing
    If dgv.CurrentCell.ColumnIndex = myColumn.Index Then
        DirectCast(e.Control, TextBox).CharacterCasing = CharacterCasing.Upper
        DirectCast(e.Control, TextBox).MaxLength = 10
        AddHandler DirectCast(e.Control, TextBox).KeyPress, AddressOf controlKeyPress
    ElseIf dgv.CurrentCell.ColumnIndex = myOtherColumn.Index Then
        RemoveHandler DirectCast(e.Control, TextBox).KeyPress, AddressOf controlKeyPress
    End If
End Sub

All suggestions welcome!

Alan O'Brien
  • 151
  • 1
  • 13
  • 1
    Your filtering logic seems not accurate **if**. 1) What if the user enters `*` then moves the cursor to position `0` and enters `*` again? Another `*` and more can be entered. 2) The user also can enter `ABC.` for example then delete the `ABC` part. You will get `.` at position `0`. Also, you should set `e.Handled = True` to prevent the inputs and not `e.KeyChar = ""`. – dr.null Oct 20 '22 at 04:08
  • 1
    Excellent observations @dr.null, thank you! I've handled 1) by modifying the condition to suppress the keystroke if an asterisk already exists in the `.Text` For 2) I've added a clean-up operation on the `.CellValueChanged` event to `.TrimStart` and `.TrimEnd` any leading or trailing period characters. Obviously this can't be done on `.KeyPress` because periods are valid if surrounded by alpha characters so it has to be retrospectively applied once the edit has been "committed". Appreciate the sense-check, thank you! – Alan O'Brien Oct 21 '22 at 13:33
  • 1
    Wrong event quoted above, the clean-up operation occurs under the `.CellEndEdit` event, not `.CellValueChanged`... – Alan O'Brien Oct 21 '22 at 13:41

1 Answers1

2

The DataGridView control will reuse an editing control if it can to improve performance. You should keep a reference to the editing control from the EditingControlShowing event handler and use a RemoveHandler statement in the CellEndEdit event handler.

Actually, you may not need to keep the reference. You may be able to use the EditingControl property of the grid. Try that first.

EDIT:

I have just tested for myself and the EditingControl property of the grid is Nothing when the CellEndEdit event is raised, so my second suggestion above is out. That means that you need to retain a reference to the editing control from the EditingControlShowing event handler. If you're going to do that though, you may as well not use AddHandler and RemoveHandler. It's simpler to declare the field WithEvents and then use a Handles clause on the event handler, e.g.

Private WithEvents editingControl As TextBox

Private Sub DataGridView1_EditingControlShowing(sender As Object, e As DataGridViewEditingControlShowingEventArgs) Handles DataGridView1.EditingControlShowing
    If DataGridView1.CurrentCell.ColumnIndex = 0 Then
        editingControl = DirectCast(e.Control, TextBox)
    End If
End Sub

Private Sub DataGridView1_CellEndEdit(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellEndEdit
    editingControl = Nothing
End Sub

Private Sub EditingControl_KeyPress(sender As Object, e As KeyPressEventArgs) Handles editingControl.KeyPress
    Console.WriteLine(e.KeyChar)
End Sub

That code will assign the editing control to the field if and only if the cell being edited is in the first column. Any control assigned to that field will have its events handled and the field is always reset when an editing session ends.

jmcilhinney
  • 50,448
  • 5
  • 26
  • 46
  • Apologies @jmcilhinney you've lost me somewhat? I've tried adding a `CellEndEdit` handler but I'm not seeing any direct `dgv.EditingControl` property to examine? I can find `dgv.CurrentCell.DataGridView.EditingControl` but it is always showing as `Nothing`? Even stranger; if I stick a breakpoint on the first line of the sub (`If TypeOf dgvSubProfiles.CurrentCell.DataGridView.EditingControl Is DataGridViewTextBoxEditingControl Then`), even though it never meets the condition, the behaviour seems to stop? Remove the breakpoint and it comes back again?? – Alan O'Brien Oct 19 '22 at 13:27
  • @AlanO'Brien, `dgv` and `dgv.CurrentCell.DataGridView` refer to the same object. My dad's name is Tom. If I say Tom and I says Tom's son's father then I'm referring to the same person. That's what you're doing there. – jmcilhinney Oct 20 '22 at 00:21
  • @AlanO'Brien, I wasn't sure whether the suggestion in the second paragraph would work or not as I haven't tested it. It may be that the `CellEndEdit` event is too late to access the editing control via the grid. In that case, just use the first suggestion and keep a reference from the `EditingControlShowing` event handler. – jmcilhinney Oct 20 '22 at 00:23
  • @AlanO'Brien, see my edited answer for a simplified solution. – jmcilhinney Oct 20 '22 at 00:38
  • Perfect, thanks @jmcilhinney that works perfectly and is actually much cleaner code to maintain. Appreciate the snippet! – Alan O'Brien Oct 21 '22 at 13:29