65.9K
CodeProject 正在变化。 阅读更多。
Home

Interop Forms Toolkit 2.0 教程

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (58投票s)

2007年5月26日

CPOL

16分钟阅读

viewsIcon

560642

downloadIcon

13965

Interop Forms Toolkit 2.0 是一款新的桥接工具,允许开发人员在 VB6 中使用 .NET Forms 和 .NET UserControls。本教程演示了如何将 Web Services、多线程和 XAML 添加到 VB6 项目。它还提供了用于 Toolkit 的自定义 C# Interop UserControl 模板。

Screenshot - Diagram.gif

为什么使用 Interop Toolkit?

几年前,我工作的公司的企业架构师们提出了一个使用 Web Services 为公司所有应用程序实现集中登录机制的方案。他们甚至提供了使用他们新组件的 Java、C# 和 VB.NET 代码示例。它 intended to be a language agnostic solution。当我们问架构师们如何处理我们仍在维护的 VB6 应用程序时,架构师们都感到无所适从。他们先是提供了一些关于在 VB6 中使用 SOAP 的晦涩难懂的白皮书,然后建议我们将所有 VB6 应用升级到 .NET,最后,他们承认 VB6 应用在其解决方案中根本没有位置。

如果当时 Interop Forms Toolkit 2.0 已经可用,我们可以在不到一个小时内完成集成。我们只需要将示例代码复制到一个新的 .NET User Control 中,使用 Interop Toolkit 将其包装成一个 ActiveX 控件,然后在我们所有的 VB6 应用程序中消费该控件。

Interop Toolkit 2.0 于五月初刚刚发布。最初的 Interop Toolkit 已经允许 VB 开发人员在他们的应用程序中使用 .NET Forms。这在 Toolkit 2.0 中仍然存在,并且似乎没有太大变化。

Toolkit 2.0 的独特之处在于它支持将 .NET User Controls 作为 ActiveX 控件在 VB6 中使用。

ActiveXHelpers Class

根据微软的说法,该工具包 intended as part of a migration strategy for upgrading VB6 applications to .NET, piece by piece。然而,我不确定这是否是它可能的使用方式,或者它是否一定应该以这种方式使用。

Toolkit 2.0 最有意义的用途是作为一个工具,允许 VB6 开发人员利用 .NET 功能,而不必被强制升级。目前仍存在的绝大多数 VB6 应用程序显然都能很好地满足特定的需求。为什么要去修复一个没坏的东西呢?

然而,有时您可能希望在 VB6 应用程序中利用 .NET 功能。长期以来,您的选择只有两个:要么将整个应用程序升级到 .NET,要么放弃那些新颖的功能。

Toolkit 2.0 提供了第三种选择。只需将您需要的 .NET 功能添加为一个控件。

本教程将引导您完成

  1. 一个模拟应用程序,它实现了我们将用于解决上述问题的技术。它还将涵盖,
  2. 安装 Interop Toolkit,
  3. 提供一个参考应用程序,让开发人员能够在 VB6 应用中使用真正的多线程,最后
  4. 提供一个将 XAML 文件集成到 VB6 的操作指南。

C# 开发人员的 Interop

与前一版本一样,Interop Forms Toolkit 2.0 主要面向 VB.NET 开发人员。Toolkit 附带的向导、项目模板和项模板只有 VB 版本。这在一定程度上是合理的,因为绝大多数会实现这些 .NET/VB6 集成的将是 VB 开发人员。然而,许多开发人员喜欢同时使用这两种语言,并且可能存在需要将预先存在的 C# 代码暴露给 VB6 的集成场景。

对于这些情况,我已经为 Interop User Controls 编写了上面链接的 C# 项模板和 C# 项目模板。只需将项目模板 zip 文件复制到您的项目模板文件夹(默认位置是 ...\My Documents\Visual Studio 2005\Templates\ProjectTemplates\Visual C#),并将项模板 zip 文件复制到您的项模板文件夹(...\My Documents\Visual Studio 2005\Templates\ItemTemplates\Visual C#)。我认为这些模板只能与 Visual Studio 2005 一起使用,但我还没有在旧版本的 Visual Studio 上进行测试以确保这一点。

