ComboBox SelectChangeCommited fires off with first keyboard input - vb.net

I have a ComboBox. My issue is when I click on element 1, then start typing the name of a element 2, the first letter of my element 2 gets added. This only happens on the key down after a click event.
Process:
Click on ComboBox
Type in a name
Click on the elements name - element gets added to my grid
Type in another name - ISSUE - first element that matches the first letter I input gets added to the grid
Clicking and the enter key on a single selected element works fine.
My AutoCompleteMode is set to Append and AutoCompleteSource is ListItems
The solution I thought to control a click is in my SelectionChangeComitted function. If I can control the flag isCLick to only be set true when we know that a element from the combo box is clicked by the mouse. MouseClick will return true if the user clicks on the overall ComboBox therefore will always be true if we're dealing with this particular ComboBox. Since the parameter is EventArgs I cannot directly cast it to MouseEventArgs, would be great if I could.
To sum up what my problem is: I need to replace isClick boolean in my SelectionChangeCommitted to validate if the SelectionChangeCommitted was triggered by a click or not.
Is that possible in this case? Would there be any alternatives to bypass this issue?
I have event handler code below, the sequence of the call is: MouseClick -> PreviewKeyDown -> SelectionChangeCommitted -> SelectedValueChanged
http://pastebin.com/9zWGHbWE
AddHandler CType(ctl, ComboBox).PreviewKeyDown, Sub(sender As Object, e As PreviewKeyDownEventArgs)
If e.KeyCode = Keys.Down OrElse e.KeyCode = Keys.Up OrElse e.KeyCode = Keys.Right OrElse e.KeyCode = Keys.Left Then
e.IsInputKey = False
AddToGrid = False
isArrowKey = True
isClick = False
ElseIf e.KeyCode = Keys.Enter OrElse e.KeyCode = Keys.Tab Then
AddToGrid = True
isArrowKey = False
isClick = False
'Check if row already exists before insert a new one.
If Not RowExists(DirectCast(ctl, Control).Tag, DirectCast(ctl, ComboBox).Text.ToUpper()) Then AppendGrid(sender)
Else
AddToGrid = False
isArrowKey = False
isClick = True
End If
End Sub
AddHandler CType(ctl, ComboBox).SelectedValueChanged, Sub(sender As Object, e As EventArgs)
'MessageBox.Show("SelectionValueChanged")
If CType(sender, ComboBox).SelectedValue Is Nothing Then Return
If Not AddToGrid Then Return
'Check if row already exists before insert a new one.
If Not RowExists(DirectCast(ctl, Control).Tag, DirectCast(ctl, ComboBox).SelectedValue.ToString().ToUpper()) Then
AppendGrid(sender)
End If
End Sub
AddHandler CType(ctl, ComboBox).SelectionChangeCommitted, Sub(sender As Object, e As EventArgs)
'MessageBox.Show("SelectionChangedCommitted")
If CType(sender, ComboBox).SelectedValue Is Nothing Then Return
If isArrowKey Then Return 'if an arrow was used, then don't add it to the grid
If isClick Then
AddToGrid = True
End If
'Check if row already exists before insert a new one.
If Not RowExists(DirectCast(ctl, Control).Tag, DirectCast(ctl, ComboBox).SelectedValue.ToString().ToUpper()) AndAlso AddToGrid Then
AppendGrid(sender)
End If
End Sub
AddHandler CType(ctl, ComboBox).MouseClick, Sub(sender As Object, e As MouseEventArgs)
'MessageBox.Show("MouseClick")
isClick = True
AddToGrid = True
End Sub

It seems like everything here works fine for you except when a click is executed. I would recommend you to reset the click after you use it. Try the code below in your SelectionChangeCommitted.
If isClick Then
AddToGrid = True
isClick = false
End If

Related

VB.NET DataGridView End Edits and Cell Movment

