Lab 6: Active X controls (with Visual Basic 6.0)

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:
    https://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 https://www.microsoft.com/downloads/details.aspx?FamilyId=9B3A2CA6-3647-4070-9F41-A333C6B9181D&displaylang=en
    or
    https://www.microsoft.com/msdownload/platformsdk/sdkupdate/
    cabarc.exe
    https://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 https://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.