Due to popular request, this lab is the Visual Basic 6.0 version of the ActiveX lab on MSDN (with some differences in the control we generate). The following lab has not been reviewed by the normal MSDN reviewers.

Prerequisites
Before you begin this lab, you should have a basic understanding of Microsoft® Office InfoPath™ 2003. You should also have some familiarity with the Microsoft Visual Basic® version 6.0 integrated development environment (IDE).
In addition, you should have a code-signing certificate so that you can sign your Microsoft ActiveX® controls.

Scenario
At Contoso Corp., sales representatives use a series of related InfoPath forms to collect sales information. Many of these forms include a list that displays different prescription medicines. This list is used in conjunction with a text box that displays the current price for each medicine.
As a form designer in the IT department, you can create a custom ActiveX control that encapsulates both controls into the same custom control. Also, you can embed code into the ActiveX control, which allows the latest price for each drug to be retrieved from an internal pricing database. Creating and distributing your own custom ActiveX control can save other form designers both time and effort. Instead of recreating the same set of controls over and over again, other designers can simply insert the ActiveX control you created from the Controls task pane in InfoPath.

Lab Objective
In this lab you will learn how to do the following:

  • Create an ActiveX control that can be used in InfoPath
  • Add the ActiveX control to the Controls task pane
  • Insert the ActiveX control into your form
  • Install and register the ActiveX controls on users' computers

Setup
Before beginning this lab, install Microsoft Visual Basic 6.0 Professional for Windows® or Microsoft Visual Studio® 6.0. In addition, you will need to obtain a code-signing certificate from a trusted certificate authority (CA).

Exercises

Exercise 1: Create an ActiveX control
Contoso uses a combination of list and text box controls on a frequent basis. As a member of the IT department, you need to write an ActiveX control containing both of these controls so form designers can simply insert this one custom control into their forms. You'll integrate the logic related to retrieving the current price for medications into the ActiveX control so that form designers don't have to worry about writing this code in the future.

Create a new ActiveX control

  1. Start Visual Basic.
  2. When the New Project dialog box appears, click ActiveX Control, and then click Open.
  3. Visual Basic creates a skeleton project with an empty ActiveX control called UserControl1.
    In the next step, you'll add two controls to this ActiveX control, and then add code to retrieve price information. ActiveX controls used in InfoPath have restrictions that are more strict than ActiveX controls used in Microsoft Internet Explorer. For example, InfoPath requires that ActiveX controls be marked as both safe for scripting and safe for initialization. If you write custom ActiveX controls for use in forms, you must implement the IObjectSafety interface so that InfoPath knows that a particular control is marked safe for scripting and safe for initialization. Because the IObjectSafety interface is not included in the Visual Basic references, you will need to build a type library (.tlb) file that Visual Basic can reference. The process for doing so is described in the following Knowledge Base article:
    http://support.microsoft.com/support/kb/articles/Q182/5/98.asp
    The first step is to import the following code and reference it in your project.

