Microsoft KB Archive/839076

From BetaArchive Wiki

Article ID: 839076

Article Last Modified on 10/28/2005



APPLIES TO

  • Microsoft .NET Framework 1.1
  • Microsoft Visual Basic 6.0 Enterprise Edition
  • Microsoft Foundation Class Library 4.2




SUMMARY

When you open an instance of a Microsoft Windows Form from a COM client application, such as a Microsoft Visual Basic 6.0 application or a Microsoft Foundation Classes (MFC) application, the .NET Windows Form may behave unexpectedly. For example, when you press TAB, the focus does not change from one control to another control. Or when you press ENTER while a command button has focus, the button's Click event does not fire. You may also experience unexpected behavior for keystrokes or for mouse activity.

These symptoms occur because the earlier application does not implement the message loop support that a .NET Framework Windows Form must have to work correctly.

This article describes how to resolve these problems by displaying the form on a .NET Framework message loop that you create by using the Application.Run method.

SYMPTOMS

When you create an instance of a Microsoft Windows Form from a COM client application, the form may behave unexpectedly. For example, when you create an instance of the form from a Microsoft Visual Basic 6.0 application or from a Microsoft Foundation Classes (MFC) application, the focus does not change from one control to another control when you press TAB. Or, if you press ENTER while a command button has focus, the button's Click event does not fire. You may also experience unexpected behavior for keystrokes or for mouse activity.

CAUSE

This problem occurs because the message loop that the Windows Form uses and the message loop that the COM client application provides are different.

Microsoft made a design change to address this problem.
For more information, click the following article number to view the article in the Microsoft Knowledge Base:

885446 FIX: You may experience unexpected behavior when you press TAB or ENTER in a Windows Form on a computer that is running the .NET Framework 1.1 S


This article describes how to resolve this problem by displaying the form on a .NET Framework message loop that you create by using the Application.Run method.

RESOLUTION

To start a .NET Framework message loop, use one of the following methods:

Use the ShowDialog method to display the Windows Form

This is the easiest solution for displaying the form on a .NET Framework message loop. To do this, replace the calls to the Show method with calls to the ShowDialog method in your .NET Framework component.

The ShowDialog method suspends the earlier application's message loop and displays the form as a dialog box. Because the host application's message loop has been suspended, the ShowDialog method creates a new .NET Framework message loop to process the form's messages. This is the easiest solution because it requires the least code to implement. However, the disadvantage to this approach is that the form will be opened modally. This behavior blocks any user interface (UI) in the calling application while the Windows Form is open. When the user closes the Windows Form, the .NET Framework message loop quits and the earlier application's message loop resumes running.

back to the top

Display each Windows Form on a new thread

Modify the .NET Framework component to display each instance of a form on its own thread by using its own message loop. You cannot have more than one message loop running per thread. Therefore, you cannot change the client application's message loop. However, you can modify the .NET Framework component to start a new thread that uses its own message loop.

Note Multithreaded programming is an advanced concept that requires careful consideration before implementation. This is especially true if multiple threads must access a shared resource. If multiple threads must access a shared resource at the same time, make sure that you design your .NET Framework components for thread safety. For additional information, click the following article number to view the article in the Microsoft Knowledge Base:

316136 How to synchronize the access to a shared resource in a multithreading environment with Visual Basic .NET


