演练:在每个 Windows 窗体各自的线程上显示该 Windows 窗体自身以支持 COM 互操作

可以通过在 .NET Framework 消息循环上显示窗体来解决 COM 互操作性问题,消息循环是使用 Application.Run 方法创建的。

若要使 Windows 窗体在 COM 客户端应用程序中正确工作,必须在 Windows 窗体消息循环上运行该窗体。 若要实现这一点,请使用下列方法之一:

下面的过程演示如何在单独的线程上显示 Windows 窗体。

若要将本主题中的代码作为一个单独的列表进行复制,请参见 如何:通过在每个 Windows 窗体各自的线程上显示该 Windows 窗体来支持 COM 互操作

过程

将窗体放置在一个单独线程上并调用 Application.Run 方法在该线程上启动 Windows 窗体消息泵。 若要使用此方法,必须通过使用 Invoke 方法封送从非托管应用程序发出的对该窗体的任何调用。

此方法要求窗体的每个实例通过使用各自的消息循环在各自的线程上运行。 一个线程上不能运行多个消息循环。 因此,不能更改客户端应用程序的消息循环。 但是,可以修改 .NET Framework 组件以启动一个使用其自己的消息循环的新线程。

在新线程上创建 Windows 窗体的每个实例

  1. 创建一个新的“类库”项目,并将其命名为 COMWinform。

  2. 删除默认的 Class1.vb 文件。

  3. 在**“项目”菜单上,单击“添加类”**。

  4. 选择**“COM 类”**模板。

  5. 在**“名称”框中,键入 COMForm.vb,然后单击“添加”**。

  6. 将下面的代码语句粘贴在 COMForm 文件顶部类定义之前。

    Imports System.Windows.Forms
    Imports System.Runtime.InteropServices
    
  7. 在 COMForm 类定义中,将下面的代码粘贴在构造函数定义之下。

    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. 在**“项目”菜单中单击“添加类”并选择“类”**模板。

  9. 在**“名称”框中,键入 FormManager.vb,然后单击“添加”**。

  10. 用下列代码替换 FormManager 文件的内容。

    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.SetApartmentState(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
    
  11. 在**“项目”菜单中单击“添加 Windows 窗体”,然后单击“添加”**。

  12. 将一些 TextBox 控件和一个 Button 控件添加到窗体中。

  13. 双击**“Button1”**添加一个 Click 事件处理程序。

  14. 将下面的代码添加到 Click 事件处理程序中。

    Private Sub Button1_Click( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles Button1.Click
        MessageBox.Show("Clicked button")
    End Sub
    
  15. 生成解决方案。

    此步骤还注册该项目以便在此计算机上进行 COM 互操作。

创建演示 COM 互操作 的可执行文件

  1. 启动 Microsoft Visual Basic 6.0。

  2. 创建新的标准 EXE 项目。

  3. 在**“项目”菜单上,单击“引用”**。

  4. 添加对生成 Visual Basic 2005 解决方案时生成的 COMWinform 类型库的引用。

    - 或 -

    如果在列表中看不到此项,请单击**“浏览”**手动查找该类型库 (.tlb) 文件。

  5. 将一个按钮添加到窗体中。

  6. 在**“视图”菜单上单击“代码”**,然后将下面的代码添加到窗体模块。

[Visual Basic]

Option Explicit

Private Sub Command1_Click()
    Dim frm As COMWinform.COMForm
    Set frm = New COMWinform.COMForm
    frm.ShowForm1
End Sub
  1. 在**“文件”菜单上单击“生成 EXE”**来编译此项目。

  2. 运行已编译的 Visual Basic 6.0 可执行文件。

  3. 单击此按钮以显示先前创建的类库中的 Windows 窗体。

  4. 将焦点设置到此 Windows 窗体中的一个 TextBox 控件上,然后按 Tab 键在控件之间切换。

    注意 Tab 键将焦点从一个控件移动到另一个控件。 还请注意,按 Enter 键后将引发按钮的 Click 事件。

请参见

任务

如何:通过使用 ShowDialog 方法显示 Windows 窗体来支持 COM 互操作

概念

向 COM 公开 .NET Framework 组件

将 COM 的程序集打包

向 COM 注册程序集

Windows 窗体和非托管应用程序概述