The Definitive \"Hello World\" Managed Smart Document Tutorial

 

John R. Durant
Microsoft Corporation

September 22, 2003

Applies to:
    Microsoft® Office Word 2003
    Microsoft Office Excel 2003
    Microsoft Office System

Summary: This article gives a brief overview of smart document technology and walks you through the steps of creating, deploying, and using a smart document solution. You will encounter all of the essential milestones in smart document development and gain a foundation in the technology that will help you develop more complex solutions. (28 printed pages)

**Note   **This article assumes a working knowledge of Microsoft .NET development and deployment.

Contents

Smart Document Conceptual Overview
Creating the Project
Adding Smart Document Controls
Populating and Using Controls
Deploying and Using the Solution
Debugging a Managed Smart Document DLL
Conclusion

In the past weeks, a lot of people have asked me how to get a basic smart document solution up and running. There is so much that has been, and will be said elsewhere about smart documents—their true value, development guidelines, security best practices, and so forth—but in this month's column, I want to show how to build one from scratch. Admittedly, the column is a bit long, but I wanted to cover the end-to-end creation, not leaving anything out. It wasn't easy deciding what should be included and what should not be, but I hope the final result gives you enough guidance to have an initial success with smart documents and makes you want to know more about them.

Smart Document Conceptual Overview

Smart documents are documents in Microsoft® Office Word 2003 or workbooks in Microsoft Office Excel 2003 that are associated with an XML schema so data in a document, rather than just being a paragraph or range of cells, acquire special meaning. For example, rather than knowing that a paragraph is the second one in the document, it would be much more valuable to know that the paragraph is the one destined to contain a product description. Furthermore, by knowing that the paragraph is supposed to contain product description detail, the host application can do the following types of tasks:

  • Restrict the length of the product description
  • Add help for users when adding product descriptions
  • Provide an interface with a drop-down list of common phrases for descriptions
  • Let users know if they have neglected to add required data
  • Provide an interface with check boxes or option buttons whose selection could automatically populate the paragraph with text

What smart documents offer is a way to make the host application more responsive to the data in a document or workbook. Smart documents help the data become meaningful to the host application. In this way, the application such as Word or Excel becomes more than a feature-rich application (a great story on its own) by becoming contextually aware of what users are producing with those features. The solution you build in this article is shown in Figure 1.

Figure 1. A view of a sample smart document.

Creating smart document code can be simple and uncomplicated or it can contain thousands of code lines and reach far into a company's enterprise. Smart document solutions cannot be written in VBA, but they can be written using Microsoft Visual Basic® 6.0, Microsoft Visual Basic .NET, Microsoft Visual C#® .NET, or Microsoft Visual C++® development systems. You can deploy smart documents over a corporate intranet, over the Internet, or through Web sites based on Microsoft SharePoint™ Products and Technologies.

For a detailed overview including explanations of the technical aspects of smart documents, see the Smart Documents Beta 2 Development Overview.

To develop this "Hello World" smart document, we perform the same steps as you would use to create a more complicated one:

  • Create a DLL that uses the smart document API to work with the Document Actions task pane and respond to user input.
  • Create an XML expansion pack manifest that attaches the solution to the target document. Attaching the solution includes associating a schema file and deploying the DLL to the target workstation.

Creating the Project

As mentioned previously, this project requires Visual Basic 6.0 or a .NET-compliant language like C# or Visual Basic .NET to create a smart document solution. In the sample for this article, Visual Basic .NET is used in Visual Studio .NET 2003, although you could use other languages and the earlier version of Visual Studio .NET.

Note   The setup for this project assumes that you have installed the .NET Programmability support for Microsoft Word 2003 and for Microsoft Smart Tags 2.0. Installing the .NET Programmability Support means that the Primary Interop Assembly for a target COM library is installed. It is also necessary to install Microsoft Forms 2.0 .NET Programmability Support and Smart Tag .NET Programmability Support.

To verify the installation, confirm the following registry keys:

Library Key
Microsoft Office Word 2003 HKEY_CLASSES_ROOT\TypeLib\{00020905-0000-0000-C000-000000000046}\8.3
Microsoft Smart Tags 2.0 HKEY_CLASSES_ROOT\TypeLib\{9B92EB61-CBC1-11D3-8C2D-00A0CC37B591}\1.2

In both of these keys you will find a string value called PrimaryInteropAssemblyName. The corresponding values for the two COM Interop libraries should include the PIA namespace and version as follows:

Library Key
Microsoft Office Word 2003 Microsoft.Office.Interop.Word, Version=11.0.0.0
Microsoft Smart Tags 2.0 Microsoft.Office.Interop.SmartTag, Version=11.0.0.0

If Primary Interop Assemblies are not installed, you can install them by running the Office Setup and choosing to Add or Remove features. Within each Office application is an option to add .NET Programmability Support. For more information, see the following articles:

The following procedure walks you through creating the basic project structure.