To create each instance of a Windows Form on a new thread, follow these steps:

  1. Start Microsoft Visual Studio .NET 2003.
  2. Create a class library. To do this, follow these steps:
    1. Use Visual Basic .NET 2003 to create a new Class Library project. Name the project COMWinform.
    2. Delete the default Class1.vb file.
    3. On the Project menu, click Add Class.
    4. Select the COM Class template.
    5. In the Name box, type COMForm.vb, and then click Open.
    6. Paste the following code statements at the top of the COMForm.vb file, before the class definition.

      Imports System.Windows.Forms
      Imports System.Runtime.InteropServices
    7. In the COMForm class definition, paste the following code under the code that was inserted when you created the class.

          Private frmManager As FormManager
      
          Public Sub ShowForm1()
              ' Call the StartForm method by using a new instance
              ' of the Form1 class.
              StartForm(New Form1)
          End Sub
      
          Private Sub StartForm(ByVal form As Form)
              ' This procedure is used to show all forms
              ' that the client application requests.
              
              ' Later forms will appear on a new message loop.
              If IsNothing(frmManager) Then
                  frmManager = New FormManager
              End If
              frmManager.ShowForm(form)
          End Sub
      
          Protected Overrides Sub Finalize()
              frmManager = Nothing
              MyBase.Finalize()
          End Sub
    8. On the Project menu, click Add Class.
    9. Click Class.
    10. In the Name box, type FormManager.vb, and then click OK.
    11. Replace the contents of the FormManager.vb file with the following code.

      Imports System.Runtime.InteropServices
      Imports System.Threading
      Imports System.Windows.Forms
      
      <ComVisible(False)> _
      Friend Class FormManager
          Public Sub ShowForm(ByVal form As Form)
              Dim thrd As Thread
              Dim wrapper As FormWrapper
              wrapper = New FormWrapper(form)
              thrd = New Thread(AddressOf wrapper.ShowFormOnNewThread)
              thrd.IsBackground = True
              thrd.ApartmentState = ApartmentState.STA
              thrd.Start()
          End Sub
      
          Private Class FormWrapper
              Private WithEvents appContext As ApplicationContext
              Public Sub New(ByVal form As Form)
                  MyBase.New()
                  appContext = New ApplicationContext(form)
              End Sub
      
              Public Sub ShowFormOnNewThread()
                  Application.Run(appContext)
              End Sub
      
              Private Sub ac_ThreadExit(ByVal sender As Object, ByVal e As System.EventArgs) Handles appContext.ThreadExit
                  appContext.MainForm.Dispose()
                  appContext.MainForm = Nothing
                  appContext.Dispose()
                  appContext = Nothing
              End Sub
          End Class
      End Class
    12. On the Project menu, click Add Windows Form, and then click Open.
    13. Add some TextBox controls and a command button to the form.
    14. Add the following code to the Click event handler of the command button.

      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
         MessageBox.Show("Clicked button")
      End Sub
    15. Build the solution. This step also registers the project for COM interop on this computer.
  3. Create an executable file. To do this, follow these steps:
    1. Start Microsoft Visual Basic 6.0.
    2. Create a new standard EXE project.
    3. On the Project menu, click References.
    4. Add a reference to the COMWinform type library that was generated when you built the Visual Basic .NET solution. If you do not see it in the list, click Browse to locate the type library (.tlb) file manually.
    5. Add a command button to the form.
    6. On the View menu, click Code, and then add the following code to the form module.

      Option Explicit
      
      Private Sub Command1_Click()
          Dim frm As COMWinform.COMForm
          Set frm = New COMWinform.COMForm
          frm.ShowForm1
      End Sub
    7. On the File menu, click Make EXE to compile the project.
  4. Run the compiled Visual Basic 6.0 executable file.
  5. Click the button. The Windows Form from the class library that you created earlier appears.
  6. Set the focus on one of the TextBox controls on the Windows Form, and then press TAB to move between the controls.

Notice that the TAB key works successfully to move between controls. Also, notice that the command button's Click event fires when you press ENTER.

back to the top

Create a shared message loop on a new thread in the .NET Framework component

This method is similar to the one in the "Display each Windows Form on a new thread" section. However, instead of displaying each form on its own thread by using its own message loop, this method creates a shared message loop that runs on only one new thread in the .NET Framework component.

This method more accurately represents the behavior that you would have if you were running a standard Windows Forms application. This design also makes it easier to share resources between multiple forms because all the forms run on the same thread. The solution in the "Display each Windows Form on a new thread" section creates a new thread for each form. That solution requires additional thread synchronization code to share resources between different forms.

Because this method is more similar than the other methods to the behavior of a Windows Forms application, notice that all .NET Framework forms that the client application opens will close when the .NET Framework message loop stops. This behavior occurs when the user closes the form that is designated as the main form for the ApplicationContext. The ApplicationContext is used to start the message loop.

In the following code sample, the main form of the ApplicationContext is set to the first form that the client application opens. Therefore, when the user closes that form instance, the .NET Framework message loop exits, and all other Windows Forms will close.

