Use a virtual Keyboard on focused Textboxes and DataGridView Cells - vb.net

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.

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 a new class object to your Project, name it ButtonNoSel, copy all the code above inside the new class to replace the two lines of code you find there.
Build the Project. You can find the ButtonNoSel Control in your ToolBox now. Replace your Buttons with this one.
Or, open up the Form's Designer file and replace (CTRL+H) all System.Windows.Forms.Button() related to the Virtual KeyBoard with ButtonNoSel.
Remove the existing event handlers, these are not needed anymore.
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):

Related

Change selection in CheckedListBox as user types

We have a custom implementation of a multi-select checkbox in our VB.NET application. It largely works just fine, but we recently replaced a regular single-select ComboBox with this control, and it does not support searching while typing.
For example, if the user wants to get to "zygote" they used to be able to start typing the word and it would slowly get closer. Now, as you type, it jumps to the z's, then the y's, then the g's, and so on.
Is it possible to make it behave as it did with a standard ComboBox?
For now, I capture KeyDown and KeyUp so it does not extraneously select an item as they type, but this is not the ideal final solution.
As described in the comments, a Timer can work, but I'll dismiss it (because old and boring :) and I'll make use of a Stopwatch instead:
The Stopwatch is restarted each time a KeyDown event is generated.
The keys pressed are added to a StringBuilder object (to avoid the creation of a multitude of strings). The StringBuilder container is cleared when the time between key presses is greater than a predefined value: here, I've set it to 400ms, to test or add a configuration option.
► The double StringBuilder.Append() is there to preserve the default behavior: when keys are pressed with a long delay, it iterates the Items that begin with the same letter (more or less what File Explorer does).
The KeyDown handler is added in the Form's Sub New(), here (to a CheckedListBox named checkedList1). It can be used to handle any ListBox or CheckedListBox in the Form.
Imports System.Diagnostics
Imports System.Text
Sub New()
AddHandler checkedList1.KeyDown, AddressOf listBox_KeyDown
End Sub
Private swLb As New Stopwatch()
Private sbCLb As New StringBuilder()
Private Sub listBox_KeyDown(sender As Object, e As KeyEventArgs)
Dim checkedList = DirectCast(sender, ListBox)
If e.KeyCode < Keys.A Then Return
If swLb.ElapsedMilliseconds > 400 Then
sbCLb.Clear()
sbCLb.Append(ChrW(e.KeyData))
swLb.Restart()
Return
End If
e.SuppressKeyPress = True
sbCLb.Append(ChrW(e.KeyData))
Dim idx = checkedList.FindString(sbCLb.ToString())
checkedList.SelectedIndex = If(idx = ListBox.NoMatches, checkedList.SelectedIndex, idx)
swLb.Restart()
End Sub

Setting tab stop between two panels