To create the project

  1. In Visual Studio .NET, create a project, and in the New Project dialog box (see Figure 2), click Class Library and name the project ProductInfo, and then click OK.

    Figure 2. Using the New Project dialog box

  2. In the project, add a reference to the Microsoft Smart Tags 2.0 Type Library and the Microsoft Word 11.0 Object Library. To do so:

    • In Solution Explorer, right-click References and then click Add Reference.

    • Click COM, double-click Microsoft Smart Tags 2.0 Type Library.

      -OR-

    • Click Select to add it to the Selected Components list.

    • Do the same for the Microsoft Word 11.0 Object Library

    • Click OK to close the dialog box.

    Note   You should see SmartTagLib now in the References node in the Solution Explorer window. If you highlight it and look at its properties in the Properties window, the Copy Local property should be set to False. And the Path property should point to its location in the glbal assembly cache (GAC) including its version number: 11.0.0.0. It should look something like: C:\WINDOWS\assembly\GAC\Microsoft.Office.Interop.SmartTag\11.0.0.0__71e9bce111e9429c\Microsoft.Office.Interop.SmartTag.dll. The Copy Local property for Word should be False as well, and its Path property should also point to the GAC.

  3. Add a reference to the Microsoft Word 11.0 Object Library.

When you create the project, Visual Studio .NET adds a generic class file, Class1.vb that contains a definition for a class, Class1. These names are not normally useful for a custom solution, so rename the file name ProductSD.vb and rename the class to ProductSmartDocument" In the Solution Explorer window, your project structure should look like Figure 3.

Figure 3. The structure of the smart document solution

You also want to add a few Imports statements at the beginning of the code file to make it easier to use long namespaces in the code. The code within the ProductSD.vb file should look like this:

Imports Microsoft.Office.Interop.SmartTag
Imports Word = Microsoft.Office.Interop.Word
Public Class ProductSmartDocument
  'Implementation code will go here
End Class

Before adding more code to the file, a good overview of what that code contains is in order.

Remember, the whole idea of smart documents is to put controls in the task pane that respond to data activity in the document. So, the two main jobs of code in a smart document component are to specify which controls should appear for each data element and what those controls should do when they are used. For example, in Figure 4 you can see how the task pane differs when the insertion point is within a node of the XML structure (note the highlighted text) and when it is not. When the insertion point is within a valid node of the XML structure, the host application recognizes this and displays the controls in the task pane.

Figure 4. The host application responds when the insertion point is placed in a node in the XML structure.

If the user clicks on the Insert button in the task pane as shown in Figure 5, code responds to this event. In this case, the code merely extracts the selected text from the list box and places it within the active XML element in the actual document.

Figure 5. The task pane shows two controls when the element for product caption information is selected.

You can see that for this node, ProductCaption, there are two controls that appear in the task pane—a list box and a button. This list box is populated with four items, and when the user clicks on the button, the text from the list box is inserted into the document. Other nodes have different controls, and those controls may react differently to user activity. So, instead of having a list box, there may be option buttons, a combo box, text boxes, or a custom Microsoft ActiveX® control. Instead of simply inserting text the way the controls in Figure 5 do, code for a different XML node could create documents, export data to databases, or do an infinite variety of things.

The code to make all this happen can be divided into two main categories:

  • Code to set up the controls and link them to XML nodes in a schema
  • Code to react when the user interacts with the controls

Adding Smart Document Controls

We will first look at the code to set up controls and associate them with nodes in a schema. The schema looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns="urn:schemas-microsoft-com.office11demos.WordProductsSD"
    targetNamespace="urn:schemas-microsoft-
    com.office11demos.WordProductsSD"
    elementFormDefault="qualified"
    attributeFormDefault="unqualified" 
    id="ProductInfo">
  <xsd:element name="ProductDescription" type="xsd:string"/>
  <xsd:element name="ProductCaption" type="xsd:string"/>
  <xsd:element name="ProductID" type="xsd:string"/>
</xsd:schema>

The schema, named ProductInfo.xsd and saved in the same directory as the final solution executable, simply contains three elements that contain string values in the document. Each element is in a namespace:

urn:schemas-microsoft-com.office11demos.WordProductsSD

This namespace is one we have arbitrarily defined, and it, along with the element names, will be of use in the code of the smart document DLL. The schema file is not actually embedded or explicitly referenced in the DLL, although its structure informs the design of the code. In fact, these elements become defined types with the smart document DLL code and are the key link between the data in the document and the actions of the smart document.

Implementing ISmartDocument

The first thing to do in the code is implement all of the interfaces for the ISmartDocument interface. This is done by typing Implements ISmartDocument within the class definition. Make sure that all of the members for that interface are implemented in the class, even if the implemented interface is not used. To implement members, you must choose each member from the Procedure list in Visual Studio .NET and click it. This adds it to your code pane. You need not actually add code to each procedure implementation.

