Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Interop Forms Toolkit 2.0 Tutorial

0.00/5 (No votes)
16 Jun 2007 6  
Interop Forms Toolkit 2.0 is a new bridging tool allowing developers to use .NET Forms and .NET UserControls in VB6. This tutorial demonstrates how to add webservices, multithreading, and XAML to VB6 projects. It also provides custom C# Interop UserControl templates for use with the Toolkit.

Screenshot - Diagram.gif

Why use the Interop Toolkit?

A few years ago, the enterprise architects at the company I worked for came up with a central login mechanism for all the company's applications using web services. They even provided code samples in Java, C#, and VB.NET for using their new component. It was intended as a language agnostic solution. When we asked the architects what we should do with the VB6 applications that we were still supporting, the architects were nonplussed. They first provided some esoteric white papers on using SOAP with VB6, then they suggested that we upgrade all of our VB6 apps to .NET, and finally, they conceded that VB6 apps simply didn't have a place in their solution.

Had Interop Forms Toolkit 2.0 been available back then, we could have come up with an integration in under an hour. We would have simply copied the sample code into a new .NET User Control, used the Interop Toolkit to wrap it up as an ActiveX control, and then consumed the control in all of our VB6 apps.

Interop Toolkit 2.0 was just released at the beginning of May. The original Interop Toolkit already allowed VB developers to use .NET Forms in their applications. This is still in Toolkit 2.0, and appears not to have changed much.

What makes Toolkit 2.0 standout is its support for using .NET User Controls as ActiveX controls in VB6.

ActiveXHelpers Class

According to Microsoft, the toolkit is intended as part of a migration strategy for upgrading VB6 applications to .NET, piece by piece. I am not sure this is how it is likely to be used, however, or even if it necessarily ought to be used in this way.

Toolkit 2.0 makes most sense as a tool that allows VB6 developers to take advantage of .NET features without being forced onto an upgrade path. Most VB6 applications that are still around obviously meet certain needs very well. Why fix something that isn't broken?

There are times, however, when you may want to leverage .NET features in your VB6 application. For a long time, your only two choices were to upgrade the whole application to .NET, or to forego the nifty new features.

Toolkit 2.0 provides a third option. Simply add the .NET feature you need as a control.

This tutorial will lead you through:

  1. a mock application that implements the sort of technology we would have used to solve the problem outlined above. It will also cover,
  2. installing the Interop Toolkit,
  3. provide a reference app that gives developers the ability to use real multithreading in their VB6 apps, and finally
  4. provide a how-to for integrating XAML files into VB6.

Interop for C# Developers

Just as with the previous version, Interop Forms Toolkit 2.0 is geared towards VB.NET developers. The wizard, project templates, and item templates that come with the Toolkit only come in VB flavors. This makes a certain amount of sense, since it will mostly be VB developers who will implement these .NET/VB6 integrations. Many developers like to work with both languages, however, and there may be integration scenarios where you need to expose pre-existing C# code to VB6.

For those cases, I've written the C# item template and C# project template linked above for Interop User Controls. Simply copy the project template zip file into your project templates folder (the default location is ...\My Documents\Visual Studio 2005\Templates\ProjectTemplates\Visual C#) and the item template zip file into your item templates folder (...\My Documents\Visual Studio 2005\Templates\ItemTemplates\Visual C#). I believe that these templates will only work with Visual Studio 2005, but I haven't yet tested on older versions of Visual Studio to make sure.

For cases where you need to expose a C# Form, you can use the clever wizard and template written by Leon Langleyben, which I was able to get to work with a bit of tweaking -- though no fault of Leon's, since his add-in was written for the previous version of the Interop Toolkit (refer to the CSXamlEmbeddedForm project in the included C# samples to see what the generated wrapper class should look like in C#).

Installing the Toolkit

