Recipe 16.7 Create a Custom Smart Tag DLL

16.7.1 Problem

Users of my application prefer to use datasheet view for browsing data. I'd like to provide a smart tag that will enable them to open forms and reports. How can I create a custom smart tag that will allow users to open a form that shows all orders for a customer as well as open a report that shows total sales for a customer?

16.7.2 Solution

If you want to provide conditional processing for smart tag actions then you must create a smart tag DLL, using Visual Basic 6.0 or Visual Basic .NET. In this solution, you'll see how you can use Visual Basic 6.0 to accomplish this.

If you prefer, you can use Visual Basic .NET to create the smart tag DLL. In Chapter 17 you'll learn how to create .NET programs that can be called by Access. For smart tags, there is no particular advantage to using Visual Basic .NET and Visual Basic 6.0 will be more familiar to Access programmers who have worked with VBA, so we have chosen to use Visual Basic 6.0 for this example.


16.7.2.1 Setting up the DLL project

Follow these steps to create the DLL project using Visual Basic 6.0:

  1. Launch Visual Basic 6.0 and create a new DLL project. The sample application is named AccessSmartTag, and it includes one class, stActions. The stActions class provides the Actions interface that defines the smart tag actions you want to take.

  2. Add the references shown in Figure 16-10. The reference to the Microsoft Smart Tags 2.0 Type Library is required. This example also has a reference to the Microsoft Access 11.0 Object Library so that you can work with Access objects from your smart tag code and the Microsoft DAO 3.6 Object Library so that you can work with data objects.

Figure 16-10. Setting references to the Microsoft Smart Tags 2.0 Type Library, the Access 11.0 Object Library, and the Microsoft DAO 3.6 Object Library
figs/acb2_1610.gif
  1. Place the following statements in the Declarations of the stActions class. You do not need a Recognizer interface for a smart tag that is designed to work exclusively with Access:

    Option Explicit
    
    Implements ISmartTagAction
    Implements ISmartTagAction2
  2. The next step is to implement the smart tag action interface by creating properties and methods that describe the smart tag action DLL. Most of these properties are fairly straightforward and just return a requested string. The ISmartTagAction_ProgId( ) is the language-independent unique identifier that corresponds to the ProgID of the DLL class. In this example, the name of the project is AccessSmartTag, and the class name is stActions:

    Private Property Get ISmartTagAction_ProgId( ) As String
        ISmartTagAction_ProgId = "AccessSmartTag.stActions"
    End Property
  3. The ISmartTagAction_Name property is a short phrase that describes the DLL:

    Private Property Get ISmartTagAction_Name(ByVal lcid As Long) As String
        ISmartTagAction_Name = "Demo Smart Tag Actions"
    End Property
  4. The ISmartTagAction_Desc property is a longer description of the DLL:

    Private Property Get ISmartTagAction_Desc(ByVal lcid As Long) As String
      ISmartTagAction_Desc = _
       "This is a Sample SmartTag used to open Forms and Reports."
    End Property
  5. The ISmartTagAction_SmartTagCount property reflects the number of smart tag types. This example contains one smart tag, so the count is 1:

    Private Property Get ISmartTagAction_SmartTagCount( ) As Long
      ISmartTagAction_SmartTagCount = 1
    End Property
  6. Each smart tag type is defined by a namespace to keep it unique, which is defined in the ISmartTagAction_SmartTagName property. SmartTag type names are always in the format of namespaceURI#tagname. In this example, the (ismarttag = 1) condition isn't strictly necessary since there is only one smart tag type defined, but this shows a pattern you could use for handling multiple types:

    Private Property Get ISmartTagAction_SmartTagName(ByVal ismarttag As Long) As String
      If (ismarttag = 1) Then
        ISmartTagAction_SmartTagName = _
         "schemas-microsoft-com/smarttag/northwind#openform"
      End If
    End Property
  7. The ISmartTagAction_SmartTagCaption property allows you to specify the caption that will be used:

    Private Property Get ISmartTagAction_SmartTagCaption( _
        ByVal ismarttag As Long, ByVal lcid As Long) As String
         ISmartTagAction_SmartTagCaption = "Access Smart Tag Demo"
    End Property
  8. The ISmartTagAction_VerbCount is where you specify the number of verbs in the smart tag. In this example, there are two actions that the smart tag can take: opening a form or opening a report:

    Private Property Get ISmartTagAction_VerbCount(ByVal bstrName As String) As Long
        If (bstrName = "schemas-microsoft-com/smarttag/northwind#openform") Then
            ISmartTagAction_VerbCount = 2
        End If
    End Property
  9. Smart tag action clients will first ask action DLLs for a unique ID integer for each of the verbs it wants to support, passing in the name and ordinal number for each one. Generating the unique ID is totally up to the action DLL, which gives the action DLL more flexibility. For example, a smart tag action DLL can specify the same VerbID value for the same action across smart tag types, or it can use the same VerbID for similar variants of an action. In this example, the ISmartTagAction_VerbID property returns iVerb (the same ordinal number passed in) back to the action client as the unique ID:

    Private Property Get ISmartTagAction_VerbID( _
     ByVal bstrName As String, ByVal iVerb As Long) As Long
        ISmartTagAction_VerbID = iVerb
    End Property
  10. The ISmartTagAction_VerbNameFromID property is used internally to represent the verb ID:

    Private Property Get ISmartTagAction_VerbNameFromID(ByVal idVerb As Long) _
     As String
      Select Case idVerb
          Case 1
            ISmartTagAction_VerbNameFromID = "openCustomers"
          Case 2
            ISmartTagAction_VerbNameFromID = "openReport"
          Case Else
            ISmartTagAction_VerbNameFromID = ""
      End Select
    End Property
  11. The code in the ISmartTagAction2_VerbCaptionFromID2 property checks the VerbID and then uses the "///" syntax to get cascading menus in the smart tag. Figure 16-11 shows the results when the smart tag is accessed in the client application:

    Private Property Get ISmartTagAction2_VerbCaptionFromID2( _
     ByVal VerbID As Long, ByVal ApplicationName As String, _
     ByVal LocaleID As Long, ByVal Properties As SmartTagLib.ISmartTagProperties, _
     ByVal Text As String, ByVal Xml As String, ByVal Target As Object) As String
        If (VerbID = 1) Then
            ISmartTagAction2_VerbCaptionFromID2 = _
             "Smart Tag Actions///Open Customer Form"
        ElseIf (VerbID = 2) Then
            ISmartTagAction2_VerbCaptionFromID2 = _
             "Smart Tag Actions///Open Customer Report"
        End If
    End Property