对于需要暴露 C# Form 的情况,您可以使用 Leon Langleyben 编写的巧妙向导和模板,我经过一些调整后使其能够工作——但这并非 Leon 的错,因为他的插件是为 Interop Toolkit 的前一个版本编写的(请参阅包含的 C# 示例中的 CSXamlEmbeddedForm 项目,了解生成的包装器类在 C# 中应该是什么样子)。

安装 Toolkit

安装 Toolkit 非常简单。导航到 Toolkit 下载站点,在三个可用下载中,运行 InteropFormsInstaller.msi 文件。在大多数情况下,这就是您需要做的全部。当您打开 Visual Studio .NET IDE 时,在创建新的 VB.NET 项目时,您应该会找到新的模板,VB6 Interop UserControlVB6 Interop Form Library。在“工具”菜单下,您还应该找到一个名为“Generate InteropForm Wrapper Classes”的新向导。

如果新向导未出现在“工具”菜单中,则安装可能存在问题。请检查 Tools | Add-in Manager 以确保已选中此向导。如果它存在并已在 Add-in Manager 中选中,但仍未出现在“工具”菜单中,您可以在命令行中运行以下命令来重置它:Devenv /resetaddin Microsoft.InteropFormTools.InteropFormProxyGenerator.Connect

在 Vista 上安装 Toolkit

为了在 Windows Vista 上使用 Toolkit,您需要将 MSI 文件和安装程序文件下载到您的硬盘驱动器。然后,运行安装程序。单独运行 MSI 文件将生成安装错误。

要在 Vista 上使用 C# UserControl 模板,您需要以管理员身份运行 Visual Studio。右键单击您的 Visual Studio IDE 链接,然后选择“Run as administrator”弹出菜单选项。这将让 Vista 的 UAC 功能知道 UserControl 在生成事件时写入注册表是允许的。

构建 User Control

在第一个示例中,UserControl 将负责所有处理,并且将直接放在 VB6 Form 中,而紧随其后的示例将演示如何在 VB6 和 VB.NET 之间传递信息。任何代码片段都将是 VB 语言,尽管上面链接的源代码示例包含了控件的 VB.NET 和 C# 示例。

在此示例中,我们使用 Daily Dilbert Web Services 下载一张漫画到我们的控件(您会注意到 Daily Dilbert 在 CodeProject 上经常出现——这是有原因的;它是少数几个可以在公开教程中使用的有趣的 Web Services 之一,因为它不需要付费或注册)。

Open new project

首先,使用 VB6 Interop UserControl 项目模板创建一个新的 VB6 Interop User Control 项目。将项目命名为 DailyDilbertControl。默认情况下,会创建一个 UserControl 文件,名为 InteropUserControl.vb。由于这是将在 VB6 控件面板中显示的控件名称,您应该将其重命名为 DilbertService.vb,以使其更具描述性。

在您的新项目中,您会找到以下文件:ActiveXControlHelpers.vbInteropUserControl.bmpInteropUserControl.manifestActiveXControlHelpers.vb,顾名思义,包含几个静态帮助方法,这些方法使将 UserControl 转换为 ActiveX 控件成为可能。有注册和注销方法,它们将关于您的控件的详细信息添加到注册表中。有帮助将颜色代码等内容在 .NET 方案和 VB6 使用的 OLE 方案之间进行转换的方法。还有一个连接您的事件以与 VB6 一起工作的方法。您可以在项目中拥有多个用户控件,但每个项目应该只有一个 ActiveXControlHelpers 文件。

InteropUserControl.bmp 用于在 VB6 工具箱中显示您的控件。我将在本教程的后面介绍如何自定义您的 ActiveX 控件图像。