Installing the toolkit is fairly straightforward. Navigate to the Toolkit Download Site and, of the three downloads available, run the InteropFormsInstaller.msi file. In most cases, this is all you need to do. When you open the Visual Studio .NET IDE, you should find the new templates, VB6 Interop UserControl and VB6 Interop Form Library, available when you create a new VB.NET project. Under your Tools menu, you should also find a new wizard labeled "Generate InteropForm Wrapper Classes".

If the new wizard does not appear in your Tools menu, there may have been a problem installing it. Check Tools | Add-in Manager to make sure that this wizard is selected. If it is present and selected in the Add-in Manager, but still does not appear in your Tools menu, you can run the following command in your command line to reset it: Devenv /resetaddin Microsoft.InteropFormTools.InteropFormProxyGenerator.Connect.

Installing the Toolkit on Vista

In order to use the Toolkit on Windows Vista, you will need to download both the MSI file as well as the setup file to your hard drive. Then, run the setup. Running the MSI file alone will generate an install error.

To use the C# UserControl templates on Vista, you will need to run Visual Studio as an Administrator. Right-click on the link to your Visual Studio IDE and select the Run as administrator popup menu option. This will let Vista's UAC feature know that it is alright for the UserControl to write to the registry on build events.

Building a User Control

In this first example, the UserControl will take care of all the processing, and will just sit in the VB6 Form, while the example following it will demonstrate how to pass information between VB6 and VB.NET. Any code snippets will be in VB, though the source code samples linked above include both a VB.NET as well as a C# sample of the control.

In this example, we use the Daily Dilbert web service to download a cartoon to our control (you will notice that the Daily Dilbert comes up a lot on CodeProject -- for good reason; it happens to be one of the few interesting web services that can actually be used in public tutorials, since it does not require a fee or registration).

Open new project

Begin by creating a new VB6 Interop User Control project using the VB6 Interop UserControl project template. Name the project DailyDilbertControl. By default, a UserControl file is created, called InteropUserControl.vb. Since this is the control name that will be displayed in your VB6 control panel, you should rename it to DilbertService.vb, to be more descriptive.

In your new project, you will find the following files: ActiveXControlHelpers.vb, InteropUserControl.bmp, and InteropUserControl.manifest. ActiveXControlHelpers.vb, as the name suggests, includes several static helper methods that make the conversion of your UserControl into an ActiveX control possible. There are register and unregister methods, which add details about your control to the registry. There are methods that help convert things like color codes between the .NET scheme and the OLE scheme used in VB6. There is also a method that wires up your events to work with VB6. You can have multiple user controls in your project, but should only have one ActiveXControlHelpers file per project.

The InteropUserControl.bmp is used to display your control in the VB6 Toolbox. I will cover how to customize your ActiveX control image, later in this tutorial.

Add a new web reference to the Daily Dilbert web service to your project. To do this, right click on your project and select Add Web Reference... A new dialog will pop up. Enter http://www.esynaps.com/WebServices/DailyDiblert.asmx for the URL, and click on Go. Finally, rename the web reference to "DDService" and select Add Reference at the lower right of the dialog. Your Solution Explorer should now look like this.

Add web reference

Add a 650 by 215 pixel PictureBox called DilbertPictureBox to your UserControl, as well as a Button called RetrieveButton. Create a new RetrieveButton_Click event handler by double clicking on RetrieveButton. Paste in the following code, which will call the web service and retrieve today's Dilbert strip.

    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

To finish making your control visible from VB6, just press F5 to preview the control, or simply build it. It will look like this in your Visual Studio UserControl Test Container:

Preview Dilbert Control

And that's all it takes to build an ActiveX control in Visual Studio .NET.

Adding an ActiveX Control Image

When you build a new control, InteropUserControl.bmp will be used as the default image for your component in the VB6 Toolbar. You can always use a different image, though. For this project, I want to use this image of Dilbert:

Screenshot - Dilbert.gif

