Microsoft KB Archive/839076

= You experience unexpected behavior when you use Windows Forms in COM client applications =

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
 * Display each Windows Form on a new thread
 * Create a shared message loop on a new thread in the .NET Framework component

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:  Start Microsoft Visual Studio .NET 2003. Create a class library. To do this, follow these steps:  Use Visual Basic .NET 2003 to create a new Class Library project. Name the project COMWinform. Delete the default Class1.vb file. On the Project menu, click Add Class. Select the COM Class template.</li> In the Name box, type COMForm.vb, and then click Open.</li>  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 </li>  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 </li> On the Project menu, click Add Class.</li> Click Class.</li> In the Name box, type FormManager.vb, and then click OK.</li>  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 </li> On the Project menu, click Add Windows Form, and then click Open.</li> Add some TextBox controls and a command button to the form.</li>  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(&quot;Clicked button&quot;) End Sub </li> Build the solution. This step also registers the project for COM interop on this computer.</li></ol> </li> Create an executable file. To do this, follow these steps: <ol style="list-style-type: lower-alpha;"> Start Microsoft Visual Basic 6.0.</li> Create a new standard EXE project.</li> On the Project menu, click References.</li> <li>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.</li> <li>Add a command button to the form.</li> <li> 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 </li> <li>On the File menu, click Make EXE to compile the project.</li></ol> </li> <li>Run the compiled Visual Basic 6.0 executable file.</li> <li>Click the button. The Windows Form from the class library that you created earlier appears.</li> <li>Set the focus on one of the TextBox controls on the Windows Form, and then press TAB to move between the controls.</li></ol>

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 &quot;Display each Windows Form on a new thread&quot; 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 &quot;Display each Windows Form on a new thread&quot; 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: <ol> <li>Start Microsoft Visual Studio .NET 2003.</li> <li>Create a class library. To do this, follow these steps: <ol style="list-style-type: lower-alpha;"> <li>Use Visual Basic .NET 2003 to create a new Class Library project that is named COMWinform.</li> <li>Delete the default Class1.vb file.</li> <li>On the Project menu, click Add Class.</li> <li>Select the COM Class template.</li> <li>In the Name box, type COMForm.vb, and then click Open.</li> <li> 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 </li> <li> 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 </li> <li>On the Project menu, click Add Class.</li> <li>Click Class.</li> <li>In the Name box, type FormManager.vb, and then click OK.</li> <li> 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 </li> <li>On the Project menu, click Add Windows Form, and then click Open.</li> <li>Add some TextBox controls and a command button to the form.</li> <li> 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(&quot;Clicked button&quot;) End Sub </li> <li>Build the solution. This step also registers the project for COM Interop on this computer.</li></ol> </li> <li>Create an executable file. To do this, follow these steps: <ol style="list-style-type: lower-alpha;"> <li>Start Microsoft Visual Basic 6.0.</li> <li>Create a new standard EXE project.</li> <li>On the Project menu, click References.</li> <li>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.</li> <li>Add a command button to the form.</li> <li> 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 </li> <li>On the File menu, click Make EXE to compile the project.</li></ol> </li> <li>Run the compiled Visual Basic 6.0 executable file.</li> <li>Click the button. The Windows Form from the class library that you created earlier appears.</li> <li>Set the focus on one of the TextBox controls on the Windows Form, and then press TAB to move between the controls.</li></ol>

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

<div class="status_section">

STATUS
This behavior is by design.

<div class="moreinformation_section">

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 &quot;Resolution&quot; 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 &quot;Resolution&quot; 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
<ol> <li>Start Microsoft Visual Studio .NET 2003.</li> <li>Create a class library. To do this, follow these steps: <ol style="list-style-type: lower-alpha;"> <li>Use Visual Basic .NET 2003 to create a new Class Library project that is named COMWinform.</li> <li>Delete the default Class1.vb file.</li> <li>On the Project menu, click Add Class.</li> <li>Select the COM Class template.</li> <li>In the Name box, type COMForm.vb, and then click Open.</li> <li> 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 </li> <li> 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 </li> <li>On the Project menu, click Add Windows Form, and then click Open.</li> <li>Add some TextBox controls and a command button to the form.</li> <li> 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(&quot;Clicked button&quot;) End Sub </li> <li>Build the solution. This step also registers the project for COM Interop on this computer.</li></ol> </li> <li>Create an executable file. To do this, follow these steps: <ol style="list-style-type: lower-alpha;"> <li>Start Microsoft Visual Basic 6.0.</li> <li>Create a new Standard EXE project.</li> <li>On the Project menu, click References.</li> <li>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.</li> <li>Add a command button to the form.</li> <li> 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 </li> <li>On the File menu, click Make EXE to compile the project.</li></ol> </li> <li>Run the compiled Visual Basic 6.0 executable file.</li> <li>Click the button. The Windows Form from the class library that you created earlier appears.</li> <li>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.</li> <li>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.</li></ol>

<div class="references_section">