KeyPress
is the wrong event. You should handle the Validating
event, which occurs when the user tries to leave the control. At that point, you can check that the input is a number and that it's unique. If it's not, you call SelectAll
on the TextBox
and set e.Cancel
to True
to prevent the control losing focus.
When handling keypress events, I can't type over a selection
Question
OK I have a series of textboxes that will only accept numeric values from 1 to 49 - with a unique number in each textbox. When validating, if there is a duplicate number in one of the boxes, I want the focus to be set to the offending textbox with it's text highlighted, so that i can just start typing and the selection will disappear. Problem (I think) is that the new keypress event is being added to the text before the selection can be removed. in searching Google for a solution, I was pointed to a post on this site which ultimately came up with this code posted by Robert Barnes
Private Sub txtScreen_KeyPress(sender As Object, e As KeyPressEventArgs) Handles txtScreen.KeyPress
If txtScreen.SelectionStart < txtScreen.TextLength AndAlso Not [Char].IsControl(e.KeyChar) Then
Dim SaveSelectionStart As Integer = txtScreen.SelectionStart
Dim sb As New StringBuilder(txtScreen.Text)
sb(txtScreen.SelectionStart) = e.KeyChar
'Add the pressed key at the right position
txtScreen.Text = sb.ToString()
'SelectionStart is reset after setting the text, so restore it
'Advance to the next char
txtScreen.SelectionStart = SaveSelectionStart + 1
e.Handled = True
End If
End Sub
This works, but allows you to overtype the selection one character at a time instead of wiping (replacing) the whole thing with one keypress. I've never seen this kind of behaviour in a windows control before, nor have I seen a need for such a process, but it is an interesting feature. In any case upon realising what was happening within the If statement, I figured out I could acheive what I wanted with one line of code:
If tb.SelectionStart < tb.TextLength AndAlso Not [Char].IsControl(e.KeyChar) Then
tb.SelectedText = ""
End If
This simply discards the selection and makes room for the new input, giving a more traditional Microsoft behaviour. I didn't need to set e.Handled in this case as I placed the code at the beginning of my KeyPress Event, allowing the keypress to be handled in subsequent code
so as per jmcilhinney I made some alterations... what I had (which worked but was a little sloppy)
Private Sub txtPick_KeyPress(sender As Object, e As System.Windows.Forms.KeyPressEventArgs) Handles txtNum1.KeyPress
Dim tb As TextBox = CType(sender, TextBox)
If tb.SelectionStart < tb.TextLength AndAlso Not Char.IsControl(e.KeyChar) Then
tb.SelectedText = ""
End If
'exclude all keypresses that aren't digits
If Not Char.IsDigit(e.KeyChar) Then e.Handled = True
Dim num As Integer = Val(String.Concat(tb.Text, e.KeyChar))
'make sure keypress keeps number in range
If num > 49 Or num < 1 Then e.Handled = True
'allow backspace, delete
If e.KeyChar = vbBack Or e.KeyChar = ChrW(Keys.Delete) Then e.Handled = False
End Sub
and now I have:
Private Sub txtPick_KeyPress(sender As Object, e As System.Windows.Forms.KeyPressEventArgs) Handles txtNum1.KeyPress
Dim tb As TextBox = CType(sender, TextBox)
'exclude all keypresses that aren't digits
If Not Char.IsDigit(e.KeyChar) Then e.Handled = True
'allow backspace, delete
If e.KeyChar = vbBack Or e.KeyChar = ChrW(Keys.Delete) Then e.Handled = False
End Sub
Private Sub txtNum_Validating(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles txtNum1.Validating
Dim tb As TextBox = CType(sender, TextBox)
Dim num As Integer = Val(tb.Text)
If IsNumeric(tb.Text) Then
Select Case Val(tb.Text)
Case Is < 1, Is > 49
e.Cancel = True
End Select
End If
End Sub
Cleaned up quite nice and worked perfect, thanks jmchilinney!
Solution