Note   Visual Studio .NET 2003 automatically adds code stubs for all of the members of an interface when the Implements keyword is used, whereas Visual Studio .NET 2002 requires that you explicitly select each member and add its implementation.

Before looking at these properties and methods, we will add at the beginning of the class definition some declarations that those members use:

   Public Const BASEURI As String = _
    "urn:schemas-microsoft-com.office11demos.WordProductsSD"
   Public Const PRODUCT_DESC As String = BASEURI _
    & "#ProductDescription"
   Public Const PRODUCT_CAPTION As String = BASEURI _
    & "#ProductCaption"
   Public Const PRODUCT_ID As String = BASEURI _
    & "#ProductID"
   Public Const TYPESCOUNT As Integer = 3
   Private ListBoxTextValue As String

The first constant, BASEURI, is the same as the one defined in the schema shown earlier. This is important because it links the data activity in the host application with the logic behind the scenes in your smart document DLL. The three constants are subsequently defined by concatenating the BASEURI constant with a unique string matching the three elements defined in the schema. The TYPESCOUNT constant is used later on to tell the smart document object model how many types it needs to consider. The final constant is one that is used in response to activities with a control and need not be explained at this point.

The properties the smart document object model uses to set things up are used in specific order. The SmartDocXmlTypeCount property is the first to be accessed. It determines how many of the elements in the namespace have controls associated with them. In our example, all of the elements have controls assigned to them, but this may not always be so.

   Public ReadOnly Property SmartDocXmlTypeCount() As Integer Implements _
     Microsoft.Office.Interop.SmartTag.ISmartDocument.SmartDocXmlTypeCount
      Get
         Return TYPESCOUNT
      End Get
   End Property

In this case, the property merely returns the value of the previously defined constant, TYPESCOUNT. Because the object model knows that there are three elements, it accesses the next property, SmartDocXmlTypeName, three times.

The SmartDocXmlTypeName property has a parameter, XMLTypeID, which is an integer, passed to it. This parameter is a number that the smart document object model assigns to the each of the types it needs to manage. In our example, the property is called three times (because the SmartDocXmlTypeCount specified that there are three types in play). The first time it is called, 1 is the value of the parameter. The second time, 2 is the value; and the third time, 3 is the value.

   Public ReadOnly Property SmartDocXmlTypeName(ByVal XMLTypeID _
    As Integer) As String Implements _
    Microsoft.Office.Interop.SmartTag.ISmartDocument.SmartDocXmlTypeName
      Get
         Select Case XMLTypeID
            Case 1
               Return PRODUCT_DESC
            Case 2
               Return PRODUCT_CAPTION
            Case 3
               Return PRODUCT_ID
         End Select
         Exit Property
      End Get
   End Property

Notice how for each number, the code returns a string containing the actual element or type name. There is no forseeable reason why the PRODUCT_DESC is associated with the number 1. The association between a number and the type is arbitrary. One could just as easily change the order of the element types returned because the object model is not concerned with this order. All it must know is what type it should associate with its own internal tracking number—the integer it assigns.

The next property accessed is the SmartDocXmlTypeCaption property. This property also has a parameter whose value is the integer the smart document object model has assigned to a type. In our example, because there are three types, this property is also accessed three times.

   Public ReadOnly Property SmartDocXmlTypeCaption(ByVal XMLTypeID As _
   Integer,  ByVal LocaleID As Integer) As String Implements _
   Microsoft.Office.Interop.SmartTag.ISmartDocument.SmartDocXmlTypeCaption
      Get
         Select Case XMLTypeID
            Case 1
               Return "Product Description"
            Case 2
               Return "Product Caption"
            Case 3
               Return "Product ID"
         End Select
      End Get
   End Property

Here, the association between numbers and the returned string is not arbitrary. The captions for the numbers must be meaningful to the types associated with the same numbers in the previous property, SmartDocXmlTypeName. Because the number 1 is associated with the PRODUCT_DESC constant in that property, it makes sense to return a string that relates to a product description for the caption. Once the order of things is laid out in the SmartDocXmlTypeName property it must be followed consistently in the rest of the code.

The next property accessed is the ControlCount property. For each element, identified by the unique name of the type, this property returns the number of actual controls that should be used when the target type is in play.

   Public ReadOnly Property ControlCount(ByVal XMLTypeName As String) _
    As Integer Implements _
    Microsoft.Office.Interop.SmartTag.ISmartDocument.ControlCount
      Get
         Select Case XMLTypeName
            Case PRODUCT_DESC
               Return 3
            Case PRODUCT_CAPTION
               Return 2
            Case PRODUCT_ID
               Return 3
            Case Else
               Return 0
         End Select
      End Get
   End Property

For example, Figure 6 shows two controls in the task pane for the ProductCaption element. The type in use is urn:schemas-microsoft-com.office11demos.WordProductsSD#ProductCaption. Therefore, this property specifies that for this type, two controls are to be used.

