Archive for September, 2011

Interop Forms Toolkit UserControls Can Launch a .NET Form!

At work, we’re heavily invested in the Interop Forms Toolkit while our product is slowly being migrated from VB6 to .NET.

When you are creating a .NET control to be hosted on a VB6 form, there are several limitations mentioned in the documentation, including:

You should not show a .NET form from an Interop UserControl. The .NET form will not close if the parent form is closed first, and tabbing on the .NET form will not work.

This was going to be a problem for us. Our product has lookup text boxes that launch a search dialog when the user leaves the field with a partial or ambiguous value. The search dialog is a .NET form, so we were concerned that this was going to cause problems.

We tried it anyway. Our interop user controls are always composite controls that can include multiple lookup text boxes. We figured that maybe things would be a little different because we were showing the form modally from within a contained control.

We found that it worked, mostly. The big stickler was when a highly productive worker was entering data using the keyboard. The TAB key would launch the search dialog, but then the focus got stuck somewhere that would not respond to the keyboard any more. They had to use the mouse to get focus back on the form, which was disruptive to their flow.

Other workflow scenarios, such as explicitly launching the search dialog with a function key (i.e. not tabbing) were working just fine and not losing focus.

I won’t rehash all the details of the debugging effort. We added lots of debug statements to event handlers and overrides of methods that were related to key input and focus. We were able to identify this sequence of events that was relevant:

  1. “Entered” event on the constituent lookup control.
  2. “ProcessTabKey” method on the host user control.
  3. “Leave” event on the constituent lookup control.
  4. “Leave” event on the host user control.
  5. Search dialog is displayed…
  6. “Validating” event on the constituent lookup control.
  7. “Validated” event on the constituent lookup control.
Using the bolded items, we were able to design a state diagram to handle this issue and force focus back to where it should have gone in the first place. The following region of code lives on the top user control. My wish is that this code could be useful to somebody else experiencing a similar problem.

#Region " Custom search focus handling "
    ' When this control is hosted on a VB6 form using interop, tabbing out of a field that
    ' launches a custom search dialog causes focus to leave the control and the user
    ' can no longer navigate with the keyboard unless they use the mouse to put focus
    ' back into the control. This block of code detects the scenario and sets focus to the
    ' appropriate control.

    ' Two textboxes on the new item use custom search:
    '   Lookup1
    '   Lookup2

    ' The following sequence of events leads to the problem. These events can also
    ' occur in other scenarios, but they will not occur in this exact sequence.
    ' Lookup control is entered
    ' Tab key is pressed (ProcessTabKey)
    ' Custom search dialog makes this control lose focus (OnLeave)

    ' The fix will set focus back to the appropriate field after the dialog
    ' returns. This will happen when the control is validated, but only when
    ' the appropriate sequence of events has occurred. A state machine enum
    ' is used to manage this.

    Private Enum CustomSearchFieldState
        Null
        Editing
        Tabbing
        NeedsFocus
    End Enum

    Dim mCustomSearchFieldState As CustomSearchFieldState = CustomSearchFieldState.Null
    Dim mCustomSearchField As MyCustomLookupTextBox
    Dim mCustomSearchFieldTabForward As Boolean = False

    Private Sub CustomSearchEnter(sender As Object, e As System.EventArgs) _
    Handles Lookup1.Enter, Lookup2.Enter
        ' The problem only occurs when the control is hosted in VB6. We can detect that specific
        ' scenario through the ParentForm property. In .NET, we will have a parent form. In VB6,
        ' the parent form is null.
        If Me.ParentForm Is Nothing AndAlso
        mCustomSearchFieldState = CustomSearchFieldState.Null Then
            mCustomSearchField = DirectCast(sender, MyCustomLookupTextBox)
            mCustomSearchFieldState = CustomSearchFieldState.Editing
        End If
    End Sub

    Protected Overrides Function ProcessTabKey(forward As Boolean) As Boolean
        If mCustomSearchFieldState = CustomSearchFieldState.NeedsFocus Then
            ' This state indicates that the user canceled out of a previous custom
            ' search. They may have chosen to change direction, so we update
            ' the direction flag.
            mCustomSearchFieldTabForward = forward
        End If
        If mCustomSearchFieldState = CustomSearchFieldState.Editing Then
            mCustomSearchFieldState = CustomSearchFieldState.Tabbing
            mCustomSearchFieldTabForward = forward
        End If
        Return MyBase.ProcessTabKey(forward)
    End Function

    Protected Overrides Sub OnLeave(e As System.EventArgs)
        If mCustomSearchFieldState = CustomSearchFieldState.Tabbing Then
            mCustomSearchFieldState = CustomSearchFieldState.NeedsFocus
        End If
        MyBase.OnLeave(e)
    End Sub

    Private Sub CustomSearchValidated(sender As Object, e As System.EventArgs) _
    Handles Lookup1.Validated, Lookup2.Validated
        If mCustomSearchFieldState = CustomSearchFieldState.NeedsFocus Then
            Me.Focus()
            Me.SelectNextControl(mCustomSearchField, mCustomSearchFieldTabForward, True, True, True)
        End If
        ' Validation is the final event in all states and resets the custom search workaroud.
        mCustomSearchFieldState = CustomSearchFieldState.Null
        mCustomSearchField = Nothing
    End Sub

#End Region
Advertisements

Leave a comment