Figure 16-11. Displaying a fly-out smart tag
figs/acb2_1611.gif
  1. The ISmartTagAction2_InvokeVerb2 method provides code to perform the actions that the smart tag takes. The first section of the code sets a variable to point to the Target, which is the Access control object passed in. If the smart tag is defined on a Table object instead of a form control, then Access creates a control under the covers that gets passed to the smart tag DLL:

    Private Sub ISmartTagAction2_InvokeVerb2( _
     ByVal VerbID As Long, ByVal ApplicationName As String, _
     ByVal Target As Object, ByVal Properties As SmartTagLib.ISmartTagProperties, _
     ByVal Text As String, ByVal Xml As String, ByVal LocaleID As Long)
    
    On Error GoTo HandleErr:
        Dim cb As Access.Control
        Set cb = Target
  2. The next block of code validates that the control source is CustomerID. If not, a MsgBox statement provides feedback to the user that the smart tag only works when attached to the CustomerID. If the smart tag is attached to CustomerID, the code gets a reference to the Access.Application object from the Target's Application property:

        If cb.ControlSource <> "CustomerID" Then
            MsgBox "This action only works if you run it on the Customer ID field.", _
             vbOKOnly, "Smart Tag Error"
             GoTo ExitHere
        Else
            Dim app As Access.Application
            Set app = cb.Application
        End If
  3. The code then branches based on VerbID. If the VerbID is 1, then the code sets a reference to the Application object's CurrentDb property to gain access to DAO objects. This allows the code to execute a query to obtain the total number of orders for a given CustomerID. This is then retrieved into a DAO Recordset, and passed to the Customers form as an OpenArgs argument:

        If VerbID = 1 Then
            Dim db As DAO.Database
            Dim rs As DAO.Recordset
            Set db = app.CurrentDb
            
            Dim strQry As String
            Dim strOrders As String
            strQry = "SELECT Count(*) AS NumOrders FROM Orders WHERE CustomerID='" _
             & cb.Value & "';"
            Set rs = db.OpenRecordset(strQry)
            If Not rs.EOF Then
                strOrders = "Total number of orders: " & rs!NumOrders
            Else
                strOrders = "No orders for this customer"
            End If
            rs.Close
            
            app.DoCmd.OpenForm "Customers", _
             WhereCondition:="[CustomerID] = '" & cb.Value & "'", _
             OpenArgs:=strOrders
  4. If the second action is chosen, then the code opens rptCustomers report, passing the CustomerID as the WhereCondition argument (without this WhereCondition, the report would open displaying all the customers):

        ElseIf VerbID = 2 Then
            app.DoCmd.OpenReport "rptCustomers", _
             View:=acViewPreview, _
             WhereCondition:="[CustomerID] = '" & cb.Value & "'"
        End If
  5. The error handling code is mainly useful for debugging. It displays any error information in a MsgBox statement:

    ExitHere:
       Exit Sub
       
    HandleErr:
       MsgBox Err.Number & " " & Err.Description, _
        vbCritical, "Error in AccessSmartTag.ISmartTagAction2_InvokeVerb2"
       Resume ExitHere
    End Sub
16.7.2.2 Compiling and registering the DLL project

Once you've written the code, build the DLL project by choosing File Make AccessSmartTag DLL from the menu. This will create the correct registry entries. Launch regedit from the Windows Start Run menu. To obtain the CLSID for the action handler, navigate to the following node in the Registry:

HKEY_CLASSES_ROOT\AccessSmartTag.stActions\Clsid

Double-click the Clsid node to obtain the value, as shown in Figure 16-12. Copy it to the clipboard and close the regedit window without saving.