Each control used in the task pane must have a unique identifier, one that can be accessed in the host application itself. The unique integers that are used up to this point will not do. They are only integers used by the smart document object model internally to track the different types. The ControlID property makes it possible to assign your own unique number to the controls in the task pane so that you can use them in code later on. This is easily one of the hardest things to grasp when working with smart documents, so let us take a moment to explain its use fully.

The reason you need to specify your own user-defined names is that as this property is called and a type name is passed in, the smart document object model resets the number for the controls back to 1 for each type. So, the numbers for the three controls (buttons, text boxes, or list boxes, and so forth) for the first type would be 1, 2, and 3. If the second type also had three controls assigned to it, the numbers would be 1, 2, and 3 also, because the beginning point is reset for each type by default. Adding conditional logic in the ControlID property allows you to take control of the number scheme.

   Public ReadOnly Property ControlID(ByVal XMLTypeName As String, _
    ByVal ControlIndex As Integer) As Integer Implements _
    Microsoft.Office.Interop.SmartTag.ISmartDocument.ControlID
      Get
         Select Case XMLTypeName
            Case PRODUCT_DESC
               Return ControlIndex
            Case PRODUCT_CAPTION
               Return ControlIndex + 100
            Case PRODUCT_ID
               Return ControlIndex + 200
            Case Else
               Return 0
         End Select
      End Get
   End Property

In this code, the first type simply uses the default starting point of 1, which is the value of the ControlIndex parameter. The second type uses the starting point of 1 and adds 100. The third type uses the same starting point, but adds 200. This allows each type to have 100 controls assigned to it, much more than would ever be practically used.

The next property accessed is the ControlNameFromID property. This property merely receives, as a parameter, the number that is assigned to the control. So, if the control is the second one of the PRODUCT_CAPTION type, its number is 102. This property merely adds that number to the namespace of the schema and returns it.

   Public ReadOnly Property ControlNameFromID _
    (ByVal ControlID As Integer) As String Implements _
    Microsoft.Office.Interop.SmartTag.ISmartDocument.ControlNameFromID
      Get
         Return BASEURI & ControlID.ToString
      End Get
   End Property

This property makes it possible to access the control's number from the host application. Therefore, the control associated with the number 102 can referenced in the object model of the host application later on.

Each control, now that it has a number, has a caption. Buttons have captions on them, whereas things like textboxes usually have a descriptive label to their left. These captions need to be specified and returned given the number assigned to the control. This is done by using the ControlCaptionFromID property.

   Public ReadOnly Property ControlCaptionFromID _
    (ByVal ControlID As Integer, ByVal ApplicationName As String, ByVal _
    LocaleID As Integer, ByVal Text As String, ByVal Xml As String, _
    ByVal Target As Object) As String Implements _
    Microsoft.Office.Interop.SmartTag.ISmartDocument.ControlCaptionFromID
      Get
         Select Case ControlID
            Case 1
               Return "Desc List"
            Case 2
               Return "Description"
            Case 3
               Return "Insert"
            Case 101
               Return "Captions"
            Case 102
               Return "Insert"
            Case 201
               Return "Category"
            Case 202
               Return "Sub Category"
            Case 203
               Return "Insert"
         End Select
      End Get
   End Property

This property receives, as a parameter, the value of the number assigned to control. Based on that number, an appropriate caption is returned.

Finally, there must be code that tells the object model what kind of control is supposed to be rendered when a type is in play. For example, Figure 6 shows two controls. They include a list box and a button. Up to this point, the code has merely specified that there are two controls associated with the ProductCaption type and it assigned them numbers—101 and 102. Now, the code must make it clear that 101 is the list box and 102 is the button. This is accomplished in the following manner using the ControlTypeFromID property:

   Public ReadOnly Property ControlTypeFromID _
    (ByVal ControlID As Integer, ByVal ApplicationName As String, _
    ByVal LocaleID As Integer) _
    As Microsoft.Office.Interop.SmartTag.C_TYPE Implements _
    Microsoft.Office.Interop.SmartTag.ISmartDocument.ControlTypeFromID
      Get
         Select Case ControlID
            Case 1
               Return C_TYPE.C_TYPE_COMBO
            Case 2
               Return C_TYPE.C_TYPE_TEXTBOX
            Case 3
               Return C_TYPE.C_TYPE_BUTTON
            Case 101
               Return C_TYPE.C_TYPE_LISTBOX
            Case 102
               Return C_TYPE.C_TYPE_BUTTON
            Case 201
               Return C_TYPE.C_TYPE_COMBO
            Case 202
               Return C_TYPE.C_TYPE_COMBO
            Case 203
               Return C_TYPE.C_TYPE_BUTTON
         End Select
      End Get
   End Property

Note   The types being referenced here are from the C_TYPE enumeration that contains constants for a variety of controls, a small sampling of which is used in the code for this article.

Populating and Using Controls