I have two panels, each has several textboxes. What I want is something that seems very simple: user enter a textbox in this panel, then press Tab to jump to its 'linked' textbox in the other panel.
But, vb refuses to jump to other panel unless it finishes through all of the textboxes inside one panel, no matter what TapStop it is.
I tried to catch the tab key from a textbox, and send focus to the linked one without success: pressing Tab doesn't even fire the KeyDown nor KeyPress event.
I tried to set TabStop to the panels first, but that also fail.
So, the problem remain.. how to set tabstop.. or any similar means, to the textboxes between two panel so that when user press Tab in one panel, it will switch to the other one ?
I need the Tab key, not anyother key.
You'll have to set the TabStop property of each control on the form to False and then handle the tabbing yourself, which you can do like this:
Private Sub TextBoxes_PreviewKeyDown(sender As Object, e As PreviewKeyDownEventArgs) Handles TextBox6.PreviewKeyDown,
TextBox5.PreviewKeyDown,
TextBox4.PreviewKeyDown,
TextBox3.PreviewKeyDown,
TextBox2.PreviewKeyDown,
TextBox1.PreviewKeyDown
If e.KeyCode = Keys.Tab Then
Dim controls As Control() = {TextBox1, TextBox4, TextBox2, TextBox5, TextBox3, TextBox6, Button2}
Dim currentControlIndex = Array.IndexOf(controls, ActiveControl)
Dim nextControl = controls(currentControlIndex + 1)
nextControl.Select()
End If
End Sub
Every control you want to be able to Tab from must be in the Handles clause and every control that you want to be able to Tab to or from must be in the array and in the order you want to Tab to them in. You should also repeat the first control at the end of the array again, in order to wrap back to the beginning from the end.
Also note that no control will be selected by default if none of them a Tab stops, in which case you must manually Select the control you want to have focus by default in the form's Shown event handler.
EDIT: Here is a more complete example:
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
'Manually focus the first control after the form is displayed.
Button1.Select()
End Sub
'Include all the controls that you want to behave as Tab stops in the Handles clause.
'The order is unimportant but ordering them you will Tab to them is not a bad idea.
Private Sub TextBoxes_PreviewKeyDown(sender As Object, e As PreviewKeyDownEventArgs) Handles Button1.PreviewKeyDown,
TextBox1.PreviewKeyDown,
TextBox4.PreviewKeyDown,
TextBox2.PreviewKeyDown,
TextBox5.PreviewKeyDown,
TextBox3.PreviewKeyDown,
TextBox6.PreviewKeyDown,
Button2.PreviewKeyDown
If e.KeyCode = Keys.Tab Then
'This array must contain all controls to behave as Tab stops in order and the first must be repeated at the end.
Dim controls As Control() = {Button1, TextBox1, TextBox4, TextBox2, TextBox5, TextBox3, TextBox6, Button2, Button1}
'Find the currently active control in the array.
Dim currentControlIndex = Array.IndexOf(controls, ActiveControl)
'Get the next control in the manual tab order.
Dim nextControl = controls(currentControlIndex + 1)
'Focus that next control.
nextControl.Select()
End If
End Sub
That code works for the following form, where TextBox1, TextBox2 and TextBox3 are in Panel1 and TextBox4, TextBox5 and TextBox6 are in Panel2:

Remove textbox that added dynamically (VB.Net)

I am trying to delete a TextBox control (that added dynamically) in my form through Button_Click event (that I added dynamically too) but I cannot find the exact way to do it. My TextBox will added together with Button control (delete button) when a LinkLabel is clicked. So when added dynamically my textbox.name will be like textbox_1,textbox_2,textbox_3 and along with them is a Button control like btnDel1,btnDel2,btnDel3 (all placed in a Panel control).
My coding goes like this :
Private Sub Button_Click(sender As Object, e As EventArgs)
Dim button As Button = TryCast(sender, Button)
Dim textbox As TextBox = TryCast(sender, TextBox)
'In this case when btnDel1 is clicked, textbox_1 will be removed as well
If button.Name = "btnDel1" Then
PanelOthers.Controls.Remove(button)
End If
End Sub
Button is removed successfully but how do I remove the textbox too? Thanks in advance.
There are a couple ways to do this:
Attach all the relevant controls to the delete button's Tag property.
Create a user control that encapsulates the button and textbox.
Option 1: the Tag property
When you create your controls, add the associated controls to the button's .Tag property:
Dim button As Button = New Button
Dim textbox As TextBox = New TextBox
button.Tag = {textbox}
' Add the button and textbox to the UI surface
Now when the button is clicked you can loop over the associated controls and remove them too:
For Each item As Control In button.Tag
item.Dispose()
Next
button.Dispose()
Option 2: A user control
So isn't a tutorial site.. but you can do your own research on this one.
Here's a place to start: https://msdn.microsoft.com/en-us/library/c316f119%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396

Visual Basic - Performing an action on an object that has the focus