I have created a DGV adding CheckBoxColumns and TextBoxColumns. When the user enters a text value in to a TextBoxCell it then takes 2 mouse clicks to move to the next clicked cell, is there a way to allow a single mouse click to allow the next value to be entered in the selected cell straight away?
Also trying to use the Arrow Up/Down keys after entering a value doesn't move to the relevant new cell? When i use the arrow key is seems to end the edit but doesn't move to the next cell, it looks like the DGV loses focus as the mouse cursor appears.
I need both of these to work and to allow for immediate edit/ data entry as each text cell is for user input and not being able to use mouse keys to quickly navigate or having to double click with the mouse is causing a lot of wasted time.
Any help regarding these two issue would be much appreciated!
Additional Info::
This is what the grid looks like::
Grid Layout Image
The first 2 columns are data populated from a previous form and the data is held in a DataTable, the rest of the columns are added via the program
With the 3 text columns it takes two clicks on the next cell to move to the requested cell and start to edit if something has been typed, again if something has been typed in a cell and i use the arrow keys doesn't seem to take you to the next cell, it leaves the cell put doesn't start to edit or put the cursor in the expected cell.
What i am looking for is when i have entered a value in to the Text Columns and click on the next cell it starts to edit straight away or if i use an arrow key it takes you to the relevant cell and starts the edit.
Below is the full code for the form i have an issue with, i have also added a screenshot of the DGV Properties as i bet all of this is fixed by something simple.
DGV Settings
Public Class frmAppTest
Public DTApp As DataTable = New DataTable("Application")
Private Sub frmAppTest_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Call InitializeAppTable()
'Add each column required for user input (inc dropdown option and "Select All" option)
Call Add_ChkColumn("Team Crest", "TEAM_CREST")
Call AddSAChkBox(dgvApp, 186, 57, 2, "TC")
Call AddCrestSelect(dgvApp, 165, 33, "Crest")
Call Add_ChkColumn("Chest Logo", "CHEST_LOGO")
Call AddSAChkBox(dgvApp, 246, 57, 3, "CL")
Call AddSponsorSelect(dgvApp, 225, 33, "Chest")
Call Add_ChkColumn("Back" & vbCr & "Top", "BACK_TOP_LOGO")
Call AddSAChkBox(dgvApp, 306, 57, 4, "BT")
Call AddSponsorSelect(dgvApp, 285, 33, "BackTop")
Call Add_ChkColumn("Back Bottom", "BACK_BOTTOM_LOGO")
Call AddSAChkBox(dgvApp, 366, 57, 5, "BB")
Call AddSponsorSelect(dgvApp, 345, 33, "BackBottom")
Call Add_ChkColumn("Left Sleeve", "LEFT_SLEEVE_LOGO")
Call AddSAChkBox(dgvApp, 426, 57, 6, "LS")
Call AddSponsorSelect(dgvApp, 405, 33, "LeftSlv")
Call Add_ChkColumn("Right Sleeve", "RIGHT_SLEEVE_LOGO")
Call AddSAChkBox(dgvApp, 486, 57, 7, "RS")
Call AddSponsorSelect(dgvApp, 465, 33, "RightSlv")
Call Add_TxtColumn("Player Name", "PLAYER_NAME")
Call AddColourSelect(dgvApp, 528, 33, "Name")
Call Add_TxtColumn("Player Number", "PLAYER_NUMBER")
Call AddColourSelect(dgvApp, 638, 33, "Number")
Call Add_TxtColumn("Player Initials", "PLAYER_INITIALS")
Call AddColourSelect(dgvApp, 748, 33, "Initials")
Call FormatDGVApp()
'Populate default data
Call PopulateDetails()
End Sub
Private Sub AddCrestSelect(ByVal theDataGridView As DataGridView, ByVal XLocation As Integer, ByVal YLocation As Integer, ByVal CboName As String)
Dim cbo As New ComboBox
cbo.Name = "cboLogo" & CboName
'The box size
cbo.DropDownStyle = ComboBoxStyle.DropDownList
cbo.Visible = True
cbo.Items.Clear()
cbo.Items.Add("Embro")
cbo.Items.Add("Heat")
cbo.Size = New Size(55, 14)
cbo.SelectedIndex = 0
cbo.Location = New System.Drawing.Point(XLocation, YLocation)
cbo.BackColor = Color.White
theDataGridView.Controls.Add(cbo)
End Sub
Private Sub AddSponsorSelect(ByVal theDataGridView As DataGridView, ByVal XLocation As Integer, ByVal YLocation As Integer, ByVal CboName As String)
Dim cbo As New ComboBox
cbo.Name = "cboLogo" & CboName
'The box size
cbo.DropDownStyle = ComboBoxStyle.DropDownList
cbo.Visible = True
cbo.Items.Clear()
cbo.Items.Add("Single")
cbo.Items.Add("Multi")
cbo.Size = New Size(55, 14)
cbo.SelectedIndex = 0
cbo.Location = New System.Drawing.Point(XLocation, YLocation)
cbo.BackColor = Color.White
theDataGridView.Controls.Add(cbo)
End Sub
Private Sub AddColourSelect(ByVal theDataGridView As DataGridView, ByVal XLocation As Integer, ByVal YLocation As Integer, ByVal CboName As String)
Dim cbo As New ComboBox
cbo.Name = "cboColour" & CboName
'The box size
cbo.DropDownStyle = ComboBoxStyle.DropDownList
cbo.Visible = True
cbo.Items.Clear()
cbo.Items.Add("White")
cbo.Items.Add("Black")
cbo.Items.Add("Royal")
cbo.Items.Add("Navy")
cbo.Items.Add("Yellow")
cbo.Items.Add("Red")
cbo.Size = New Size(100, 14)
cbo.SelectedIndex = 0
cbo.Location = New System.Drawing.Point(XLocation, YLocation)
cbo.BackColor = Color.White
theDataGridView.Controls.Add(cbo)
End Sub
Private Sub InitializeAppTable()
DTApp.Columns.Add("Size", GetType(String))
DTApp.Columns.Add("Price", GetType(String))
dgvApp.DataSource = DTApp
End Sub
Private Sub FormatDGVApp()
Dim xCol As New DataGridViewColumn
dgvApp.Columns("Size").Width = 70
dgvApp.Columns("Size").ReadOnly = True
dgvApp.Columns("Size").SortMode = DataGridViewColumnSortMode.NotSortable
dgvApp.Columns("Size").HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter
dgvApp.Columns("Size").DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
dgvApp.Columns("Price").Width = 70
dgvApp.Columns("Price").ReadOnly = True
dgvApp.Columns("Price").SortMode = DataGridViewColumnSortMode.NotSortable
dgvApp.Columns("Price").HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter
dgvApp.Columns("Price").DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
dgvApp.ColumnHeadersHeight = 74
dgvApp.Font = New Font("Arial", 7)
End Sub
Private Sub PopulateDetails()
Dim AppRow As DataRow
Dim i As Integer
For i = 0 To 4
AppRow = DTApp.NewRow
AppRow("Size") = "S"
AppRow("Price") = "5.00"
DTApp.Rows.Add(AppRow)
Next
End Sub
Private Sub Add_ChkColumn(ByVal Header As String, ByVal Name As String)
Dim AddColumnLabelExport As New DataGridViewCheckBoxColumn
With AddColumnLabelExport
.HeaderText = Header
.Name = Name
.Width = 60
.HeaderCell.Style.Alignment = DataGridViewContentAlignment.TopCenter
.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
.SortMode = DataGridViewColumnSortMode.NotSortable
End With
dgvApp.Columns.Add(AddColumnLabelExport)
End Sub
Private _IsSelectAllChecked As Boolean
Private Sub AddSAChkBox(ByVal theDataGridView As DataGridView, ByVal XLocation As Integer, ByVal YLocation As Integer, ByVal TagNo As Integer, ByVal ChkName As String)
Dim cbx As New CheckBox
cbx.Name = "SelectAll" & ChkName
'The box size
cbx.Size = New Size(14, 14)
cbx.Tag = TagNo
cbx.Location = New System.Drawing.Point(XLocation, YLocation)
cbx.BackColor = Color.White
theDataGridView.Controls.Add(cbx)
AddHandler cbx.Click, AddressOf HeaderCheckBox_Click
AddHandler theDataGridView.CellValueChanged, AddressOf DataGridView_CellChecked
AddHandler theDataGridView.CurrentCellDirtyStateChanged, AddressOf DataGridView_CurrentCellDirtyStateChanged
End Sub
Private Sub HeaderCheckBox_Click(ByVal sender As Object, ByVal e As EventArgs)
Me._IsSelectAllChecked = True
Dim cbx As CheckBox
cbx = DirectCast(sender, CheckBox)
Dim theDataGridView As DataGridView = cbx.Parent
Dim rowId As Integer = cbx.Tag
For Each row As DataGridViewRow In dgvApp.Rows
row.Cells(rowId).Value = cbx.Checked
Next
theDataGridView.EndEdit()
Me._IsSelectAllChecked = False
End Sub
Private Sub DataGridView_CellChecked(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs)
dgvApp.EndEdit()
Dim dataGridView As DataGridView = DirectCast(sender, DataGridView)
If Not Me._IsSelectAllChecked Then
Select Case e.ColumnIndex
Case 2
If dataGridView.Rows(e.RowIndex).Cells(2).Value = False Then
'When any single CheckBox is unchecked, uncheck the header CheckBox.
DirectCast(dataGridView.Controls.Item("SelectAllTC"), CheckBox).Checked = False
Else
'When any single CheckBox is checked, loop through all CheckBoxes to determine
'if the header CheckBox needs to be unchecked.
Dim isAllChecked As Boolean = True
For Each row As DataGridViewRow In dataGridView.Rows
If row.Cells(2).Value = False Then
isAllChecked = False
Exit For
End If
Next
DirectCast(dataGridView.Controls.Item("SelectAllTC"), CheckBox).Checked = isAllChecked
End If
Case 3
If dataGridView.Rows(e.RowIndex).Cells(3).Value = False Then
'When any single CheckBox is unchecked, uncheck the header CheckBox.
DirectCast(dataGridView.Controls.Item("SelectAllCL"), CheckBox).Checked = False
Else
'When any single CheckBox is checked, loop through all CheckBoxes to determine
'if the header CheckBox needs to be unchecked.
Dim isAllChecked As Boolean = True
For Each row As DataGridViewRow In dataGridView.Rows
If row.Cells(3).Value = False Then
isAllChecked = False
Exit For
End If
Next
DirectCast(dataGridView.Controls.Item("SelectAllCL"), CheckBox).Checked = isAllChecked
End If
Case 4
If dataGridView.Rows(e.RowIndex).Cells(4).Value = False Then
'When any single CheckBox is unchecked, uncheck the header CheckBox.
DirectCast(dataGridView.Controls.Item("SelectAllBT"), CheckBox).Checked = False
Else
'When any single CheckBox is checked, loop through all CheckBoxes to determine
'if the header CheckBox needs to be unchecked.
Dim isAllChecked As Boolean = True
For Each row As DataGridViewRow In dataGridView.Rows
If row.Cells(4).Value = False Then
isAllChecked = False
Exit For
End If
Next
DirectCast(dataGridView.Controls.Item("SelectAllBT"), CheckBox).Checked = isAllChecked
End If
Case 5
If dataGridView.Rows(e.RowIndex).Cells(5).Value = False Then
'When any single CheckBox is unchecked, uncheck the header CheckBox.
DirectCast(dataGridView.Controls.Item("SelectAllBB"), CheckBox).Checked = False
Else
'When any single CheckBox is checked, loop through all CheckBoxes to determine
'if the header CheckBox needs to be unchecked.
Dim isAllChecked As Boolean = True
For Each row As DataGridViewRow In dataGridView.Rows
If row.Cells(5).Value = False Then
isAllChecked = False
Exit For
End If
Next
DirectCast(dataGridView.Controls.Item("SelectAllBB"), CheckBox).Checked = isAllChecked
End If
Case 6
If dataGridView.Rows(e.RowIndex).Cells(6).Value = False Then
'When any single CheckBox is unchecked, uncheck the header CheckBox.
DirectCast(dataGridView.Controls.Item("SelectAllLS"), CheckBox).Checked = False
Else
'When any single CheckBox is checked, loop through all CheckBoxes to determine
'if the header CheckBox needs to be unchecked.
Dim isAllChecked As Boolean = True
For Each row As DataGridViewRow In dataGridView.Rows
If row.Cells(6).Value = False Then
isAllChecked = False
Exit For
End If
Next
DirectCast(dataGridView.Controls.Item("SelectAllLS"), CheckBox).Checked = isAllChecked
End If
Case 7
If dataGridView.Rows(e.RowIndex).Cells(7).Value = False Then
'When any single CheckBox is unchecked, uncheck the header CheckBox.
DirectCast(dataGridView.Controls.Item("SelectAllRS"), CheckBox).Checked = False
Else
'When any single CheckBox is checked, loop through all CheckBoxes to determine
'if the header CheckBox needs to be unchecked.
Dim isAllChecked As Boolean = True
For Each row As DataGridViewRow In dataGridView.Rows
If row.Cells(7).Value = False Then
isAllChecked = False
Exit For
End If
Next
DirectCast(dataGridView.Controls.Item("SelectAllRS"), CheckBox).Checked = isAllChecked
End If
End Select
End If
dgvApp.CurrentCell = Nothing
End Sub
Private Sub DataGridView_CurrentCellDirtyStateChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)
Dim dataGridView As DataGridView = DirectCast(sender, DataGridView)
If TypeOf dataGridView.CurrentCell Is DataGridViewCheckBoxCell Then
dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit)
End If
End Sub
Private Sub Add_TxtColumn(ByVal Header As String, ByVal Name As String)
Dim AddColumnLabelExport As New DataGridViewTextBoxColumn
With AddColumnLabelExport
.HeaderText = Header
.Name = Name
.Width = 110
.HeaderCell.Style.Alignment = DataGridViewContentAlignment.TopCenter
.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
.SortMode = DataGridViewColumnSortMode.NotSortable
End With
dgvApp.Columns.Add(AddColumnLabelExport)
End Sub
Private Sub btnCancelLine_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancelLine.Click
Me.Close()
End Sub
End Class
Well… the updated code explains numerous issues you may be having. The big picture is that you need to take a little more care when subscribing to some events. It is not difficult to create a problem if you do not take care in checking when and how often your subscribed to events are firing.
Example, in your current code you have the method…
Private Sub AddSAChkBox(ByVal theDataGridView As DataGridView, ByVal XLocation As Integer, ByVal YLocation As Integer, ByVal TagNo As Integer, ByVal ChkName As String)
Dim cbx As New CheckBox
cbx.Name = "SelectAll" & ChkName
'The box size
cbx.Size = New Size(14, 14)
cbx.Tag = TagNo
cbx.Location = New System.Drawing.Point(XLocation, YLocation)
cbx.BackColor = Color.White
theDataGridView.Controls.Add(cbx)
AddHandler cbx.Click, AddressOf HeaderCheckBox_Click
AddHandler theDataGridView.CellValueChanged, AddressOf DataGridView_CellChecked
AddHandler theDataGridView.CurrentCellDirtyStateChanged, AddressOf DataGridView_CurrentCellDirtyStateChanged
End Sub
This code drops a check box into the grid and is called six (6) times. Once for each of the “select all” Header check boxes. Is what is incorrect are the last two lines of code…
AddHandler theDataGridView.CellValueChanged, AddressOf DataGridView_CellChecked
AddHandler theDataGridView.CurrentCellDirtyStateChanged, AddressOf DataGridView_CurrentCellDirtyStateChanged
This is odd because each time the method is called a NEW handler is added to the grid. In other words, the code is subscribing to the same GRID event’s six (6) times.
If you put a couple of debug statements at the beginning and the end of the events, you will see that the events will obligingly fire five more times than needed.
Bottom line… these GRID events only need to be subscribed to ONCE. I suggest moving the code to the Forms Load event or some method that is only called once.
Next, your current issue relates to the fact that when you type text into the other non-check box cells, the wired-up grid events are still firing and you really do not want this when editing those non-check box cells. So, a simple solution is to check and make sure that when those events fire… that we do nothing if the cell is not one of the check box cells in the grid.
Currently the code is using the grids CellValueChanged event and its CurrentCellDirtyStateChanged event. In the current cell dirty state changed event, the code simply “commits” the edit to the cell which in turn will fire the grids CellValueChanged event where the code checks the check box cells. As you note… the current cell dirty state changed event will fire BEFORE the actual cell value is changed and this is the purpose of the commit so we can immediately see the changes in the grid.
I suggest you use the grids, CellContentClick event instead of the CurrentCellDirtyStateChanged event. This event will fire when the user clicks into a cell and starts to edit the cell. It will fire when the user clicks a check box cell and changes its value. We do not need to do anything other than check if the cell is a check box cell and commit the edit. If the cell is NOT a check box cell then do nothing. This event may look something like…
Private Sub DataGridView_CellContentClick(ByVal sender As System.Object, ByVal e As System.EventArgs)
If (dgvApp.CurrentCell.ColumnIndex < 2 Or dgvApp.CurrentCell.ColumnIndex > 7) Then
Return
End If
Debug.WriteLine("DataGridView_CellContentClick <- Enter")
dgvApp.CommitEdit(DataGridViewDataErrorContexts.Commit)
Debug.WriteLine("DataGridView_CellContentClick -> Leave")
End Sub
This will work when the check boxes are changed and it will ignore the non-check box cells, however if the user is editing a non-check box cell, then the grids CellValueChanged event will still fire if the non-check box cells value changed.… So similar to the above code, we need to check in the grids CellValueChanged event to see which cell value changed. If it is NOT a check box cell that changed, then we do not want to do anything. Therefore you could add the same check using the e.RowIndex and e.ColumnIndex variables… in the grids CellValueChanged event… Something like…
Private Sub DataGridView_CellChecked(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs)
If (e.ColumnIndex < 2 Or e.ColumnIndex > 7) Then
Return
End If
….. ‘rest of code
Another issue which I wish I had a good answer for is that… if you call the HeaderCheckBox_Click event that fires when the user clicks one of the “select all” header check boxes, then you may note that… IF the user has clicked into a check box cell and changed its value, then this will make that cell the grids CurrentCell and for some reason, it will not change its value as expected. I was only able to code a workaround that simply changes the grids CurrentCell to some other cell, then sets it back after the code changes the check box cell values. The changes I made may look something like…
Private Sub HeaderCheckBox_Click(ByVal sender As Object, ByVal e As EventArgs)
Debug.WriteLine("HeaderCheckBox <- Enter")
Dim curCellRowIndex = dgvApp.CurrentCell.RowIndex
Dim curCellColIndex = dgvApp.CurrentCell.ColumnIndex
dgvApp.CurrentCell = dgvApp.Rows(0).Cells(0)
Dim cbx As CheckBox
cbx = DirectCast(sender, CheckBox)
Dim rowId As Integer = cbx.Tag
RemoveHandler dgvApp.CellValueChanged, AddressOf DataGridView_CellValueChanged
For Each row As DataGridViewRow In dgvApp.Rows
row.Cells(rowId).Value = cbx.Checked
Next
AddHandler dgvApp.CellValueChanged, AddressOf DataGridView_CellValueChanged
dgvApp.CurrentCell = dgvApp.Rows(curCellRowIndex).Cells(curCellColIndex)
Debug.WriteLine("HeaderCheckBox -> Leave")
End Sub
You may note that just before the For Each loop is started through the grid rows, we remove the GRIDS CellValueChanged event to prevent it from firing. We do not want it to fire at this time since we are checking or unchecking all the check boxes, therefore no checking is needed to see if they are all checked or not checked.
In my tests, these changes allowed the non-check box cells to work as expected. I hope this makes sense and helps. You may want to consider simplifying this. Example, in each of the Select/Case sections the code is doing the same thing with a different column index. I suggest creating a simple method that checks if all the check boxes are checked in a given column and return true/false. And I hope you do not plan on any horizontal scrolling of the grid as this will create a lot of work for you to scroll the added combo boxes, labels and check boxes you added to the grid… just a heads up.