Once the smart document knows which types are to be used, how many controls are assigned to those types, and what kinds of controls are to be used, the code can populate the controls or set their initial properties, if necessary, and respond to user input.

Populating and Setting Initial Properties

There are a number of methods for populating controls such as PopulateCheckbox, PopulateRadioGroup, or PopulateListOrComboContent. The list is actually much longer, but you only need to use the methods that correspond to control types you have specified for use in the DLL through the ControlTypeFromID property. In our sample, the types include:

  • C_TYPE_Combo
  • C_TYPE_ListBox
  • C_TYPE_TextBox
  • C_TYPE_Button

The first two types in the list actually share a method because they behave in a similar fashion. The method used for populating them is the PopulateListOrComboContent method. Our smart document code specifies four different controls that use this method, three of which are combo boxes and one of which is a list box. This method receives a parameter, ControlID, whose value is the identifier for the control that is going to be populated. By adding conditional logic to the method, you can populate the controls differently based on the unique identifier.

   Public Sub PopulateListOrComboContent _
     (ByVal ControlID As Integer, ByVal ApplicationName As String, _
     ByVal LocaleID As Integer, ByVal Text As String, _
     ByVal Xml As String, ByVal Target As Object, ByVal Props As _
     Microsoft.Office.Interop.SmartTag.ISmartDocProperties, _
     ByRef List As System.Array, ByRef Count As Integer, ByRef _
     InitialSelected As Integer) Implements _
     Microsoft.Office.Interop.SmartTag.ISmartDocument. _
     PopulateListOrComboContent
      Select Case ControlID
         Case 1
            Count = 4
            List(1) = "Description 1"
            List(2) = "Description 2"
            List(3) = "Description 3"
            List(4) = "Description 4"
            InitialSelected = 1
         Case 101
            Count = 4
            List(1) = "Caption 1"
            List(2) = "Caption 2"
            List(3) = "Caption 3"
            List(4) = "Caption 4"
            InitialSelected = 1
         Case 201
            Count = 4
            List(1) = "1000"
            List(2) = "2000"
            List(3) = "3000"
            List(4) = "4000"
            InitialSelected = 1
         Case 202
            Count = 4
            List(1) = "AWC"
            List(2) = "DXF"
            List(3) = "GYI"
            List(3) = "JZL"
            InitialSelected = 1
      End Select
   End Sub

Here, the values are hard-coded for simplicity, but this need not be the case. You can access databases, XML files, file shares, Web services, or other data sources to specify the items in a list.

Three other parameters are being used in this code. The first is the Count parameter. Notice that it is passed by reference so that setting the value of the parameter in the method means that the calling code can access the same value. This value tells the smart document object model how many items are going to be in the list that it creates. The second parameter of interest is the List parameter, also passed by reference. This parameter is a System.Array that holds the list of items to be placed in the control. The smart document object model has 1-based lists to maintain consistency with other object models in Microsoft Office development. Therefore, even though the System.Array type is 0-based in .NET, irrespective of the language being used, you should not populate the first element in the array. After populating the list, the InitialSelected parameter, also passed by reference, is set to 1 so that the first item in the list is selected. The example code is designed for explanatory purposes.

To populate other controls our code uses the PopulateOther method. This method lets you set initial properties for things like buttons, labels, links, and separators. Our code uses the method to set the properties of three buttons.

   Public Sub PopulateOther(ByVal ControlID As Integer, _
    ByVal ApplicationName As String, ByVal LocaleID As Integer, _
    ByVal Text As String, ByVal Xml As String, ByVal Target _
    As Object, ByVal Props As _
    Microsoft.Office.Interop.SmartTag.ISmartDocProperties) Implements _
    Microsoft.Office.Interop.SmartTag.ISmartDocument.PopulateOther
      Select Case ControlID
         Case 3
            Text = "Insert Description"
         Case 102
            Text = "Insert Caption"
         Case 203
            Text = "Insert ProductID"
      End Select
   End Sub

Responding to User Interaction

The code for this sample has three buttons, one for each of the types defined in the schema. One is displayed with the other controls assigned to each of the types. As the user clicks on a button, values taken from the other controls are added to the document within the active XML node of the structure. To make this happen, a couple of methods are used. The first is the InvokeControl method.

   Public Sub InvokeControl(ByVal ControlID As Integer, _
    ByVal ApplicationName As String, ByVal Target As Object, _
    ByVal Text As String, ByVal Xml As String, ByVal _
    LocaleID As Integer) Implements _
    Microsoft.Office.Interop.SmartTag.ISmartDocument.InvokeControl
      Dim localRange As Word.Range
      Dim insertText As String
      localRange = CType(Target, Word.Range)
      Select Case ControlID
         Case 3
            insertText = localRange.XMLNodes(1). _
             SmartTag.SmartTagActions(2). _
             TextboxText
         Case 102
            insertText = ListBoxTextValue
         Case 203
            insertText = localRange.XMLNodes(1). _
             SmartTag.SmartTagActions(1). _
             TextboxText
            insertText = insertText & localRange.XMLNodes(1). _
             SmartTag.SmartTagActions(2). _
             TextboxText
      End Select
      localRange.XMLNodes(1).Text = insertText
   End Sub