向项目中添加对 Daily Dilbert Web Services 的新的 Web 引用。要做到这一点,右键单击您的项目并选择 Add Web Reference...。将弹出一个新对话框。在 URL 处输入 http://www.esynaps.com/WebServices/DailyDiblert.asmx,然后单击 Go。最后,将 Web 引用重命名为“DDService”,然后选择对话框右下角的“Add Reference”。此时,您的 Solution Explorer 应该看起来像这样。

Add web reference

向您的 UserControl 添加一个 650 x 215 像素的 PictureBox,名为 DilbertPictureBox,以及一个 Button,名为 RetrieveButton。通过双击 RetrieveButton 创建一个新的 RetrieveButton_Click 事件处理程序。粘贴以下代码,该代码将调用 Web Service 并检索今天的 Dilbert 漫画。

    Private Sub RetrieveButton_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles RetrieveButton.Click
        Dim myDilbert As New DDService.DailyDilbert()
        Dim DilbertMemoryStream As _
        New System.IO.MemoryStream(myDilbert.DailyDilbertImage())
        With Me.DilbertPictureBox
            .Image = Image.FromStream(DilbertMemoryStream)
            .BorderStyle = BorderStyle.Fixed3D
        End With
    End Sub

要完成使您的控件从 VB6 可见,只需按 F5 预览控件,或直接构建它。它将在您的 Visual Studio UserControl Test Container 中显示如下。

Preview Dilbert Control

这就是在 Visual Studio .NET 中构建 ActiveX 控件所需的全部。

添加 ActiveX 控件图像

当您构建新控件时,InteropUserControl.bmp 将用作 VB6 工具栏中组件的默认图像。不过,您随时可以使用不同的图像。对于这个项目,我想使用 Dilbert 的这张图片。

Screenshot - Dilbert.gif

要添加它,您首先必须将要使用的位图文件添加到您的项目中,并将其 Build 属性设置为“content”。现在,用记事本打开 InteropUserControl.rc 资源脚本文件。不要使用 Visual Studio 来执行此操作,因为这会弄乱您的资源脚本文件。InteropUserControl.rc 可以在 DailyDilbertControl 项目文件夹下找到。在默认的 101 BITMAP InteropUserControl.bmp 条目下方,添加一个额外的条目来指定您要使用的自定义位图。

Edit resource script

保存您的资源脚本文件。现在,打开 ActiveXHelpers.vb 并找到 RegisterControl 方法。这是创建注册表条目的地方。在指定位图文件的部分,将默认条目“101”替换为您自己的位图的引用。

Replace bitmap reference

现在,重新构建您的控件以确保创建了一个新的编译资源文件。新图像应该会出现在 VB6 工具箱中,而不是默认图像。

VB6 Control Toolbar

更多关于添加 ActiveX 控件图像的信息

您可以在项目中为每个 UserControl 使用不同的图像,但这需要您稍微修改 ActiveXControlHelpers.vb 文件才能使其工作。将 RegisterControl 方法签名更改为接受第二个 String 参数,然后将此参数传递给指定图像资源 ID 的代码行。

Public Sub RegisterControl(ByVal t As Type, ByVal BitmapId As String)

    '...
    
    'ToolBoxBitmap32
    Using bitmapKey As RegistryKey = subkey.CreateSubKey("ToolBoxBitmap32")
    bitmapKey.SetValue("", Assembly.GetExecutingAssembly.Location & ", "  _
    & BitmapId, RegistryValueKind.String)
    End Using
    
    '...
    
End Sub

然后,在每个 UserControl 中,将您希望为控件使用的 BitmapId 添加到 RegisterControl 调用中。

    <EditorBrowsable(EditorBrowsableState.Never)> _
    <ComRegisterFunction()> _
    Private Shared Sub Register(ByVal t As Type)
        ComRegistration.RegisterControl(t, "102")
    End Sub