Mark the control as safe for scripting and initialization

  1. Get the OLE Automation Type Library Generator from the Visual Basic CD-ROM. To do this, copy all four files from the \Common\Tools\VB\Unsupprt\Typlib\ folder to your project folder (If you have not saved your VB project, then you should create a folder to store your project and these files in).
  2. Copy the following code into a text editor, such as Microsoft Notepad, and then save the file in the project folder as Objsafe.odl:
    [
        uuid(C67830E0-D11D-11cf-BD80-00AA00575603),
        helpstring("VB IObjectSafety Interface"),
        version(1.0)
    ]
    library IObjectSafetyTLB
    {
        importlib("stdole2.tlb");
        [
            uuid(CB5BDC81-93C1-11cf-8F20-00805F2CD064),
            helpstring("IObjectSafety Interface"),
            odl
        ]
        interface IObjectSafety:IUnknown {
            [helpstring("GetInterfaceSafetyOptions")]
            HRESULT GetInterfaceSafetyOptions(
                    [in]  long  riid,
                    [in]  long *pdwSupportedOptions,
                    [in]  long *pdwEnabledOptions);

            [helpstring("SetInterfaceSafetyOptions")]
            HRESULT SetInterfaceSafetyOptions(
                    [in]  long  riid,
                    [in]  long  dwOptionsSetMask,
                    [in]  long  dwEnabledOptions);
        }
    }
  3. To reference the file in Visual Basic, you must compile it into a .tlb file. Open a command prompt window, and then switch to the folder containing your project. This is the folder where you saved the Objsafe.odl file.
  4. At the command prompt, type the following command:
    MKTYPLIB objsafe.odl /tlb objsafe.tlb
    After creating the .tlb file, you can reuse it whenever you create ActiveX controls in the future.
  5. On the Project menu, click References.
  6. In the References – Project1 dialog box, click Browse, and then locate and open the Objsafe.tlb file in your project folder.
    The reference, called VB IObjectSafety Interface, now appears in the Available References list.
  7. Add a new module named basSafeCTL to your project.
  8. Insert the following code inside the module:
    Option Explicit

    Public Const IID_IDispatch = "{00020400-0000-0000-C000-000000000046}"
    Public Const IID_IPersistStorage = _
      "{0000010A-0000-0000-C000-000000000046}"
    Public Const IID_IPersistStream = _
      "{00000109-0000-0000-C000-000000000046}"
    Public Const IID_IPersistPropertyBag = _
      "{37D84F60-42CB-11CE-8135-00AA004BB851}"

    Public Const INTERFACESAFE_FOR_UNTRUSTED_CALLER = &H1
    Public Const INTERFACESAFE_FOR_UNTRUSTED_DATA = &H2
    Public Const E_NOINTERFACE = &H80004002
    Public Const E_FAIL = &H80004005
    Public Const MAX_GUIDLEN = 40

    Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
       (pDest As Any, pSource As Any, ByVal ByteLen As Long)
    Public Declare Function StringFromGUID2 Lib "ole32.dll" (rguid As _
       Any, ByVal lpstrClsId As Long, ByVal cbMax As Integer) As Long

    Public Type udtGUID
        Data1 As Long
        Data2 As Integer
        Data3 As Integer
        Data4(7) As Byte
    End Type

    Public m_fSafeForScripting As Boolean
    Public m_fSafeForInitializing As Boolean

    Sub Main()
        m_fSafeForScripting = True
        m_fSafeForInitializing = True
    End Sub
  9. On the Project menu, click Project1 Properties.
  10. On the General tab in the Project1 Properties dialog box, click Sub Main in the Startup Object list.
    The m_fSafeForScripting and m_fSafeForInitializing variables are used to specify the values of the safe for scripting and initialization variables. For the ActiveX control to work in InfoPath, both values must be set to True.
  11. Open the code window for your ActiveX control.
    Tip You should see UserControl1 in the title bar.
  12. Add the following line of code at the top of the code window:
    Implements IObjectSafety
    This indicates that your ActiveX control implements the necessary IObjectSafety interface.
  13. Now that you've referenced the IObjectSafety interface in your ActiveX control, you must implement the methods in IObjectSafety. Because this implementation is the same for any ActiveX control, you can copy the two methods below and paste them into your control's code:
    Private Sub IObjectSafety_GetInterfaceSafetyOptions(ByVal riid As _
    Long, pdwSupportedOptions As Long, pdwEnabledOptions As Long)

        Dim Rc      As Long
        Dim rClsId  As udtGUID
        Dim IID     As String
        Dim bIID()  As Byte

        pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER Or _
                              INTERFACESAFE_FOR_UNTRUSTED_DATA

        If (riid <> 0) Then
            CopyMemory rClsId, ByVal riid, Len(rClsId)

            bIID = String$(MAX_GUIDLEN, 0)
            Rc = StringFromGUID2(rClsId, VarPtr(bIID(0)), MAX_GUIDLEN)
            Rc = InStr(1, bIID, vbNullChar) - 1
            IID = Left$(UCase(bIID), Rc)

            Select Case IID
                Case IID_IDispatch
                    pdwEnabledOptions = IIf(m_fSafeForScripting, _
                  INTERFACESAFE_FOR_UNTRUSTED_CALLER, 0)
                    Exit Sub
                Case IID_IPersistStorage, IID_IPersistStream, _
                   IID_IPersistPropertyBag
                    pdwEnabledOptions = IIf(m_fSafeForInitializing, _
                  INTERFACESAFE_FOR_UNTRUSTED_DATA, 0)
                    Exit Sub
                Case Else
                    Err.Raise E_NOINTERFACE
                    Exit Sub
            End Select
        End If
    End Sub

    Private Sub IObjectSafety_SetInterfaceSafetyOptions(ByVal riid As _
    Long, ByVal dwOptionsSetMask As Long, ByVal dwEnabledOptions As Long)
        Dim Rc          As Long
        Dim rClsId      As udtGUID
        Dim IID         As String
        Dim bIID()      As Byte

        If (riid <> 0) Then
            CopyMemory rClsId, ByVal riid, Len(rClsId)

            bIID = String$(MAX_GUIDLEN, 0)
            Rc = StringFromGUID2(rClsId, VarPtr(bIID(0)), MAX_GUIDLEN)
            Rc = InStr(1, bIID, vbNullChar) - 1
            IID = Left$(UCase(bIID), Rc)

            Select Case IID
                Case IID_IDispatch
                    If ((dwEnabledOptions And dwOptionsSetMask) <> _
                 INTERFACESAFE_FOR_UNTRUSTED_CALLER) Then
                        Err.Raise E_FAIL
                        Exit Sub
                    Else
                        If Not m_fSafeForScripting Then
                            Err.Raise E_FAIL
                        End If
                        Exit Sub
                    End If

                Case IID_IPersistStorage, IID_IPersistStream, _
              IID_IPersistPropertyBag
                    If ((dwEnabledOptions And dwOptionsSetMask) <> _
                  INTERFACESAFE_FOR_UNTRUSTED_DATA) Then
                        Err.Raise E_FAIL
                        Exit Sub
                    Else
                        If Not m_fSafeForInitializing Then
                            Err.Raise E_FAIL
                        End If
                        Exit Sub
                    End If

                Case Else
                    Err.Raise E_NOINTERFACE
                    Exit Sub
            End Select
        End If
    End Sub
    To make the ActiveX control useful, you must add the controls you want it to include and also specify properties for InfoPath to bind to. In this first exercise, you'll add two controls to the ActiveX control.