To create a shared message loop on a new thread for all forms to use, follow these steps:

  1. Start Microsoft Visual Studio .NET 2003.
  2. Create a class library. To do this, follow these steps:
    1. Use Visual Basic .NET 2003 to create a new Class Library project that is named COMWinform.
    2. Delete the default Class1.vb file.
    3. On the Project menu, click Add Class.
    4. Select the COM Class template.
    5. In the Name box, type COMForm.vb, and then click Open.
    6. Paste the following code statements at the top of the COMForm.vb file, before the class definition.

      Imports System.Windows.Forms
      Imports System.Runtime.InteropServices
    7. In the class definition of COMForm, paste the following code under the code that was inserted when you created the class.

          Private WithEvents frmManager As FormManager
      
          Public Sub ShowForm1()
              ' Call the StartForm method by using a new instance
              ' of the Form1 class.
              StartForm(New Form1)
          End Sub
      
          Private Sub StartForm(ByVal frm As Form)
      
              ' This procedure is used to show all forms
              ' that the client application requests. When the first form
              ' is displayed, this code will create a new message
              ' loop that runs on a new thread. The new form will
              ' be treated as the main form.
      
              ' Later forms will be shown on the same message loop.
              If IsNothing(frmManager) Then
                  frmManager = New FormManager(frm)
              Else
                  frmManager.ShowForm(frm)
              End If
          End Sub
      
          Private Sub frmManager_MessageLoopExit() Handles frmManager.MessageLoopExit
              'Release the reference to the frmManager object.
              frmManager = Nothing
          End Sub
    8. On the Project menu, click Add Class.
    9. Click Class.
    10. In the Name box, type FormManager.vb, and then click OK.
    11. Replace the contents of the FormManager.vb file with the following code.

      Imports System.Runtime.InteropServices
      Imports System.Threading
      Imports System.Windows.Forms
      
      <ComVisible(False)> _
      Friend Class FormManager
          ' This class is used so that you can generically pass any
          ' form that you want to the delegate.
      
          Private WithEvents appContext As ApplicationContext
          Private Delegate Sub FormShowDelegate(ByVal form As Form)
          Event MessageLoopExit()
      
          Public Sub New(ByVal MainForm As Form)
              Dim t As Thread
              If IsNothing(appContext) Then
                  appContext = New ApplicationContext(MainForm)
                  t = New Thread(AddressOf StartMessageLoop)
                  t.IsBackground = True
                  t.ApartmentState = ApartmentState.STA
                  t.Start()
              End If
          End Sub
      
          Private Sub StartMessageLoop()
              ' Call the Application.Run method to run the form on its own message loop.
              Application.Run(appContext)
          End Sub
       
           Public Sub ShowForm(ByVal form As Form)
      
              Dim formShow As FormShowDelegate
      
              ' Start the main form first. Otherwise, focus will stay on the 
              ' calling form.
              appContext.MainForm.Activate()
      
              ' Create a new instance of the FormShowDelegate method, and
              ' then invoke the delegate off the MainForm object.
              formShow = New FormShowDelegate(AddressOf ShowFormOnMainForm_MessageLoop)
              appContext.MainForm.Invoke(formShow, New Object() {form})
          End Sub
      
          Private Sub ShowFormOnMainForm_MessageLoop(ByVal form As Form)
              form.Show()
          End Sub
       
          Private Sub ac_ThreadExit(ByVal sender As Object, ByVal e As System.EventArgs) Handles appContext.ThreadExit
              appContext.MainForm.Dispose()
              appContext.MainForm = Nothing
              appContext.Dispose()
              appContext = Nothing
              RaiseEvent MessageLoopExit()
          End Sub
        End Class
      End Class
    12. On the Project menu, click Add Windows Form, and then click Open.
    13. Add some TextBox controls and a command button to the form.
    14. Add the following code to the Click event handler of the command button.

      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
         MessageBox.Show("Clicked button")
      End Sub
    15. Build the solution. This step also registers the project for COM Interop on this computer.
  3. Create an executable file. To do this, follow these steps:
    1. Start Microsoft Visual Basic 6.0.
    2. Create a new standard EXE project.
    3. On the Project menu, click References.
    4. Add a reference to the COMWinform type library that was generated when you built the Visual Basic .NET solution. If you do not see it in the list, click Browse to locate the type library (.tlb) file manually.
    5. Add a command button to the form.
    6. On the View menu, click Code, and then add the following code to the form module.

      Option Explicit
      
      Private Sub Command1_Click()
          Dim frm As COMWinform.COMForm
          Set frm = New COMWinform.COMForm
          frm.ShowForm1
      End Sub
    7. On the File menu, click Make EXE to compile the project.
  4. Run the compiled Visual Basic 6.0 executable file.
  5. Click the button. The Windows Form from the class library that you created earlier appears.
  6. Set the focus on one of the TextBox controls on the Windows Form, and then press TAB to move between the controls.