再次重新构建整个项目。

将 UserControl 添加到 VB6 项目

Add DailyDilbert Component

这实际上是最简单的一部分。创建一个新的 VB6 项目。按 CTRL+T 将新组件添加到您的窗体,并勾选 DailyDilbertControl 库。按 OK。(Vista 在您尝试添加 ActiveX 控件时表现有点奇怪。第一次选择 OK 时可能会偶尔出现错误,然后第二次操作时会正常工作。为了安全起见,先单击 Apply 看看是否有错误,然后单击 OK。)项目中的任何 UserControl 现在都将出现在 VB 工具栏上。只需选择您想要使用的控件,然后将其拖放到 VB6 窗体上。按 F5 即可在 Visual Basic 6 应用程序中运行您的 .NET UserControl。

VB6 Dilbert

为 VB6 添加真正的多线程

当我定期使用 VB6 进行开发时,升级到 .NET 的主要驱动力之一是能够实现多线程。Interop UserControls 提供了一种简单的方法来为 VB6 应用程序添加真正的多线程。在常见场景中,您可能希望您的 VB 应用程序的用户能够启动一个进程,然后在处理程序在后台运行时继续他们的工作。为了模拟这种情况,我们将要构建的参考代码将使用一个 BackgroundWorker 控件,它将在后台执行一个耗时的进程,同时更新进度条。与此同时,使用该控件的 VB6 应用程序的用户可以继续他们的工作。

创建一个名为 MultithreadedControl 的新的 VB6 Interop UserControl 项目。添加一个名为 BackgroundWorker1BackgroundWorker 控件,一个名为 LabelWarningMessageLabel,以及一个名为 ProgressBar1ProgressBar。粘贴以下代码。

    Public Delegate Sub StartEventHandler(ByVal simpleEventText As String)
    Public Delegate Sub FinishAsyncEventHandler(ByVal asyncEventText As String)

    Public Event StartEvent As StartEventHandler
    Public Event FinishAsyncEvent As FinishAsyncEventHandler

    Public Sub StartProcessing()
        Try
            RaiseEvent StartEvent(".NET process starting")
            Me.BackgroundWorker1.RunWorkerAsync()
        Catch
        End Try
    End Sub

    Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, _
      ByVal e As System.ComponentModel.DoWorkEventArgs) _
      Handles BackgroundWorker1.DoWork
        'wait
        Static prog As Integer = 0
        While (prog < 100)
            System.Threading.Thread.Sleep(50)
            prog = prog + 2
            Me.BackgroundWorker1.ReportProgress(prog)
        End While
        prog = 0
    End Sub

    Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As System.Object, _
      ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
      Handles BackgroundWorker1.ProgressChanged
        Me.LabelWarningMessage.ForegroundColor = Color.Red
        Me.LabelWarningMessage.Text = "Working in background..."
        Me.LabelWarningMessage.Visible = True
        Me.ProgressBar1.Value = e.ProgressPercentage
    End Sub

    Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, _
      ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
      Handles BackgroundWorker1.RunWorkerCompleted
        Me.LabelWarningMessage.Visible = False
        RaiseEvent FinishAsyncEvent("Interop User Control process finished.")
    End Sub
    
    Private Sub BackgroundWorker_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load
        Me.ProgressBar1.Value = 0
        Me.LabelWarningMessage.Visible = False
    End Sub

最后,确保 BackgroundWorker 事件已连接到我们编写的处理程序。

Background Worker events

同时,确保 BackgroundWorkerWorkerReportsProgress 属性设置为 True。构建项目。

打开一个新的 VB6 项目,并将 MultithreadedControl 组件添加到您的 VB6 Form 中,就像在前面的示例中所做的那样。此外,添加一个多行 TextBox 控件(Text1)、一个 ListBoxList1)和一个标有“Process”(Command1)的 CommandButton