Add controls to the ActiveX control

  1. On the Toolbox, double-click the combo box and text box controls to add them to the design surface, and then position them next to one another, as shown in the following illustration.
In your InfoPath form, the combo box will show a list of medications from which sales representatives can choose. The text box will show prices for the selected medication.
InfoPath interacts with ActiveX controls in a form through the use of Binding and Enabled properties. InfoPath uses the Binding property to store and retrieve data, and the Enabled property to either enable or disable a control. Visual Basic comes with a wizard that lets you quickly specify properties for an ActiveX control.

Specify properties for an ActiveX control

  1. On the Add-Ins menu, click ActiveX Control Interface Wizard.
    Note If you do not see this command on the Add-Ins menu, you can add it by clicking Add-In Manager on the Add-Ins menu.
  2. On the first page of the wizard, click Next.
  3. In the Selected names list on the Select Interface Members page, remove everything except the Enabled property, as shown in the illustration below. Although you can use as many standard properties as you would like for your control, InfoPath only uses two—the Binding and Enabled properties.
  4. To continue, click Next.
  5. To create a Value property, on the Create Custom Interface Members page, click New.
    Note You don't have to name the property "Value," but it's helpful to do so because InfoPath automatically recognizes properties with this name as the binding property for the ActiveX control.
  6. To continute, click Next.
  7. In the Public Name list on the Set Mapping page, click Enabled. In the Control list under Maps to, click UserControl, and then click Enabled in the Member list.
  8. Click Value in the Public Name list.
  9. In the Control list under Maps to, click Text1, and then click Text in the Member list.
  10. To continue, click Next, and then click Finish.

Add code to retrieve pricing information

  1. If it isn't already visible, open the form designer for UserControl1.
  2. Select the combo box you just added, and then in the Properties – Combo 1 window, click List.
  3. Click the arrow next to the List property, and then type the following Contoso medicines as values:
    Petosel
    Accuphen
    Ciprophen
  4. Open the code window for UserControl1 control, and then insert the following code:
    Private Sub Combo1_Click()
        Text1.Text = Combo1.ListIndex
    End Sub
    If you were creating a real ActiveX control, you'd likely have more complicated code that retrieved pricing information from a database or Web service. However, making calls to a database or Web service is beyond the scope of this lab. For the purposes of this exercise, the code uses hard-coded values for the Contoso medicine.
    At this point, you can compile your project.

Compile the ActiveX control

  1. On the File menu, click Make Project1.ocx.
  2. In the Make Project dialog box, save the .ocx file in your project folder, and then click OK to start compiling.

Exercise 2: Making ActiveX controls available to users
If you use a custom ActiveX control in a form, you must take measures to ensure that the control is installed and registered on users' computers. If you don't do this, users won't be able to open the form to fill it out. You can use InfoPath to package an installation .cab file with your form template. This .cab file installs the ActiveX control on users' computer (with their permission) and registers it for use. The following exercise shows you how to create this file.

Obtain the necessary command-line tools
Note Certain command-line tools are necessary for signing the package file. If you do not have the following files, you can download them from the Web.

  1. Download the necessary command-line tools.
  2. The following three tools are included with the Internet Development Software Development Kit (SDK) and the .NET Framework SDK. If you already have these SDKs installed on your computer, then there is no need to download anything extra. cert2spc.exe, makecert.exe, setreg.exe, signcode.exe http://www.microsoft.com/downloads/details.aspx?FamilyId=9B3A2CA6-3647-4070-9F41-A333C6B9181D&displaylang=en
    or
    http://www.microsoft.com/msdownload/platformsdk/sdkupdate/
    cabarc.exe
    http://support.microsoft.com/default.aspx?scid=KB;en-us;310618
    If you do not already have a code signing certificate from a Certificate Authority, you will need to get a test certificate to use on your computer.

