Simple Sample of a Smart Document in Microsoft Office Word 2003

Summary: Smart documents offer a way for developers to add context to the data with which a user interacts. In this article, learn how one smart document sample was created. (15 printed pages)

Important

The information set out in this topic is presented exclusively for the benefit and use of individuals and organizations outside the United States and its territories or whose products were distributed by Microsoft before January 2010, when Microsoft removed an implementation of particular functionality related to custom XML from Word. This information may not be read or used by individuals or organizations in the United States or its territories whose products were licensed by Microsoft after January 10, 2010; those products will not behave the same as products licensed before that date or licenses for use outside the United States.

Frank Rice, Microsoft Corporation

January 2004

Applies to: Microsoft Office Word 2003

Download odc_WdSimpleSDSample.exe.

Contents

  • What Are Smart Documents?

  • Reviewing a Smart Document Solution

  • Steps to Develop a Smart Document

  • The Smart Document Sample

  • Adding Smart Document Controls

  • Implementing the ISmartDocument Interface

  • Populating and Using Controls

  • Building and Using the Solution

  • Conclusion

What Are Smart Documents?

Smart documents build on the concept of smart tags introduced in Microsoft Office XP, and extend it using a document-based metaphor aimed at simplifying and enhancing the user experience of working with documents. With smart document technology, developers build upon rich XML-based documents in Microsoft Office Word 2003 and Microsoft Office Excel 2003 to create solutions that can be deployed and subsequently updated from a server, once the initial document or template is opened on the client, making distribution a non-issue. Users benefit from a smart document's ability to deliver relevant information and actions using an intuitive task pane that synchronizes content based on the user's current location within the document. For more information about Smart Documents, see Microsoft Office Developer Center: Smart Documents Developer Portal.

Reviewing a Smart Document Solution

A smart document solution is composed of the following set of files:

  • Smart document. Word document or Excel spreadsheet that implements the solution.

  • Schema file. XML schema file that describes the solution document.

  • Action handler. A dynamic-link library (DLL) that implements the smart document programmatic object model or application programming interface (API).

  • Document Actions task pane. A task pane that displays smart document actions and help content.

  • XML expansion pack manifest file. An XML file that that specifies the identity and location of component files that are necessary to fully enable a specific smart document solution.

  • Control. A user interface (UI) component displayed in the Document Actions task pane. Examples include command buttons, hyperlinks, and text boxes.

Steps to Develop a Smart Document

Creating a smart document solution can be simple or it can contain thousands of code lines and reach far into a company's enterprise. You cannot write smart document solutions in Microsoft Visual Basic for Applications (VBA), but you can write them using Microsoft Visual Basic 6.0, Microsoft Visual Basic .NET, Microsoft C# .NET, or Microsoft Visual C++ . You can create a smart document solution as a DLL or as an XML file using an XML editor such as Microsoft Notepad or Word 2003. Regardless of which approach you take, the development cycle is straightforward:

  1. Create an XML schema that describes the document.

  2. Attach the XML schema to a document.

  3. Associate XML elements with portions of the document.

  4. Use the smart document API or the smart document XML file schema to write code that implements ISmartDocument or build smart doc XML that does the following:

    • Displays content in the Document Actions task pane

    • Takes action when the user interacts with the controls

  5. Create an XML expansion pack manifest file.

  6. Attach the solution to the document.

  7. Save the document solution on a server where users can access it.

  8. Distribute the document as a template either by e-mail, Microsoft SharePoint Products and Technologies, and so on.

When a user creates an instance of the document from the template, the Microsoft Office System downloads and registers the smart document and any supporting files used by the smart document locally on the user's computer without user intervention.

The Smart Document Sample

To demonstrate the techniques and code that comprise a smart document, the sample is available as a download. The sample described here was created using Microsoft Visual Basic 6.0.

Important

For the code in this article to work properly, you must set the security level for this application to Low. The code in this article is meant for demonstration purposes only. Microsoft highly recommends that you maintain the security level for your applications at High during normal use.

The sample demonstrates ways that you can add controls in the Document Actions task pane that interact with the content in the document body. For example, when the user clicks into the top line of text in the document, the task pane presents help text and a text box that prompts the user to type a name (see Figure 1). After the user clicks out of the text box, a message box is displayed with a greeting.

Figure 1. The Smart Document sample

The Smart Document sample

Adding Smart Document Controls

This first example demonstrates how to set up controls and associate them with nodes in a schema:

<?xml version="1.0" encoding="UTF-8" ?> 
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
      xmlns="mySmartDoc"
      targetNamespace="mySmartDoc"
      elementFormDefault="qualified">