To add it, you first have to add the bitmap file you want to use to your project and set its Build property to "content". Now, open the InteropUserControl.rc resource script file with Notepad. Do not use Visual Studio to do this, as this will mess up your resource script file. InteropUserControl.rc can be found under the DailyDilbertControl project folder. Beneath the default 101 BITMAP InteropUserControl.bmp entry, add an additional entry specifying the custom bitmap you want to use.

Edit resource script

Save your resource script file. Now, open ActiveXHelpers.vb and find the RegisterControl method. This is where registry entries are created. In the section where the bitmap file is specified, replace the default entry, "101", with a reference to your own bitmap.

Replace bitmap reference

Now, rebuild your control to make sure a new compiled resource file is created. The new image should appear in the VB6 Toolbox rather than the default image.

VB6 Control Toolbar

More About Adding ActiveX Control Images

You can use a different image for each UserControl in your project, but you will have to modify the ActiveXControlHelpers.vb file a bit to make this work. Alter the RegisterControl method signature to take a second String parameter, and then pass this parameter to the line where the code specifies the resource ID of the image.

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

Then, in each UserControl, add the BitmapId you want to use for your control to the RegisterControl call.

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

Rebuild the entire project once more.

Adding the UserControl to a VB6 project

Add DailyDilbert Component

This is actually the easiest part. Create a new VB6 project. Press CTRL+T to add a new component to your form, and check the DailyDilbertControl library. Press OK. (Vista behaves a bit strangely when you try to add your ActiveX control. It will occasionally throw an error the first time you select OK, then will work normally the second time you do so. Just to be safe, click Apply first just to see if there is an error, and then OK.) Any UserControl in your project will now appear on the VB Toolbar. Simply select the control you want to use, and draw it onto your VB6 form. Press F5 to see your .NET UserControl run in a Visual Basic 6 application.

VB6 Dilbert

Adding True Multithreading to VB6

When I was working with VB6 on a regular basis, one of the main impetuses for upgrading to .NET was the ability to implement multithreading. Interop UserControls provide an easy way to add true multithreading to a VB6 application. In a common scenario, you may want the users of your VB app to be able to kick off a process and then continue with their work while the processing occurs in the background. To simulate this scenario, the reference code we are about to build will use a BackgroundWorker control that will perform a time-consuming process in the background while updating a progress bar. In the meantime, the users of the VB6 application that consumes the control can continue with their work.

Create a new VB6 Interop UserControl project called MultithreadedControl. Add a BackgroundWorker control named BackgroundWorker1, a Label called LabelWarningMessage, and a ProgressBar called ProgressBar1. Paste in the following code:

    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

Finally, make sure that the BackgroundWorker events are hooked up to the handlers we've written.

Background Worker events

Also, make certain that the BackgroundWorker's WorkerReportsProgress property is set to True. Build the project.

Open a new VB6 project and add the MultithreadedControl component to your VB6 Form, as you did in the previous example. Also add a multiline TextBox control (Text1), a ListBox (List1), and a CommandButton labeled "Process" (Command1).

In order to receive events from the BackgroundWorker control, you will need to add an additional reference to the control. Click on the menu item Project | References... A reference to MultithreadedControlCtrl will already be checked off from previously adding it as a component. You will now need to also include a reference to the library MultithreadedControl in order to capture events thrown from the .NET control.

Add reference

Finally, paste in the following VB6 code. In this code, you declare a new reference to the control, this time decorated with the keyword "WithEvents" in order to expose the control's events. Wiring up handlers for the events is based only on the names of the procedures, so you have to be careful when typing out the Sub routines that will be used in this way.

Before the underscore, always use the same name you used when you created the second reference (in this case, "BackgroundEvents"). Then, after the underscore, use the actual event name as it appears in your original .NET control. I've seen lots of problems posted to various message boards concerning problems with VB Interop event handling that basically came down to misspelling a handler's signature -- so be careful.

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