Obtain a test certificate

  1. Open a command prompt window.
  2. The makecert.exe command-line tool can generate a test certificate for testing purposes. At the command prompt, type makecert newCert.cer –sv privatekey.pvk.
  3. Because this certificate is only good for testing, your computer will not have the necessary certificate authority (CA) certificate root on your computer. To turn this on, at the command prompt, type setreg 1 true.
    Now that you have a certificate installed on your computer, you will need to prepare it for signing.

Prepare your certificate for signing
If your certificate file is a .cer file, you will need to generate a .spc file.

  1. Open a command prompt window.
  2. An .spc file can be created by running the cert2spc.exe command-line tool on your .cer file. At the command prompt, type Cert2Spc newCert.cer output.spc.
You are now ready to sign the ActiveX control.

Sign the ActiveX control

  1. Copy the following files into one directory:
    • Your ActiveX control (.dll or .ocx) and any supplementary files that the control needs. (For the purposes of this lab, there aren't any extra files for the control you created.)
    • Your certificates and private keys (.spc and .pvk).
  2. Open a command prompt window.
  3. Package all your files up into a .cab archive. At the command prompt, type: Usage: cabarc N cabfile.cab .
    Note If you have an input-mask project, you can type cabarc N inputmask.cab inputmask.ocx at the command prompt. If you have multiple files, you can type cabarc N inputmask.cab inputmask.ocx setupfile.inf otherfiles.txt at the command prompt.
  4. Use the signcode tool to digitally sign the .cab file. At the command prompt, type signcode -spc output.spc -v privatekey.pvk -t http://timestamp.verisign.com/scripts/timstamp.dll inputmask.cab.
    Tip Instead of following the preceding step, you can choose to run signcode.exe, and a wizard will guide you through the necessary steps.

Exercise 3: Add your ActiveX control to InfoPath
Now that the ActiveX control is complete and you have packaged it, it is ready to be used within InfoPath. In the following exercise, you'll learn how to add the control to the Controls task pane.

Add the ActiveX control to the Controls task pane

  1. In design mode, click More Controls on the Insert menu.
  2. At the bottom of the Controls task pane, click Add or Remove Custom Controls.
  3. In the Add or Remove Custom Controls dialog box, click Add.
  4. On the first page of the Add Custom Control Wizard, click the ActiveX control that you just created in the Select a control list, and then click Next.
    By default, the ActiveX control is called Project1.UserControl1.
  5. On the next page of the wizard, click Include a .cab file, and then click Browse to search for the .cab file you created in Exercise 2.
  6. To continue, click Next.
  7. In the Binding property list on the next page of the wizard, click a property to bind to. The ActiveX uses this binding property to receive and store XML data.
  8. To continue, click Next.
  9. In the Enable or Disable property list on the next page of the wizard, click Enabled, and then make sure that the value is set to true. This step is necessary for InfoPath forms that use digital signatures, conditional formatting, or Reduced Functionality Mode.
  10. To continue, click Next.
  11. If you specified a binding property in step 7, you must now specify binding options for the ActiveX control. Although there are three types of binding options, for the purposes of this lab, you'll bind the control to a field with a simple data type. This data type is appropriate when the ActiveX control deals with single values. By default, Text (string) is selected in the Select one or more data types list, and also appears as the default data type. InfoPath will use the default data type when you insert the ActiveX control into your form from the Controls task pane.
Now that you have added the ActiveX control the Controls task pane, you can add it to your form.

Add the ActiveX control to your form

  1. In design mode, click Design a Form on the File menu.
  2. In the Design a Form task pane, click New Blank Form.
  3. In the Design Tasks task pane, click Controls.
  4. Under Insert Controls, click the name of your ActiveX control. By default, the name is Project1.UserControl1.
Your ActiveX control now appears in your InfoPath form. When you save your form, a copy of the .cab file will be packaged along with the form template (.xsn) file. InfoPath uses this .cab file to install and register the ActiveX control for users who don't already have it installed and registered on their computers.

Summary
In the previous exercises, you learned how to write a custom ActiveX control that meets all requirements for use in InfoPath. You also learned how to deploy the control to users' computers, how to add the control to the InfoPath Controls task pane, and how to insert it into the form.