为了接收来自 BackgroundWorker 控件的事件,您需要向该控件添加额外的引用。单击菜单项 Project | References...。由于之前已将其添加为组件,因此 MultithreadedControlCtrl 的引用将已勾选。现在您还需要包含对库 MultithreadedControl 的引用,以便捕获从 .NET 控件引发的事件。

Add reference

最后,粘贴以下 VB6 代码。在此代码中,您声明了对控件的新引用,这次用关键字“WithEvents”进行装饰,以便公开控件的事件。事件处理程序的连接仅基于过程的名称,因此您在键入将以此方式使用的 Sub 例程时必须小心。

始终在下划线之前使用您创建的第二个引用时使用的名称(在本例中为“BackgroundEvents”)。然后,在下划线之后,使用您的原始 .NET 控件中出现的实际事件名称。我在各种论坛上看到了许多关于 VB Interop 事件处理问题的帖子,这些问题基本上是由于拼写错误处理程序签名引起的——所以要小心。

Dim WithEvents BackgroundEvents As MultithreadedControl.BackgroundWorker

Private Sub Command1_Click()
    Me.Text1.Text = ""
    Me.List1.Clear
    Me.List1.AddItem ("Start processing from VB6: " & DateTime.Now)
    Me.BackgroundWorker1.StartProcessing
End Sub

Private Sub Form_Load()
    Set BackgroundEvents = Me.BackgroundWorker1
End Sub

Private Sub BackgroundEvents_StartEvent(ByVal EventText As String)
    Me.List1.AddItem (EventText)
End Sub

Private Sub BackgroundEvents_FinishAsyncEvent(ByVal EventText As String)
    Me.List1.AddItem (EventText)
    Me.List1.AddItem ("Finish processing from VB6:" & DateTime.Now)
End Sub

这个参考应用程序基本上演示了如何在 VB6 应用程序中使用 .NET BackgroundWorker。即使在处理正在进行并且状态栏正在更新以告知我们进度的情况下,最终用户也可以继续在文本框中键入。如果您从未用 VB6 编程过,那么这可能看起来是一项微不足道的成就。

对于我们职业生涯的大部分时间都在处理 Visual Basic 6 应用程序的人来说,这是一个突破。

VB6 Multithreaded App

在 VB6 中使用 XAML

不幸的是,您无法构建一个 XAML UserControl 或 XAML Form 然后直接在 VB6 中使用它。您也不能简单地将一个 XAML Form 添加到一个 Windows Application 项目中并通过它来暴露。经过一番技巧,您可以将一个 XAML UserControl 嵌入到 Windows Form 中,然后在 VB6 应用程序中使用它。以下演练将向您展示如何操作。

在 Visual Studio 中创建一个新的 VB6 InteropForm Library 项目,并将其命名为 XamlEmbeddedForm。将默认的 Windows Form 重命名为 XamlForm。现在,添加一个基于 .NET Framework 3.0 Custom Control Library (WPF) 项目模板的新项目,并将其命名为 XamlUserControl。此时,您可以添加任何您喜欢的 XAML 代码。对于参考项目,我使用了 WPF SDK 中找到的 Cube Animation 代码。构建 XamlUserControl 项目。在 XamlEmbeddedForm 中,添加对 UserControl 项目的引用。此外,添加以下四个库引用:PresentationCorePresentationFrameworkWindowsBaseWindowsFormsIntegration

Add references

现在,您需要向 FormLoad 事件添加一些代码,以便在 .NET Form 中托管 XAML UserControl。完整的代码隐藏应如下所示。

Imports Microsoft.InteropFormTools
Imports System.Windows.Forms.Integration
Imports System.ComponentModel
Imports System.Windows.Forms