This reference app basically demonstrates how a .NET BackgroundWorker can be used inside a VB6 application. Even while the processing is occurring and updating the status bar to let us know how far it has gotten, the end-user can continue typing into the textbox. If you have never programmed in VB6, then this probably seems like a trivial accomplishment.

For those of us who have worked on Visual Basic 6 apps for large portions of our careers, it is a breakthrough.

VB6 Multithreaded App

Using XAML in VB6

You cannot build a XAML UserControl or a XAML Form and then consume it directly in VB6, unfortunately. You also cannot simply add a XAML Form to a Windows Application project and expose it that way. With a bit of finesse, what you can do is embed a XAML UserControl in a Windows Form and consume that in your VB6 apps. The following walkthrough will show you how.

Create a new VB6 InteropForm Library Project in Visual Studio, and call it XamlEmbeddedForm. Rename the default Windows Form to XamlForm. Now, add a second project based on the .NET Framework 3.0 Custom Control Library (WPF) project template and call it XamlUserControl. You can add whatever XAML code you like, at this point. For the reference project, I've used the Cube Animation code found in the WPF SDK. Build the XamlUserControl project. In XamlEmbeddedForm, add a reference to the UserControl project. Also add the following four library references: PresentationCore, PresentationFramework, WindowsBase, and WindowsFormsIntegration.

Add references

You now will need to add some code to the FormLoad event in order to host the XAML UserControl in your .NET Form. The complete code-behind should look like this:

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

Rebuild your solution one more time for good measure. Then, go to the Tools menu and select Generate InteropForm Wrapper Classes (if this menu option is missing, refer to the installation instructions above). This will add a new wrapper class to your project that can be exposed to VB6. Rebuild one last time to register your wrapper class in the registry. At this point, your .NET code is complete.

Open a new project in VB6. Open Project | References, and check two items to add them to your VB6 project: Microsoft Interop Forms Toolkit Library as well as your .NET project, XAMLEmbeddedForm, to add the COM wrapper for your .NET assembly to your VB6 project.

.NET Interop Forms have difficulty knowing when the host VB6 application starts and stops, so some extra code must be added to your VB6 application to handle this. Add the following code snippet to your VB6 Main Form so the .NET code is informed when these events occur:

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

We are nearly done. To open your .NET Form from VB6, just add a command button to your Main Form and handle its click event with the following code:

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

If everything goes right, when you click the button, you should see an animated cube, written all in XAML.

XAML Cube

Conclusion

This tutorial is intended to walk you through the steps needed to create a useful .NET/VB6 integration. It is also 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. Half a decade after people were foretelling the doom of VB6 as a development tool, we should come to terms with the idea that VB6 will still be with us for quite a while longer. Interop Toolkit 2.0 ensures that the many years left to VB6 development will be both graceful and productive.

Update History

  • 6/01/2007
    • Corrected various typographical and usage errors.
  • 6/16 - 6/18 2007
    • Updated the C# templates and C# samples. The C# compiler handles the ComClass attribute differently than the VB compiler. To be specific, the VB compiler creates additional code in IL to make a VB class' events and properties visible in VB6, while the C# compiler does not. I had written the original templates with the assumption that the two compilers would write similar IL -- because of this faulty assumption, the events thrown in a C# UserControl were never received in VB6. This has been corrected in the updated C# templates by including in C# the extra attributes and interfaces required to make the events visible -- interfaces which the VB compiler automatically creates for you in IL.
    • The C# templates will now also automatically create these interfaces for you. When you add a new event to your UserControl, you will just need to be sure to add it to the appropriate interface, as well. The naming convention for the interfaces (one for exposed events, one for exposed properties) is simply the name of the UserControl class preceded by two underscores and one underscore, respectively. It's a little bit more manual labor than the VB templates require -- but not too much more.

      Special thanks to Mike Dooney for his help with this.

  • 6/18/2007
    • Of some interest (at least to me), this Interop article recently received a nod from the Microsoft VB Team, here and here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here