<xsd:complexType name="exampleType">
   <xsd:all>
      <xsd:element name="top" type="xsd:string"/>
      <xsd:element name="middle" type="xsd:string"/>
      <xsd:element name="bottom" type="xsd:string"/>
   </xsd:all>
   <xsd:attribute name="attributeChoice">
      <xsd:simpleType>
         <xsd:restriction base="xsd:string">
            <xsd:enumeration value="value1"/>
            <xsd:enumeration value="value2"/>
         </xsd:restriction>
      </xsd:simpleType>
   </xsd:attribute>
   <xsd:attribute name="attributeString1" type="xsd:string"/>
   <xsd:attribute name="attributeString2" type="xsd:string"/>
   <xsd:attribute name="date" type="xsd:string"/>
</xsd:complexType>
<xsd:element name="allDocument" type="exampleType"/>
</xsd:schema>

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

xmlns:xsd=http://www.w3.org/2001/XMLSchema

This namespace is arbitrarily defined for use, along with the element names, in the code of the smart document DLL. You do not actually embed the schema file or explicitly refer to it in the DLL, although its structure informs the design of the code. In fact, you define these element types with the smart document DLL code. These elements are the key link between the data in the document and the actions of the smart document.

Implementing the ISmartDocument Interface

First, you must implement all of the interfaces for the ISmartDocument interface. To do so, type Implements ISmartDocument within the class definition. Make sure that you implement all of the members for that interface, even if the implemented interface is not used.

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

Const cNAMESPACE As String = "mySmartDoc"
Const cAllDoc As String = cNAMESPACE & "#allDocument"  
' will be numbered 1
Const cTop As String = cNAMESPACE & "#top"             
' will be numbered 2
Const cMiddle As String = cNAMESPACE & "#middle"       
' will be numbered 3
Const cBottom As String = cNAMESPACE & "#bottom"       
' will be numbered 4
Const cTYPES As Integer = 4

The first value, mySmartDoc is the same as the one defined in the schema shown in the previous example. This is important because it links the data activity in the host application with the logic in your smart document DLL. The four constants are subsequently defined by concatenating the cNAMESPACE constant and represent mySmartDoc, with a unique string. These constants match the four elements defined in the schema. The cTYPES constant indicates to the smart document object model how many types are present. The final constant is one you use to respond to activities with a control and is explained later in this article.

The smart document object model uses the properties to configure the solution in a specific order. The smart document object model accesses the SmartDocXMLTypeCount property first. This property determines how many of the elements in the namespace are associated with controls. In our example, we assign all of the elements to controls, but this may not always be so.

Private Property Get ISmartDocument_SmartDocXMLTypeCount() As Long
    ISmartDocument_SmartDocXMLTypeCount = cTYPES
End Property

In this example, the property merely returns the value of the previously defined constant, cTYPES. Because the code indicates four elements, it accesses the next property, SmartDocXmlTypeName, four times.

The SmartDocXMLTypeName property has a parameter, smartDocID, 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 object model calls the property four times because the SmartDocXmlTypeCount property specified four elements. The first time it calls the smartDocID parameter, the value is 1. The second time, the value is 2, and so on:

Private Property Get ISmartDocument_SmartDocXMLTypeName(ByVal smartDocID As Long) As String
    Select Case smartDocID
        Case 1
            ISmartDocument_SmartDocXMLTypeName = cAllDoc
        Case 2
            ISmartDocument_SmartDocXMLTypeName = cTop
        Case 3
            ISmartDocument_SmartDocXMLTypeName = cMiddle
        Case 4
            ISmartDocument_SmartDocXMLTypeName = cBottom
        Case Else
    End Select
End Property

Notice how for each number, the example returns a string containing the actual element or type name. There is no reason why the cAllDoc value 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. The code simply must indicate the type to associate with the internal tracking number, the integer it assigns.

Next, the code accesses the SmartDocXmlTypeCaption property. This property also has a parameter whose value is the integer the smart document object model assigned to a type. In our example, because the code indicates four elements, it accesses this property, four times.

Note

The following example and subsequent examples include line breaks to allow this code example to display correctly. To use, remove extra paragraph marks.