Use ENTER or RETURN as TAB in form and panels

My form as multiple controls like Textboxes and Panels, and Textboxes in Panels, which cause problem. I try to make keys ENTER and RETURN do the same as TAB, so select next control, but for an unknown reason if I i go from any control to a panel, it doesn't enter the first control in the panel, it skips to the next control which isn't a panel.
My form key preview is already True and my tab index are okay :
First textbox is 10, first panel 11, first textbox of panel 12. For now it skips to 20, next textbox not in a panel.
Code based on this question : Tab Key Functionality Using Enter Key in VB.Net
Here is my code
Private Sub Values_KeyDown(ByVal sender As Control,
ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
If e.KeyCode = Keys.Return Or e.KeyCode = Keys.Enter Then
If TypeOf Me.GetNextControl(Me.ActiveControl, True) Is Panel Then
Me.SelectNextControl(CType(Me.ActiveControl, Panel).Controls.Item(0), True, True, False, True)
Else
Me.SelectNextControl(Me.ActiveControl, True, True, False, True)
End If
e.Handled = True
End If
End Sub
Thanks!
I don't really understand the code snippet, it looks like the last attempt before giving up. Nor how it got to run at all, KeyPreview is not good enough to intercept KeyDown for the navigation keys like the Enter key. The nested argument for SelectNextControl() should not certainly not be False, you do want to consider controls that are nested inside a panel as the next tab target, presumably what made the code jump off the rails.
I'll post a more universal solution that does not depend on KeyPreview and still properly deals with controls that need the Enter key to function correctly. Simply copy/paste it into the form, it does not use events:
Protected Overrides Function ProcessCmdKey(ByRef msg As Message, keyData As Keys) As Boolean
Dim dotab = False
Dim ctl = Me.ActiveControl
If ctl IsNot Nothing And keyData = Keys.Enter Then
dotab = True
If TypeOf ctl Is TextBoxBase Then
If DirectCast(ctl, TextBoxBase).Multiline Then dotab = False
End If
End If
If dotab Then
If Me.SelectNextControl(ctl, True, True, True, True) Then Return True
End If
Return MyBase.ProcessCmdKey(msg, keyData)
End Function
With Hans' answer i manage to make it work by simply changing the nested parameter to true and get rid of the part that was suppose to make it work with panels, like this:
Private Sub Values_KeyDown(ByVal sender As Control, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
If e.KeyCode = Keys.Return Or e.KeyCode = Keys.Enter Then
Me.SelectNextControl(Me.ActiveControl, True, True, True, True)
e.Handled = True
End If
End Sub
You'll still need to put the Key Preview parameter on your form to True
I also found an alternative here : How to make Enter on a TextBox act as TAB button
Private Sub Values_KeyDown(ByVal sender As Control, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
If e.KeyCode = Keys.Return Or e.KeyCode = Keys.Enter Then
SendKeys.Send("{TAB}")
e.Handled = True
End If
End Sub
Use processTabKey(true) function

Managing `CheckBox_Checked` Event without an infinite loop

I know this seems like an easy fix, but I am having trouble. I have a CheckBox, when checked, I remove the data source of a DataGridView on my Windows Form and remove the ReadOnly properties of a few Textbox.
I know the CheckedChanged event will send my code into an infinite loop, but I cannot figure out which event would handle this change without changing the CheckedState each time. I have tried using Click, MouseClick, and CheckStateChanged events with no luck.
This is my current code:
Private Sub chkManual_MouseClick(sender As Object, e As EventArgs) Handles chkManual.MouseClick
If Not Me.chkManual.Checked Then
Me.chkManual.Checked = False
Me.cbRegion.SelectedIndex = -1
Me.txtIssueDate.ReadOnly = True
Me.txtCasenum.ReadOnly = True
Me.txtCommnum.ReadOnly = True
Exit Sub
Else
Me.dgDataEntry.DataSource = Nothing
Me.cbRegion.SelectedIndex = -1
Me.txtIssueDate.ReadOnly = False
Me.txtCasenum.ReadOnly = False
Me.txtCommnum.ReadOnly = False
ClearForm()
frmPDF.Hide()
Exit Sub
End If
End Sub
Properties of CheckBox: AutoCheck = True, Checked = False, and CheckState = Unchecked
I have looked into these already:
CheckBox_Checked event
Is there a simpler way to process check boxes?
CheckBox reverts to checked after being unchecked
How to check if a checkboxes state has changed
How can I prevent an assignment statement to CheckBox.Checked from raising the CheckChanged event?
http://www.vbforums.com/showthread.php?758455-CheckBox-code-got-stuck-in-an-infinite-loop-can-not-unchecked-it
EDIT
It helps if your ClearForm() sub doesn't change the CheckedState of your CheckBox back to False every time. Thank you #Visual Vincent for pointing out the obvious. Nothing is wrong with the code, changed the EventHandler to CheckedChanged
Final code (so simple):
Private Sub chkManual_CheckedChanged(sender As Object, e As EventArgs) Handles chkManual.CheckedChanged
If Me.chkManual.Checked Then
Me.dgDataEntry.DataSource = Nothing
Me.cbRegion.SelectedIndex = -1
Me.txtIssueDate.ReadOnly = False
Me.txtCasenum.ReadOnly = False
Me.txtCommnum.ReadOnly = False
ClearForm()
frmPDF.Hide()
Else
Me.cbRegion.SelectedIndex = -1
Me.txtIssueDate.ReadOnly = True
Me.txtCasenum.ReadOnly = True
Me.txtCommnum.ReadOnly = True
End If
End Sub
I'm not sure if you want the CheckBox, when Checked, to Uncheck again automatically or not!?...seems like a weird interface.
At any rate, if you want something to occur when the Check state changes:
Private Sub chkManual_CheckedChanged(sender As Object, e As EventArgs) Handles chkManual.CheckedChanged
If chkManual.Checked Then
Debug.Print("Checked")
Me.dgDataEntry.DataSource = Nothing
Me.cbRegion.SelectedIndex = -1
Me.txtIssueDate.ReadOnly = False
Me.txtCasenum.ReadOnly = False
Me.txtCommnum.ReadOnly = False
ClearForm()
frmPDF.Hide()
Else
Debug.Print("UnChecked")
Me.cbRegion.SelectedIndex = -1
Me.txtIssueDate.ReadOnly = True
Me.txtCasenum.ReadOnly = True
Me.txtCommnum.ReadOnly = True
End If
End Sub
You can uncheck the checkbox without an infinite loop like this:
Private Sub chkManual_CheckedChanged(sender As Object, e As EventArgs) Handles chkManual.CheckedChanged
Static counter As Integer
If chkManual.Checked Then
counter = counter + 1 ' just to show we're not in an infinite loop...
Debug.Print("Checked " & counter) ' just to show we're not in an infinite loop...
chkManual.Checked = False
Else
Debug.Print("UnChecked")
End If
End Sub
Not sure why you'd want to do that...seems like that would basically be a "reset" button as it couldn't stay in a checked state...
Can you remove the code that changes the checkbox state? Ex:
Me.chkManual.Checked = False
The box will check or uncheck without your code having to do it, and the event will only throw once.
I think the easiest way is to remove event listener before handling checkbox checked state. Use try-finally block to ensure that checkbox event listener is always set back.
Try
RemoveHandler chkManual AddressOf chkManual_MouseClick
Me.chkManual.Checked = False
...
Finally
AddHandler chkManual AddressOf chkManual_MouseClick
End Try

VB.NET 2010 DataGridView Handling Keypress via EditingControlShowing Event

I am working with a DataGridView for the first time and while I have MANY questions, this latest issue is vexing me.
Summary of issue:
I have a DataGridView (dgv) which I have a set of columns defined. Some readonly some editable.
For the editable columns I need four things to occur.
1) Allow Numeric entry
2) Allow maximum of 2 digits
3) Zero Pad any entries <2 digits
4) My ISSUE:
If the user types in a two digit number, I want to detect that and TAB to the next column. I cannot get this to work.
Sample code (with some known working items left out):
Private Sub dgvDiary_EditingControlShowing(sender As Object, e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles dgvDiary.EditingControlShowing
Dim txtEdit As TextBox = e.Control
txtEdit.MaxLength = 2
'remove any existing handler
RemoveHandler txtEdit.KeyPress, AddressOf txtdgvDiaryEdit_Keypress
AddHandler txtEdit.KeyPress, AddressOf txtdgvDiaryEdit_Keypress
End Sub
Private Sub txtdgvDiaryEdit_Keypress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs)
'Test for numeric value or backspace
If IsNumeric(e.KeyChar.ToString()) _
Or e.KeyChar = ChrW(Keys.Back) Then
e.Handled = False 'if numeric
Else
e.Handled = True 'if non numeric
End If
'If user typed in 2 characters, move on!
'Don't work!
If Strings.Len(Me.dgvDiary.Rows(Me.dgvDiary.CurrentRow.Index).Cells(Me.dgvDiary.CurrentCell.ColumnIndex).Value) = 2 Then
SendKeys.Send("{TAB}")
End If
End Sub
Basically during this event I'm not able to see what the value of the cell "will be" when entered.
I tried adding a ".RefreshEdit" and a ".Commit" but they didn't work.
Any way to test the code within this event OR is there an event that would fire IMMEDIATELY afterward that I can use?
You are looking in the wrong place. You need to examine the text in the TextBox, not the grid, to see how many characters are currently being typed. Try using the TextChanged event for that:
Private Sub txtdgvDiaryEdit_TextChanged(sender As Object, e As EventArgs)
If DirectCast(sender, TextBox).Text.Length = 2 Then
SendKeys.Send("{TAB}")
End If
End Sub
Like your other code, add the handlers:
'remove any existing handler
RemoveHandler txtEdit.TextChanged, AddressOf txtdgvDiaryEdit_TextChanged
AddHandler txtEdit.TextChanged, AddressOf txtdgvDiaryEdit_TextChanged
RemoveHandler txtEdit.KeyPress, AddressOf txtdgvDiaryEdit_KeyPress
AddHandler txtEdit.KeyPress, AddressOf txtdgvDiaryEdit_KeyPress
Alternatively, you can check to see if the TextBox only has one character, and if the KeyPress is passing in another number, send your Tab key then. You would remove the TextChanged event code in this case:
Private Sub txtdgvDiaryEdit_KeyPress(sender As Object, e As KeyPressEventArgs)
'Test for numeric value or backspace
If IsNumeric(e.KeyChar.ToString()) _
Or e.KeyChar = ChrW(Keys.Back) Then
e.Handled = False 'if numeric
Else
e.Handled = True 'if non numeric
End If
If DirectCast(sender, TextBox).Text.Length = 1 AndAlso Char.IsNumber(e.KeyChar) Then
SendKeys.Send("{TAB}")
End If
End Sub