This method receives the numeric identifier, ControlID, for the control (as do all of the methods for populating controls or responding to control events). This lets allows your code to know which control has received an event that caused this method to fire.

It also receives a parameter, Target, which is a reference to the range being accessed in the host application. Our sample converts this from an Object type to its true type:

      Dim localRange As Word.Range
      Dim insertText As String
      localRange = CType(Target, Word.Range)

Then, the code has different logic depending on which control was accessed. For example, if the clicked button was on the task pane when product description information was being shown, its number is 3, and the value of the second control on the pane (in this case a text box) is assigned to a local variable, insertText. The value of that variable is in turn inserted into the range in the document. This range is represented by the XML node in the XML structure and is accessed via the XMLNodes collection of the Range object in this way: localRange.XMLNodes(1).Text = insertText.

The way to gain access to the controls is by using the XMLNodes collection returned from the Range object in the host application. By accessing the first node, we can gain a reference to the SmartTag.SmartTagActions property that returns a list of the controls currently associated with the type that defines the target range. If the user clicked a button on the task pane when product description information was displayed (see Figure 6), the controls can be referenced to extract values and place them in the document. Because there are three controls associated with the PRODUCT_DESC type we have defined and the second one is a text box, then we can access the textual value of that control by the statement: localRange.XMLNodes(1).SmartTag.SmartTagActions(2).TextboxText.

Figure 6. Controls in the task pane for product description information

The value of the textbox is placed in a variable and then added to the document using the Range object.

If the clicked button was on the task pane when product ID information was shown (see Figure 7), the code retrieves two strings—one from the first combo box and one from the second combo box—concatenates them and assigns them to the insertText variable before they are placed in the document.

Figure 7. Controls in the task pane for product ID information

If the clicked button was on the task pane when product caption information (see Figure 6) was being shown, the value of a global variable, ListBoxTextValue, is assigned to the local insertText variable and subsequently placed in the document through the localRange object. The ListBoxTextValue is a global variable whose value gets set every time the selected item of the list box on the task pane changes. This list box only shows when the product caption information is showing, and there is no way to access the textual value of the selected value directly outside of the smart document DLL. Thus, as the selected item changes, its textual value is set in a global variable so it can be accessed later on. The code to set the global variable when the selected item changes as follows:

   Public Sub OnListOrComboSelectChange(ByVal ControlID As Integer, _
    ByVal Target As Object, ByVal Selected As Integer, _
    ByVal Value As String) Implements _
    Microsoft.Office.Interop.SmartTag. _
    ISmartDocument.OnListOrComboSelectChange
      Dim localRange As Word.Range
      Dim insertText As String
      localRange = CType(Target, Word.Range)
      Select Case ControlID
         Case 1
            localRange.XMLNodes(1). _
             SmartTag.SmartTagActions(2).TextboxText = Value
         Case 101
            ListBoxTextValue = Value
      End Select
   End Sub

This method, much like the previous one, lets you gain access to the target range back in the document, and the method first every time the selected item in a list box or combo box changes. Through the range, you can gain access to the controls that have been assigned to the type that currently defines this range. If the control is the list box displayed when the insertion point is in the node of the XML structure associated with the PRODUCT_CAPTION type, then the ID for the control is 101. Hence, the value of the list box, passed in as a parameter, Value, is assigned to the global variable. Another control also uses this method and it is the combo box assigned to the PRODUCT_DESC type. When the selected item in that combo box changes, its value is placed in the text box that displays on the task pane at the same time.

Deploying and Using the Solution

With the code complete, it is time to build and deploy the smart document DLL so it can be used with the host application. Building a project is easy. Go to the Build menu, and click on Build Solution. Make sure that you have specified a release version for your solution by clicking on Configuration Manager (also on the Build menu) and selecting Release for the Active Solution Configuration as shown in Figure 8.

Figure 8. Set your project for a final release version when you build it

This creates a managed DLL in the \BIN directory of your solution. You do not need to register the assembly using the RegAsm utility or take any other measure for interoperability with COM.

Creating an XML Expansion Pack Manifest File