Private Property Get ISmartDocument_SmartDocXMLTypeCaption(ByVal smartDocID As Long, ByVal LocaleID 
As Long) As String
    Select Case smartDocID
        Case 1
            ISmartDocument_SmartDocXMLTypeCaption = "Smart Document Sample 4 U"
        Case 2
            ISmartDocument_SmartDocXMLTypeCaption = "Page header"
        Case 3
            ISmartDocument_SmartDocXMLTypeCaption = "The Document's body"
        Case 4
            ISmartDocument_SmartDocXMLTypeCaption = "Page footer"
        Case Else
    End Select
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 SmartDocXMLTypeName property. Because the number 1 is associated with the cAllDoc constant in that property, it makes sense to return a string that relates to a product description for the caption. Once the order of items 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 to use when you use the target type.

Private Property Get ISmartDocument_ControlCount(ByVal SmartDocName As String) As Long
    Select Case SmartDocName
        Case cAllDoc
            ISmartDocument_ControlCount = 1
        Case cTop
            ISmartDocument_ControlCount = 1
        Case cMiddle
            ISmartDocument_ControlCount = 1
        Case cBottom
            ISmartDocument_ControlCount = 2
        Case Else
    End Select
End Property

For example, Figure 1 shows one control in the task pane for the top element. The type in use is mySmartDoc#top. Therefore, this property specifies to use one control for this type.

Each control used in the task pane uses a unique identifier for access by the host application. The unique integers used previously are not sufficient because they are just integers used by the smart document object model to track the different control types internally. You can use the ControlID property to assign unique numbers to the controls in the task pane. This may be difficult to grasp when working with smart documents, so let's take a moment to fully explain the use of the ControlID property.

The reason you need to specify unique names is because when you call this property and pass a type name, by default the object model resets the number for the controls back to 1 for each type. For example, the numbers for the three controls (buttons, text boxes, or list boxes, and so forth) for the first type are 1, 2, and 3. If you also associate the second type with three controls, their numbers are also 1, 2, and 3 because the beginning point resets for each type. Adding conditional logic to the ControlID property allows you to control the number scheme.

Private Property Get ISmartDocument_ControlID(ByVal SmartDocName As String, ByVal ControlIndex As Long) As Long
    Select Case SmartDocName
        Case cAllDoc
            ISmartDocument_ControlID = ControlIndex + 100
        Case cTop
            ISmartDocument_ControlID = ControlIndex + 200
        Case cMiddle
            ISmartDocument_ControlID = ControlIndex + 300
        Case cBottom
            ISmartDocument_ControlID = ControlIndex + 400
    End Select
End Sub

In this example, the first type simply uses the default starting point of 1, the value of the SmartDocName 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 you to assign each type up to 100 controls, much more than you ever practically use.

Next, the code accesses the ControlNameFromID property. This property receives the number you assigns to the control as a parameter. So, if the control is the first control of the cTop type, its number is 101. This property adds that number to the namespace of the schema and returns it:

Private Property Get ISmartDocument_ControlNameFromID(ByVal ControlID As Long) As String
    ISmartDocument_ControlNameFromID = cNAMESPACE & ControlID
End Property

This property allows you to access the control's number from within the host application. Therefore, you can add a reference to the control associated with the number 102 directly from code in the host application.

Each control, now that it has a number, includes a caption. Buttons include captions; text boxes and similar controls usually have descriptive labels. You must specify and return the value of these captions according to the number assigned to the control. To do so, use the ControlCaptionFromID property:

Private Property Get ISmartDocument_ControlCaptionFromID(ByVal ControlID As Long, ByVal ApplicationName
 As String, ByVal LocaleID As Long, ByVal Text As String, ByVal Xml As String, ByVal Target As Object) As String
   Select Case ControlID
       Case 101 ' Help
           ISmartDocument_ControlCaptionFromID = _
               "Help text applies to all elements."
       Case 201 ' Text
           ISmartDocument_ControlCaptionFromID = _
               "Please type your name:"
       Case 301 ' Button
           ISmartDocument_ControlCaptionFromID = _
               "Click me"
       Case 401 ' Radio
           ISmartDocument_ControlCaptionFromID = _
               "Pick your favorite color"
       Case 402 ' Image
           ISmartDocument_ControlCaptionFromID = _
               "Click image to insert into document."
       Case Else
   End Select
End Property

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

Finally, you need code that indicates the type of control to render. For example, Figure 2 shows two controls. They include a radio button and an image.

Figure 2. Bottom option of the SmartDocSample

Bottom option of the SmartDocSample

Up to this point, the code associates two controls with the bottom type and assigns them the following values: 401 and 402. Now, the code indicates that 401 is the radio button and 402 is the image. This is accomplished using the ControlTypeFromID property:

