You experience unexpected behavior when you use Windows Forms in COM client applications (839076)
The information in this article applies to:
- Microsoft .NET Framework 1.1
- Microsoft Visual Basic Enterprise Edition for Windows 6.0
- The Microsoft Foundation Classes (MFC)
SUMMARYWhen 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.SYMPTOMSWhen 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.CAUSEThis 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. RESOLUTIONTo start a .NET Framework message loop, use one of the following methods: Use the ShowDialog method to display the Windows FormThis 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 topDisplay each Windows Form on a new threadModify 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.
- In the Name box, type COMForm.vb, and then click Open.
- 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 - 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 - On the Project menu, click Add Class.
- Click Class.
- In the Name box, type FormManager.vb, and then click OK.
- 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 -
On the Project menu, click Add Windows Form, and then click Open.
- Add some TextBox controls and a command button to the form.
- 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
- Build the solution. This step also registers the project for COM interop on this computer.
- Create an executable file. To do this, follow these steps:
- Start Microsoft Visual Basic 6.0.
- Create a new standard EXE project.
- On the Project menu, click References.
- 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.
- Add a command button to the form.
- 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
- On the File menu, click Make EXE to compile the project.
- Run the compiled Visual Basic 6.0 executable file.
- Click the button. The Windows Form from the class library that you created earlier appears.
- 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 topCreate a shared message loop on a new thread in the .NET Framework componentThis 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: - 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 that is named COMWinform.
- Delete the default Class1.vb file.
- On the Project menu, click Add Class.
- Select the COM Class template.
- In the Name box, type COMForm.vb, and then click Open.
- 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 - 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
- On the Project menu, click Add Class.
- Click Class.
- In the Name box, type FormManager.vb, and then click OK.
- 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 -
On the Project menu, click Add Windows Form, and then click Open.
- Add some TextBox controls and a command button to the form.
- 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
- Build the solution. This step also registers the project for COM Interop on this computer.
- Create an executable file. To do this, follow these steps:
- Start Microsoft Visual Basic 6.0.
- Create a new standard EXE project.
- On the Project menu, click References.
- 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.
- Add a command button to the form.
- 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
- On the File menu, click Make EXE to compile the project.
- Run the compiled Visual Basic 6.0 executable file.
- Click the button. The Windows Form from the class library that you created earlier appears.
- 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 topSTATUS This behavior is by design.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
Modification Type: | Major | Last Reviewed: | 10/28/2005 |
---|
Keywords: | kbprb kbWindowsForms kbinfo KB839076 kbAudDeveloper |
---|
|