The manifest file is an XML file with settings that determine how the smart document solution should be loaded and how it should behave. Here are the contents of the manifest file for our solution:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<manifest xmlns=
  "https://schemas.microsoft.com/office/xmlexpansionpacks/2003">
  <version>1.0</version>
  <updateFrequency>20160</updateFrequency>
  <uri>urn:schemas-microsoft-com.office11demos.WordProductsSD</uri>
  <solution>
    <solutionID>{BDE88D2F-FA67-4890-A674-A2BEE936A1A4}</solutionID>
    <type>smartDocument</type>
    <alias lcid="1033">Product Info</alias>
    <documentSpecific>False</documentSpecific>
    <targetApplication>Word.Application.11</targetApplication>
    <file>
      <type>solutionActionHandler</type>
      <version>1.0</version>
      <filePath>ProductInfo.dll</filePath>
      <CLSNAME>ProductInfo.ProductSmartDocument</CLSNAME>
      <managed/>
      <runFromServer>True</runFromServer>
    </file>
  </solution>
  <solution>
    <solutionID>schema</solutionID>
    <type>schema</type>
    <alias lcid="1033">Product Info</alias>
    <file>
      <type>schema</type>
      <version>1.0</version>
      <filePath>ProductInfo.xsd</filePath>
    </file>
  </solution>
</manifest>

All of the elements in this file are discussed in detail in the Smart Documents Beta 2 Development Overview article mentioned at the beginning of this document. You can give your manifest any name you wish as long as it conforms to the schema for smart document manifests and ends with the .XML extension. For clarity, you may wish to call the file manifest_Signed.xml and place it in the \BIN directory of your solution.

Looking more closely at the elements with settings particular to the current solution, the <alias> element contains a user-friendly name for the solution, in this case Product Info. As previously mentioned, the <uri> element contains the full namespace for the solution, and this namespace matches the namespace defined in the smart document DLL. The <file> element contains settings for file containing the smart document executable. In this instance, the manifest file is in the same directory as the DLL, so the name of the DLL suffices. In an unmanaged smart document solution, there is no <CLSNAME> element because it is exchanged for a <CLSID> element. The <CLSNAME> element is used only for managed solutions. It contains the fully qualified classname or Namespace.ClassName for the smart document DLL. The <managed/> element specifies that the solution is a managed one, and the <runFromServer> element has a Boolean value that determines if a copy of the smart document DLL should be made in the user's C:\Documents and Settings\ directory when the solution is loaded. By setting it to True, the DLL is run from its actual location rather than from a local directory.