Notice that the TAB key works successfully to move between the controls. Also, notice that the command button's Click event fires when you press ENTER.

back to the top

STATUS

This behavior is by design.

MORE INFORMATION

An application's message loop is an internal program loop that retrieves messages from a thread's message queue, translates them, and then sends them to the application to be handled. The message loop for a Windows Form does not have the same architecture as message loops that earlier applications, such as Visual Basic 6.0 applications and MFC applications, provide. The window messages that are posted to the message loop may be handled differently than the Windows Form expects. Therefore, unexpected behavior may occur. Some keystroke combinations may not work, some mouse activity may not work, or some events may not fire as expected.

Note A .NET Framework message loop is the only supported architecture for displaying and for using a Windows Form. The solutions that are described in the "Resolution" section apply only to displaying a Windows Form. The only supported container for hosting a .NET Framework control through a COM callable wrapper (CCW) is Microsoft Internet Explorer. Because a control must be in a container, that control must use the message loop that the container provides. Therefore, you cannot use the solutions that are described in the "Resolution" section for a .NET Framework control.

For additional information, click the following article number to view the article in the Microsoft Knowledge Base:

311334 ActiveX control containers that support .NET controls


317346 Native versus COM-callable .NET controls in Internet Explorer


Steps to reproduce the behavior

  1. Start Microsoft Visual Studio .NET 2003.
  2. Create a class library. To do this, follow these steps:
    1. Use Visual Basic .NET 2003 to create a new Class Library project that is named COMWinform.
    2. Delete the default Class1.vb file.
    3. On the Project menu, click Add Class.
    4. Select the COM Class template.
    5. In the Name box, type COMForm.vb, and then click Open.
    6. Paste the following code statements at the top of the COMForm.vb file, before the class definition.

      Imports System.Windows.Forms
      Imports System.Runtime.InteropServices
    7. In the class definition of COMForm, paste the following code under the code that was inserted when you created the class.

          Public Sub ShowForm1()
              ' Create an instance of the .NET Windows Form (Form1),
              ' and then display it
      
              Dim frm As Form1
              frm = New Form1
              frm.Show
          End Sub
      End Class
    8. On the Project menu, click Add Windows Form, and then click Open.
    9. Add some TextBox controls and a command button to the form.
    10. Add the following code to the Click event handler of the command button.

      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
         MessageBox.Show("Clicked button")
      End Sub
    11. Build the solution. This step also registers the project for COM Interop on this computer.
  3. Create an executable file. To do this, follow these steps:
    1. Start Microsoft Visual Basic 6.0.
    2. Create a new Standard EXE project.
    3. On the Project menu, click References.
    4. Add a reference to the COMWinform type library that was generated when you built the Visual Basic .NET solution. If you do not see it in the list, click Browse to locate the type library (.tlb) file manually.
    5. Add a command button to the form.
    6. On the View menu, click Code, and then add the following code to the form module.

      Option Explicit
      
      Private Sub Command1_Click()
          Dim frm As COMWinform.COMForm
          Set frm = New COMWinform.COMForm
          frm.ShowForm1
      End Sub
    7. On the File menu, click Make EXE to compile the project.
  4. Run the compiled Visual Basic 6.0 executable file.
  5. Click the button. The Windows Form from the class library that you created earlier appears.
  6. Set the focus on one of the TextBox controls on the Windows Form, and then press TAB to move between the controls. Notice that the TAB key does not move focus between the controls as expected.
  7. Set the focus on the command button on the Windows Form, and then press ENTER. Notice that the button does not respond and the Click event does not fire.


REFERENCES

For more information, click the following article numbers to view the articles in the Microsoft Knowledge Base:

316422 Roadmap for threading in Visual Basic .NET


315577 How to create threads in Visual Basic .NET


Keywords: kbinfo kbwindowsforms kbprb KB839076