Private Property Get ISmartDocument_ControlTypeFromID(ByVal ControlID As Long, ByVal ApplicationName
 As String, ByVal LocaleID As Long) As SmartTagLib.C_TYPE
    Select Case ControlID
        Case 101
            ISmartDocument_ControlTypeFromID = C_TYPE_HELP
        Case 201
            ISmartDocument_ControlTypeFromID = C_TYPE_TEXTBOX
        Case 301
            ISmartDocument_ControlTypeFromID = C_TYPE_BUTTON
        Case 401
            ISmartDocument_ControlTypeFromID = C_TYPE_RADIOGROUP
        Case 402
            ISmartDocument_ControlTypeFromID = C_TYPE_IMAGE
        Case Else
    End Select
End Property

The example refers to types from the C_TYPE enumeration. This enumeration 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 you indicate the types of controls to use, assign the number of controls associated with those types and what kinds of controls to use, the code can populate the controls or specify their initial properties, if necessary, and respond to user-interaction.

Populating and Setting Initial Properties

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

  • C_TYPE_HELP

  • C_TYPE_TEXTBOX

  • C_TYPE_BUTTON

  • C_TYPE_RADIOGROUP

  • C_TYPE_IMAGE

The method to use to populate the Help is the PopulateHelpContent method. This method receives the ControlID parameter whose value is the identifier for the control you want to populate.

Private Sub ISmartDocument_PopulateHelpContent(ByVal ControlID As Long, ByVal ApplicationName As String,
 ByVal LocaleID As Long, ByVal Text As String, ByVal Xml As String, ByVal Target As Object, ByVal Props As
 SmartTagLib.ISmartDocProperties, Content As String)
        
    Select Case ControlID
        Case 101
            Content = "<html><body><p>This is the SimpleSample " & _
                "smart document.</p></body></html>"
    End Select
End Sub

In this example, the value is hard-coded for simplicity, but you can also choose to access databases, XML files, file shares, Web services or other data sources to specify the items in a list.

To populate other controls the example uses the PopulateRadioGroup method. This method lets you specify initial properties for radio buttons.

Private Sub ISmartDocument_PopulateRadioGroup(ByVal ControlID As Long, ByVal ApplicationName As String,
 ByVal LocaleID As Long, ByVal Text As String, ByVal Xml As String, ByVal Target As Object, ByVal Props As
 SmartTagLib.ISmartDocProperties, List() As String, Count As Long, InitialSelected As Long)
        
    Select Case ControlID
        Case 401
            ReDim List(1 To 5) As String
            Count = 5
            List(1) = "Red"
            List(2) = "Blue"
            List(3) = "Yellow"
            List(4) = "Purple"
            List(5) = "Green"
            InitialSelected = -1
    End Select
End Sub

This code uses three other parameters. The first is the Count parameter. Notice that the code passes it 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 indicates to the object model how many items to include 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 member that holds the list of items to place in the control. The smart document object model uses 1-based lists so as to maintain consistency with other object models in Microsoft Office development. Therefore, even though the System.Array type is 0-based in the .NET Framework, regardless of the language you use, you should not populate the first element in the array. After populating the list, the code also passes the InitialSelected parameter by reference.

Responding to User Interaction

The code for this sample includes five buttons, one for each of the control types defined in the schema. One, the Help button, is displayed with the other controls assigned to each of the control types. For other controls such as the button, values taken from the other control are displayed in a message box. To make this happen, a couple of methods are used. The first is the InvokeControl method.

Private Sub ISmartDocument_InvokeControl(ByVal ControlID As Long, ByVal ApplicationName As String, ByVal
 Target As Object, ByVal Text As String, ByVal Xml As String, ByVal LocaleID As Long)
    
    Select Case ControlID

        Case 301
            MsgBox "You clicked me"
    End Select
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 indicates which control receives an event that caused this method to fire.

Private Sub ISmartDocument_OnRadioGroupSelectChange(ByVal ControlID As Long, ByVal Target As Object, ByVal
 Selected As Long, ByVal value As String)
        
    Dim objRange As Word.Range
    
    Select Case ControlID
        Case 401
            Set objRange = Target.XMLNodes(1).Range
            objRange.Text = "My favorite color is " & value & "."
            Set objRange = Nothing
    End Select
End Sub

Similar to the previous example, this method also takes the ControlID parameter. It also receives a parameter, Target, which is a reference to the range accessed in the host application. 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 control(s) currently associated with the element type that defines the target range. If the user clicks a button on the task pane when the bottom information is displayed (see Figure 3), you can add a reference to the control to extract values and place them in the document. We access the textual value of that control by using the statement: objRange.Text = "My favorite color is " & value & ".". The statement places the value in a variable and then adds it to the document using the Range object.