I'm trying to do something very simple:
Create a method that handles multiple textbox.gotFocus() events in my form.
The goal is to select all text when the focus is given to any of the textboxes.
I know I could create an if-else block that checks to see whether or not each textbox has the focus, and then if so, just do textbox1.selectall(), or textbox2.selectall(), etc.
Is there a quicker way to do this that I'm missing that would just do it all in one statement that uses something like object.focused.selectall()? I know these aren't keywords in vb, but they're the best descriptive words I can think of to explain what I'm trying to do.
Any input is appreciated. Thanks!
What you want to do is more complicated than you think. You would have to create an event to trigger the method that handles the textboxes focus. The easy way is to put as you said a selectAll() method in every textbox gotFocus event.
Nothing built in to do that, unless you inherit from the TextBox and call SelectAll in the OnEnter override method yourself.
If you have a lot of TextBox controls, you could just iterate over the collections and add the method yourself:
Public Sub New()
InitializeComponent()
Dim ctrls As New Stack(Of Control)
ctrls.Push(Me)
While ctrls.Count > 0
Dim ctrl As Control = ctrls.Pop
If ctrl.Controls.Count > 0 Then
For Each c As Control In ctrl.Controls
ctrls.Push(c)
Next
Else
If TypeOf ctrl Is TextBox Then
AddHandler ctrl.Enter, Sub() DirectCast(ctrl, TextBox).SelectAll()
End If
End If
End While
End Sub
Since textbox doesn't have a focus event, what you can do is set up one handler to handle all the textboxes' Enter event.
Enter Event Handler
Private Sub TextboxEnter(Sender As Object, E As EventArgs)
Dim FocusedTextbox As TextBox = DirectCast(Sender, TextBox)
FocusedTextBox.SelectAll
End Sub
Add the handler for each textboxes' Enter event
For Each tb As TextBox in Me.Controls.OfType(Of TextBox)
AddHandler tb.Enter, AddressOf TextBoxEnter
Next
If you need to filter the textboxes, use a common name pattern and use a conditional to check for that pattern in the Name property.

Change a checkbox text and function based on checkbox.checkstate in groupbox

I'm trying to teach myself VB .net and as my first project I'm trying to design a form that functions much like the checkboxes in Gmail. Tons of checkboxes in a group and one checkbox that sits outside the group to select/deselect those within.
I've gotten far enough to have that master checkbox do its thing, but I would really like to have the form notice whenever anything within the groupbox is checked by the user, then to change its text & function automatically. The code I came up with to change the text works, but I can't figure out where to put it:
For Each ctrl As CheckBox In GroupBox1.Controls
If ctrl.CheckState = 1 Then
CheckBox1.Text = "Deselect All"
End If
Next
I can link the code to a button push or a checkbox change, but I'd like it to be automatic since having the user click something to run the check defeats the purpose. I tried double clicking the groupbox and placing the code there but it does nothing. Also tried double clicking the form background but it does nothing there either. Please help.
As you have probably noticed, there may be a few different places where you need to do this. To reuse a piece of functionality, create a new method that does that job. Double-click the form, and place this just before the End Class:
''' <summary>Update each of the CheckBoxes that in the same GroupBox</summary>
''' <param name="sender">The CheckBox that was clicked.</param>
''' <param name="e"></param>
''' <remarks>It is assumed that only checkboxed that live in a GroupBox will use this method.</remarks>
Public Sub UpdateCheckBoxState(ByVal sender As System.Object, ByVal e As System.EventArgs)
'Get the group box that the clicked checkbox lives in
Dim parentGroupBox As System.Windows.Forms.GroupBox = DirectCast(DirectCast(sender, System.Windows.Forms.CheckBox).Parent, System.Windows.Forms.GroupBox)
For Each ctrl As System.Windows.Forms.Control In parentGroupBox.Controls
'Only process the checkboxes (in case there's other stuff in the GroupBox as well)
If TypeOf ctrl Is System.Windows.Forms.CheckBox Then
'This control is a checkbox. Let's remember that to make it easier.
Dim chkBox As System.Windows.Forms.CheckBox = DirectCast(ctrl, System.Windows.Forms.CheckBox)
If chkBox.CheckState = 1 Then
chkBox.Text = "Deselect All"
Else
chkBox.Text = "Select All"
End If
End If ' - is CheckBox
Next ctrl
End Sub
Now you have a method that will do what you want, you need to connect it to each CheckBox that you want to manage. Do this by adding the following code in The Form_Load event:
AddHandler CheckBox1.CheckedChanged, AddressOf UpdateCheckBoxState
AddHandler CheckBox2.CheckedChanged, AddressOf UpdateCheckBoxState
...
So now the same method will handle the ClickChanged method of all of your connected checkboxes.
You can also update the checkBoxes in addition to when the user clicks it by calling the Method UpdateCheckBoxState(CheckBoxThatYouWantToProgramaticallyUpdate, Nothing) perhaps in Form_Load, or elsewhere.