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:
- “Entered” event on the constituent lookup control.
- “ProcessTabKey” method on the host user control.
- “Leave” event on the constituent lookup control.
- “Leave” event on the host user control.
- Search dialog is displayed…
- “Validating” event on the constituent lookup control.
- “Validated” event on the constituent lookup control.
#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