Disabling buttons one at a time

My partner and I are trying to figure out how to disable a button one at a time. We're making a program in Visual Studio Express 2012 that will disable a button once it is typed in a textbox. For example, we have five letters placed seperately on five different buttons If we were to put the letter "D" on the textbox, the button that contains that specific letter will be disabled. We're using the code
If e.KeyCode = Keys.D Then
Button1.Enabled = False
End If
Now that works, BUT if there were two or more buttons that has the same letters, all of them disables because then the code will be :
If e.KeyCode = Keys.D Then
Button1.Enabled = False
End If
If e.KeyCode = Keys.D Then
Button2.Enabled = False
End If
My problem would be in what way could I distinguish those buttons that has the same letter from one another so that when I type the letter on a textbox, only one button disables and when I type it in again, another button containing the same letter disables. Thanks!
Assuming all of the buttons are not in child panels:
If e.KeyCode = Keys.D Then
For Each b As Button In Me.Controls.OfType(Of Button)()
If b.Text.Contains("D") AndAlso b.Enabled Then
b.Enabled = False
Exit For
End If
Next
End If
This will recursively iterate all controls on the form looking for buttons and disable them based on the characters and number of characters entered into the textbox:
Private Sub textBox1_TextChanged(sender As Object, e As System.EventArgs)
Dim text As String = TryCast(sender, TextBox).Text.ToLower()
For Each b As Button In GetAllButtons(Me)
b.Enabled = True
Next
For Each c As Char In text
Dim count As Integer = text.Count(Function(cc) cc = c)
For i As Integer = 0 To count - 1
For Each b As Button In GetAllButtons(Me).Where(Function(x) x.Text.ToLower().Contains(c.ToString())).Take(count).ToList()
b.Enabled = False
Next
Next
Next
End Sub
Private Function GetAllButtons(control As Control) As List(Of Button)
Dim allButtons As New List(Of Button)()
If control.HasChildren Then
For Each c As Control In control.Controls
allButtons.AddRange(GetAllButtons(c))
Next
ElseIf TypeOf control Is Button Then
allButtons.Add(TryCast(control, Button))
End If
Return allButtons
End Function