This manifest file also contains a section that imports our target schema into the schema library of the host application when the smart document solution is loaded. Because the schema file, ProductInfo.xsd, is in the same directory (in this case, within the solution's \BIN directory) as the manifest file the name of the file suffices for the file path.

Configuring Security

A COM component loader called the Visual Studio Tools for Office loader is built into the new release of the Microsoft Office System. This loader handles the loading and security of managed smart document DLLs and managed smart tag DLLs. Managed smart document DLLs and smart tag DLLs loaded by the Visual Studio Tools for Office loader use its security checking mechanism, thus bypassing the normal security settings of the Microsoft Office System and being held to more stringent yet flexible security requirements. Ultimately, the security of the Visual Studio Tools for Office loader relies on.NET Framework security, and the managed DLL runs in the context of the common language runtime (runtime). As such, it is subject to the security policies and settings of the .NET Framework like any other .NET assembly.

**Note   **It is assumed that readers are familiar with the .NET Framework and how its security works. However, it is helpful to recall that the runtime inspects assemblies for evidence before any of the code is loaded or executed. Based on that evidence, the runtime assigns the code to a code group, and the code group has permissions associated with it. Those permissions determine what the code can do, when it can run, and how it can behave. For more information on the the "Visual Studio Tools for Office" loader and .NET security see the Smart Tag and Smart Document SDK.

In addition, the Office application running the smart document solution does not allow it to run unless the XML manifest is digitally signed. This process helps confirm the origin and authenticity of code. If unsigned, an XML expansion pack manifest file does not run in the host application, which means that none of the Smart Document solution files are loaded into memory regardless of where they are installed and what their intent is.

Because both .NET security and XML manifest security must be configured, we will look at both of these processes, starting with the .NET side of things. However, you should refer to the Smart Tag and Smart Document SDK for more detail on digital signatures and related topics.

To configure .NET security, you can use the Microsoft Management Console snap-in. To do so, on the Start menu, point to All Programs, point to Administrative Tools and then click Microsoft .NET Framework 1.1 Configuration.

Note   If you have both versions 1.0 and 1.1 of the Framework installed on the same computer, make sure that your solution and its security settings target the same version. In other words, if your solution uses the Framework version 1.1 but you configure the security in version 1.0, the smart document will not load properly.

In the console tree, expand the Runtime Security Policy node, and then expand the Machine node. We will define a new code group for our solution, and we will make the security depend on the URL or file path of the solution that attempts to load. To do this, expand the Code Groups node, expand All_Code, and then right-click My_Computer_Zone. Click New to create a code group. Follow these steps to create a new code group:

  1. In the Create Code Group wizard dialog box give the code group a name, in this case TrustProductInfo" and then click Next.

  2. In the Choose the condition type for this code group list, click URL. In the text box for the URL, specify the following string: file://C:/Projects/ProductInfo/* (see Figure 9) and then click Next.

    Figure 9. Setting the code group properties for your solution in .NET

    You should adjust this path if you placed your solution elsewhere. The asterisk tells .NET that any code that loads from anywhere below the specified directory structure is part of this code group.

  3. In the next window, you need to tell .NET what kind of permissions this code group should be given. In our example, we will grant full trust. Click Next and then Finish.

**Tip   **You can automate this process using a batch file to make it easier to add and remove your .NET security settings.

To digitally sign a manifest file for the XML expansion pack, you can use the XMLSign.exe utility available from the Smart Tag and Smart Document SDK. To sign the manifest, follow these steps:

  1. Start XMLSign.exe.

  2. Click the Browse button (. . . ) next to the XML File box (see Figure 10).

    Figure 10. The XMLSign.exe utility lets you sign your manifest file

  3. Locate and click the manifest file of the XML expansion pack you want signed.

  4. Click Open.

  5. Click Load XML.

  6. From the Stores list, select the certificate store that you want to use.

  7. From the Certificate list, select the certificate you want to use to verify the authenticity of your manifest when the host application attempts to load it.

  8. In the XML document tree view, right-click the manifest node, and click Append Signature to this node.

  9. Click the Sign XML button.

  10. In the File name dialog box, you will see a suggested name <original name>_signed.xml. Use this name.

  11. Click Save.

  12. Click OK.

A signed version of the manifest file, <original name>_signed.xml, is created in the same directory as the unsigned one, <original>.xml file you originally created.

Note   For more information about using Microsoft Authenticode® tools for signing, see the Digital Code Signing Step-by-Step Guide.

Adding the XML Expansion Pack

Once you configure the security properly and have a signed manifest file, you can run your solution. Before you do, make sure that no instances of Word are running. If you use Word as the e-mail editor for Microsoft Office Outlook® 2003, then you must also close Outlook. Then, start Word and create a blank document.

  1. On the Tools menu, click Templates and Add-Ins. The dialog box lets you add XML expansion packs and manage your schema library.
  2. Click XML Expansion Packs, and click the Add button.
  3. Browse to the directory that contains your manifest_Signed.xml file, select the file, and then click Open.
  4. Click XML Schema. You should see the Product Info schema listed there.
  5. Click OK to close the dialog box.

Your solution is now ready to use. The task pane should be visible. You must add elements to your document in order for the Document Actions pane to have any contents. Add elements by opening the XML Structure pane and clicking on one of the elements that appears. You can add the other elements at different locations in the document if you wish. When you have added desired elements, open the Document Actions pane and place the insertion point inside an XML element back in the document. The task pane shows the controls and behavior as the solution has prescribed.

Debugging Your Managed Smart Document DLL

Debugging a managed smart document DLL can be done in a variety of ways. This solution employs one of the most straightforward techniques. To set up debugging, make sure that you have specified a debug version for your solution by clicking Configuration Manager (also on the Build menu) and clicking Debug for the Active Solution Configuration. This creates a debug version of the managed DLL in the \BIN directory of your solution.

You must also set a debugging property for the project so that the host application loads before the smart document DLL loads. To set this property, right-click the project name in the Solution Explorer window and click Properties. In the Property Pages window, click Debugging. There are different actions that you can take when starting the solution. Click Start external program and click the button to the right of its text box. This brings up a dialog box that lets you go to the program that you want to start when the solution is started. Browse to the directory containing the Office executables and click WINWORD.EXE, and then click Open. The properties should look like those in Figure 11.

Figure 11. Setting properties for your project allows the host application to load when you debug the project code

Click OK to close the Property Pages window.

As before, make sure that all instances of Word are closed before attempting to run the solution. You should also place a breakpoint somewhere in your code so that you can see it work with the solution in debug mode. From the Debug menu of Visual Studio .NET click Start or just press F5 to start the debug version of the solution. When you run the project and instance of Word also starts. The rest of the procedures for using the solution are the same as when using the release version. When you close Word, the solution returns to design mode.

Conclusion

Creating smart document solutions in managed code lets you take advantage of the many benefits offered by the .NET Framework and Visual Studio .NET. You take advantage of a managed runtime with powerful security. The .NET runtime also provides some performance advantages, and the type safe attributes of the runtime can make your code more crash proof. Using .NET lets you create more intelligent exception handling routines, and you can code the solution more easily using the powerful language features offered in the Framework.

Visual Studio .NET 2003 lets you get your solution up and running faster by automatically adding all members from implemented interfaces. There are many other features of Visual Studio .NET 2003 that can improve your productivity. Smart document solutions are among the solutions you can create with this tool. Smart documents take a big step forward in making Office applications more responsive to user activity, in making it easier for users to work with data in documents, and in connecting them to external data sources. This "Hello World"-style sample elaborates on the fundamental steps in smart document development to bring these advantages to your own Microsoft Office solutions.

John R. Durant manages the MSDN Office Developer Center whose mission is to help developers use the latest and greatest technologies in Office and to provide documentation for previous versions. If he could, he would rent the space in this bio.