Figure 16-12. Obtaining the Clsid from the AccessSmartTag.stActions
figs/acb2_1612.gif

You can then edit the registry directly or create a reg file to update the registry entries. Use Notepad to create a new file and name it Reg_AccessSmartTag.reg. The file should contain the following text. However, you will need to replace the value shown in the curly braces with the value that you copied to the Clipboard from the Registry in the previous step:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Office\Common\Smart Tag\Actions\
{9F7503BB-4BBA-4A4A-B1A5-A0DF0A0187F5}]

In case you ever need to unregister the smart tag, create a second file named Unreg_AccessSmartTag.reg. The file should contain the following text. Again, replace the value shown here in the curly braces with the value copied to the Clipboard:

Windows Registry Editor Version 5.00

[-HKEY_CURRENT_USER\Software\Microsoft\Office\Common\Smart Tag\Actions\
{9F7503BB-4BBA-4A4A-B1A5-A0DF0A0187F5}]

Save both files and double-click Reg_AccessSmartTag.reg. This will create the entries in the registry so that Access can recognize the smart tag actions.

Open the 16-07.MDB sample database and open the Customers form in design view. View the code in the form's Open event, which displays anything passed in the OpenArgs event in the form's Caption property:

Private Sub Form_Open(Cancel As Integer)
' Display any OpenArgs in the Caption
    Dim str As String
    str = Me.OpenArgs & ""
    If Len(str) > 0 Then
        Me.Caption = str
    End If
End Sub

Close the form and open the Customers table in design view. Assign the smart tag to the CustomerID field, as shown in Figure 16-13.

Figure 16-13. Assigning the smart tag to the CustomerID field in the Customers table
figs/acb2_1613.gif

Save the table and view it in datasheet view. When you choose the first smart tag action, the Customers form will open with the total number of orders for the selected customer displayed in the form's Caption. If you choose the second smart tag action, then the rptCustomers report will open displaying sales data for the selected customer.

16.7.3 Discussion

You can write a smart tag DLL in any language that supports writing COM add-ins. You can also write a smart tag DLL in managed (.NET) code.

There are two interfaces involved in implementing smart tag actions: the ISmartTagAction interface and the ISmartTagAction2 interface. These interfaces provide the client application with the information needed to support smart tag actions. The ISmartTagAction interface is compatible with Office XP, and the ISmartTagAction2 interface is specific to Office 2003, and allows you to tap into new functionality.

You do not need to implement the ISmartTagRecognizer and ISmartTagRecognizer2 interfaces in a smart tag DLL targeted specifically for Access because Access does not use recognizers.


The role of an ISmartTagAction interface is to provide actions for individual smart tag types. Each smart tag type is defined by a namespace URI plus its tag name to keep it unique. A "#" character is appended to the namespace URI and is used to separate the namespace URI from its tag name, as shown in this example, where "schemas-microsoft-com/smarttag/northwind" is the namespace URI and "openform" is the tag name. The combination results in the fully qualified name of the smart tag type. The URI portion of the property name ensures that it is globally unique and unambiguous, so that two tags with the same tag name (openform) can be differentiated:

Private Property Get ISmartTagAction_SmartTagName( _
 ByVal ismarttag As Long) As String
    ISmartTagAction_SmartTagName = _
     "schemas-microsoft-com/smarttag/northwind#openform"
End Property
16.7.3.1 Working with the Access object model

The most interesting part of the sample smart tag DLL is that it shows you how you can work with the Access object model as well as DAO. The code in the ISmartTagAction2_InvokeVerb2 method has an input parameter, Target As Object, which Access uses to pass in the control that has the smart tag attached. The code then creates an Access.Control variable that references the Target:

Dim cb As Access.Control
Set cb = Target

Once you have the Access Control object, you can then set a variable to point to the Access Application object, giving you full access to any part of your application:

Dim app As Access.Application
Set app = cb.Application

From there, you can work with the data in your application by creating a DAO Database object using the Application object's CurrentDb property:

Dim db As DAO.Database
Set db = app.CurrentDb

The code goes on to open a Recordset based on a query that counts the total number of orders for the selected customer, writing it to a String variable. It then opens the form to display the selected customer and passes that count value in the OpenArgs argument of the Application object's DoCmd.OpenForm method:

app.DoCmd.OpenForm "Customers", _
 WhereCondition:="[CustomerID] = '" & cb.Value & "'", _
 OpenArgs:=strOrders

When the Customers form opens, the code in the Open event evaluates whether any data has been passed in the OpenArgs argument, and then displays that information in the form's Caption property. If the form is opened normally without any OpenArgs data being passed to it, then the default caption is displayed:

Private Sub Form_Open(Cancel As Integer)
    Dim str As String
    str = Me.OpenArgs & ""
    If Len(str) > 0 Then
        Me.Caption = str
    End If
End Sub

The code for opening a report uses similar techniques. Creating a smart tag DLL allows you full access to the entire Access object model, and allows you to create conditional logic for your smart tag. Smart tags can be a good way to provide extra functionality for users who prefer working in datasheet view.

16.7.4 See Also

See the Preface for more information on working with DAO to access data.