This has been driving me nuts for ages. If the focus of a VB.NET app is a MaskedTextBox
, anytime a MessageBox
is displayed, the cursor location of the MaskedTextBox
resets to the 2nd letter. Why? Any ideas how to prevent this?
Here's how to replicate:
Create a blank Winforms app, add a MaskedTextBox
. Set the Mask
to ">&&&&&&-&". Add the following code to the form:
Public Class Form1
Private Sub MaskedTextBox1_TextChanged(sender As Object, e As EventArgs) Handles MaskedTextBox1.TextChanged
If MaskedTextBox1.Text = "000000-" Then
MsgBox("hi")
End If
End Sub
End Class
Run the app. Start typing 0's into the text box. Notice that when you hit the 6th zero and the MsgBox
is fired, the cursor moves back to the 2nd letter.
EDIT: This has nothing to do with the message box itself. It seems to have something to do with any other form showing. See this example:
Public Class Form1
Private Sub MaskedTextBox1_TextChanged(sender As Object, e As EventArgs) Handles MaskedTextBox1.TextChanged
If MaskedTextBox1.Text = "000000-" Then
Dim f As New Form1
f.Show() 'after you close the new form, the cursor will have moved in the first!
End If
End Sub
End Class
The MaskedTextBox
overrides OnTextChanged, to set some state that allows to determine later what is the actual length of the text and the mask, or a mix of the two, depending on what the Control needs to present to the user.
To keep it simple (or read what's at the bottom), you should allow the sequence of method calls generated by the event to complete, before you do something that moves the Focus somewhere else, generating a new sequence of method calls and validation events that end up compromising the registration of states in some Controls.
This applies to different events of other Controls as well.
To defer the presentation of the MessageBox, you can BeginInvoke() it.
The code that follows is executed asynchronously, after the TextChanged
event has completed its procedures:
Private Sub MaskedTextBox1_TextChanged(sender As Object, e As EventArgs) Handles MaskedTextBox1.TextChanged
If MaskedTextBox1.IsHandleCreated AndAlso MaskedTextBox1.Text = "000000-" Then
BeginInvoke(New Action(Sub() MessageBox.Show(Me, "Some message")))
End If
End Sub
If you need to handle the result of the MessageBox, do that in the same way:
Private Sub MaskedTextBox1_TextChanged(sender As Object, e As EventArgs) Handles MaskedTextBox1.TextChanged
If MaskedTextBox1.IsHandleCreated AndAlso MaskedTextBox1.Text = "000000-" Then
BeginInvoke(New Action(
Sub()
If MessageBox.Show(
Me, "Some question", "Some caption", MessageBoxButtons.YesNo) = DialogResult.Yes Then
' Do something when the answer is 'Yes'
End If
End Sub))
End Sub
You can also set a Field to the result of the Dialog and inspect it somewhere else.
The same if you show a Form instead, Modal or not:
Private Sub MaskedTextBox1_TextChanged(sender As Object, e As EventArgs) Handles MaskedTextBox1.TextChanged
If MaskedTextBox1.IsHandleCreated AndAlso MaskedTextBox1.Text = "000000-" Then
BeginInvoke(New Action(
Sub()
Dim f As New SomeForm()
f.Show()
End Sub))
End Sub
If you're interested in what happens under the hood, see the code that sets the final state in the OnTextChanged
override, and read the notes here, then see what happens when the OnTextChanged method of the base class is called.
You can infer what happens after, when SetWindowText() is called for other reasons (OnTextChange()
is also called when the mask is set back to the Control, when it's been computed what part is the text and what part is the mask)