Figure 3. Option that embeds data in the smart document

Option that embeds data in the smart document

The next example shows how the OnTextboxContentChange method also receives the ControlID parameter. This method displays a message box with the contents of the text box, assuming that the text box contains data.

Private Sub ISmartDocument_OnTextboxContentChange(ByVal ControlID As Long, ByVal Target As Object, ByVal
 value As String)
    
    Select Case ControlID
        Case 201
            If Len(value) > 0 Then
                MsgBox "Hello, " & value
            End If
    End Select
End Sub

The next example uses the Target parameter to gain access to the target range back in the document, and you access the method every time you click the image. Through the range, you can gain access to the control that is assigned to the element type that currently defines this range.

Private Sub ISmartDocument_ImageClick(ByVal ControlID As Long, ByVal ApplicationName As String,
 ByVal Target As Object, ByVal Text As String, ByVal Xml As String, ByVal LocaleID As Long, ByVal XCoordinate
 As Long, ByVal YCoordinate As Long)
    Dim objRange As Word.Range
    Dim strImage As String
    Static strText As String
    
    Select Case ControlID
        Case 402
            Set objRange = Target.XMLNodes(1).Range
            strImage = strPath & "simplesample.bmp"
            objRange.Select
            Selection.InlineShapes.AddPicture strImage
            strText = ""
    End Select
End Sub

Building and Using the Solution

With the code complete, it is time to save and run the smart document DLL so you can use it with the host application. First, save the project in Microsoft Visual Basic 6.0. To do so, on the File menu, click Save Project and type a name for the project such as mySD. Save the project to the same location as the manifest.xml file you will create in the next section. Running the project is easy; just go to the Run menu and click Start. As long as no errors surface, the code is ready.

Creating an XML Expansion Pack Manifest File

The manifest file is an XML file with settings that determine how the smart document solution should load and how it should act. 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.1</version>
   <updateFrequency>20160</updateFrequency>
   <uri>mySmartDoc</uri>
   <solution>
      <solutionID>{MySD}</solutionID>
      <type>smartDocument</type>
      <alias>A Smart Document Sample</alias>
      <file>
         <type>solutionActionHandler</type>
         <version>1.0</version>
         <filePath>mySD.dll</filePath>
         <CLSID>{2D802F0E-BE75-42BD-BA4E-00CEB42F96F3}</CLSID>
         <regsvr32>True</regsvr32>
         <runFromServer>True</runFromServer>
      </file>
      </solution>
   <solution>
      <solutionID>schema</solutionID>
      <type>schema</type>
      <alias>A Smart Document Sample</alias>
      <runFromServer>True</runFromServer>
      <file>
         <type>schema</type>
         <version>1.0</version>
         <filePath>SmartDocSample.xsd</filePath>
         <runFromServer>True</runFromServer>
      </file>
   </solution>
</manifest>

All of the elements in this file are discussed in greater detail in the Smart Documents Development Overview. This article focuses on the elements with settings particular to the current solution. The <alias> element contains a user-friendly name for the solution, in this case "A Smart Document Sample." 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 example, the manifest file is in the same directory as the DLL, so the name of the DLL suffices. The <CLSID> element contains the progID for the smart document DLL. The manifest file also contains a section that imports our target schema into the schema library when the smart document solution is installed. Because the schema file, SmartDocSample.xsd, is in the same directory as the manifest file the name of the file suffices for the file path.

Adding the XML Expansion Pack

Now you need to 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.

To add the XML Expansion Pack

  1. Start Word and create a document.

  2. On the Tools menu, click Templates and Add-Ins. Use this dialog box to add XML expansion packs and manage your schema library.

  3. Click the XML Expansion Packs tab, and click Add.

  4. Navigate to the directory that contains your manifest.xml file. Select the file and click Open.

  5. Click the XML Schema tab, and you should see A Smart Document Sample schema listed there.

  6. 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 for the Document Actions pane to have any contents. Add elements by causing the XML Structure pane to appear and click one of the elements that appears (see Figure 4). You can also add elements at different locations in the document. Once you add desired elements, open the Document Actions pane and click an XML element in the document. The task pane shows the controls and behavior as outlined in the solution.

Figure 4. The Document Actions task pane

The Document Actions task pane

Conclusion

Smart documents take a big step forward in making Office applications more responsive to user activity by making it easier for users to work with data in documents and by connecting documents to external data sources. This sample smart document elaborates on the fundamental steps in smart document development to bring these advantages to your own customized Microsoft Office solutions.