vb.netwinformsmaskedtextbox

MessageBox Causes MaskedTextBox Cursor Location To Move


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

Solution

  • 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)