vb.netwinformsdatagridviewvirtual-keyboard

Use a virtual Keyboard on focused Textboxes and DataGridView Cells


In my Form I have various Textboxes that I write into with an in Form keyboard I created using Buttons. I have this code in Form.Load, which uses an event handler to determine which Textbox has the Focus:

For Each control As Control In Me.Controls
    If control.GetType.Equals(GetType(TextBox)) Then
        Dim textBox As TextBox = control
        AddHandler textBox.Enter, Sub() FocussedTextbox = textBox
     End If
Next

Then I use this on each button to write a specific character:

Private Sub btnQ_Click(sender As Object, e As EventArgs) Handles btnQ.Click
    If btnQ.Text = "Q" Then
        FocussedTextbox.Text += "Q"
    ElseIf btnQ.Text = "q" Then
        FocussedTextbox.Text += "q"
    End If
End Sub

Up to that point I'm good and everything works as intended. The problem is I also have a DataGridView I want to write into but can't focus on it selected cells as I do on Textboxes.

I tried this:

For Each control As Control In Me.Controls
    If control.GetType.Equals(GetType(TextBox)) Then
        Dim textBox As TextBox = control
        AddHandler textBox.Enter, Sub() FocussedTextbox = textBox
    ElseIf control.GetType.Equals(GetType(DataGridViewCell)) Then
        Dim DGVC As DataGridView = control
        AddHandler DGVC.CellBeginEdit, Sub() FocussedTextbox = DGVC
    End If
Next

But it just selects my last Textbox.

I declared the variable FocussedTextbox as Control so it's not specific to Textbox but any control.

Any help will be greatly appreciated.


Solution

  • To add text to the current ActiveControl using Buttons, these Button must not steal the focus from the ActiveControl (otherwise they become the ActiveControl).
    This way, you can also avoid all those FocusedTextbox = textBox etc. and remove that code.

    You just need Buttons that don't have the Selectable attribute set. You can use a Custom Control derived from Button and remove ControlStyles.Selectable in its constructor using the SetStyle method:

    Public Class ButtonNoSel
        Inherits Button
        Public Sub New()
            SetStyle(ControlStyles.Selectable, False)
        End Sub
    End Class
    

    Replace your Buttons with this one (or, well, just set the Style if you're already using Custom Controls).


    To replace the existing Buttons with this Custom Control:


    Add the same Click event handler in the Constructor of the class that hosts those Buttons (a Form or whatever else you're using).
    You can then remove all those event handlers, one for each control, that you have now; only one event handler is needed for all:

    Public Sub New()
        InitializeComponent()
    
        For Each ctrl As Control In Me.Controls.OfType(Of ButtonNoSel)
            AddHandler ctrl.Click, AddressOf KeyBoardButtons_Click
        Next
    End Sub
    

    Of course, you also don't need to add event handlers to any other control, this is all that's required.

    Now, you can filter the Control types you want your keyboard to work on, e.g., TextBoxBase Controls (TextBox and RichTextBox), DataGridView, NumericUpDown etc.
    Or filter only special cases that need special treatment (e.g., MonthCalendar).

    To add the char corresponding to the Button pressed, you can use SendKeys.Send(): it will insert the new char in the current insertion point, so you don't need any other code to store and reset the caret/cursor position as it happens if you set the Text property of a Control.

    In this example, I'm checking whether the ActiveControl is a TextBoxBase Control, then just send the char that the clicked Button holds.
    If it's a DataGridView, first send F2 to enter Edit Mode, then send the char.
    You could also just send a char (so, no filter would be required), but in this case, you'll replace, not add to, the existing value of that Cell.

    Private Sub KeyBoardButtons_Click(sender As Object, e As EventArgs)
        Dim selectedButton = DirectCast(sender, Control)
        Dim keysToSend = String.Empty
    
        If TypeOf ActiveControl Is TextBoxBase Then
            keysToSend = selectedButton.Text
        ElseIf TypeOf ActiveControl Is DataGridView Then
            Dim ctrl = DirectCast(ActiveControl, DataGridView)
            If TypeOf ctrl.CurrentCell IsNot DataGridViewTextBoxCell Then Return
            SendKeys.Send("{F2}")
            keysToSend = selectedButton.Text
        Else
            ' Whatever else
        End If
        If Not String.IsNullOrEmpty(keysToSend) Then
            SendKeys.Send(keysToSend)
        End If
    End Sub
    

    ► Note that {F2} is sent just once: when the Cell enters Edit Mode, the ActiveControl is a DataGridViewTextBoxEditingControl, hence a TextBox Control, handled by the TextBoxBase filter.

    This is how it works (using just the code posted here):

    Virtual Keyboard SendKeys