<InteropForm()> _
Public Class XamlForm

    Private Sub XamlForm_Load(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) _
        Handles MyBase.Load
        ' Create the ElementHost control for hosting the
        ' WPF UserControl.
        Dim host As New ElementHost()
        host.Dock = DockStyle.Fill

        ' Create the WPF UserControl.
        Dim uc As New XamlUserControl.UserControl1()

        ' Assign the WPF UserControl to the ElementHost
        '  control's Child property.
        host.Child = uc

        ' Add the ElementHost control to the form's
        ' collection of child controls.
        Me.Controls.Add(host)
    End Sub
End Class

为了万无一失,再次重建您的解决方案。然后,转到 Tools 菜单并选择 Generate InteropForm Wrapper Classes(如果缺少此菜单选项,请参阅上面的安装说明)。这将向您的项目添加一个新的包装器类,该类可以暴露给 VB6。最后一次重建以在注册表中注册您的包装器类。此时,您的 .NET 代码已完成。

在 VB6 中打开一个新项目。打开 Project | References,并勾选两项将其添加到您的 VB6 项目:Microsoft Interop Forms Toolkit Library 以及您的 .NET 项目 XAMLEmbeddedForm,以将 .NET 程序集的 COM 包装器添加到您的 VB6 项目中。

.NET Interop Forms 很难知道宿主 VB6 应用程序何时启动和停止,因此必须向您的 VB6 应用程序添加一些额外代码来处理这些情况。将以下代码片段添加到您的 VB6 主窗体中,以便 .NET 代码在这些事件发生时得到通知。

Public g_InteropToolbox As InteropToolbox

Private Sub Form_Load()
    Set g_InteropToolbox = New InteropToolbox
    g_InteropToolbox.Initialize
    g_InteropToolbox.EventMessenger.RaiseApplicationStartedupEvent
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    g_InteropToolbox.EventMessenger.RaiseApplicationShutdownEvent
End Sub

我们快完成了。要从 VB6 打开您的 .NET Form,只需向主窗体添加一个命令按钮,并使用以下代码处理其点击事件。

Private Sub Command1_Click()
Dim xaml As New XamlEmbeddedForm.XamlForm
    xaml.Show vbModal
End Sub

如果一切顺利,当您单击按钮时,您应该会看到一个动画立方体,完全用 XAML 编写。

XAML Cube

结论

本教程 intended to walk you through the steps needed to create a useful .NET/VB6 integration。它还 intended to give you a sense of the almost limitless possibilities that are open to VB6 developers now that this technology has been made widely available。在人们预言 VB6 作为开发工具的末日过去半个十年后,我们应该接受 VB6 仍将陪伴我们相当长一段时间的想法。Interop Toolkit 2.0 确保 VB6 开发的剩余多年将是优雅而富有成效的。

更新历史

  • 6/01/2007
    • 修正了各种排版和用法错误。
  • 6/16 - 6/18 2007
    • 更新了 C# 模板和 C# 示例。C# 编译器处理 `ComClass` 属性的方式与 VB 编译器不同。具体来说,VB 编译器会在 IL 中创建额外的代码,以使 VB 类的事件和属性在 VB6 中可见,而 C# 编译器则不会。我最初编写的模板假设两种编译器将写入相似的 IL——由于这个错误的假设,C# UserControl 中引发的事件从未在 VB6 中收到。这已在更新的 C# 模板中得到纠正,方法是在 C# 中包含使事件可见所需的额外属性和接口——而 VB 编译器会自动为您在 IL 中创建这些接口。
    • C# 模板现在也将自动创建这些接口。当您向 UserControl 添加新事件时,您只需确保也将其添加到相应的接口中。接口的命名约定(一个用于公开的事件,一个用于公开的属性)只是 UserControl 类名称后跟两个下划线和一个下划线。这比 VB 模板所需的工作量要多一些——但也不是多太多。

      特别感谢 Mike Dooney 在这方面的帮助。

  • 6/18/2007
    • (至少对我来说)有趣的是,这篇 Interop 文章最近得到了 Microsoft VB Team 的认可,在此在此
© . All rights reserved.