Export (0) Print
Expand All
Expand Minimize

Adding an ActiveX Control to a Smart Document

Office 2003
 

Frank C. Rice
John R. Durant
Microsoft Corporation

December 2003

Applies to:
    Microsoft® Office Word 2003
    Microsoft Office System

Summary: The Microsoft Office System features a new technology called smart documents for creating schema-based markup of data within Microsoft Office Word 2003 and Microsoft Office Excel 2003. In this article, after a brief overview, you will walk through the steps for creating, deploying, and using a straight-forward smart document sample with a Microsoft ActiveX control. (18 printed pages)

Download odc_Ofactivexsdsample.exe.

Download the odc_Ofactivexsdsample file. (142 KB)

Contents

Introduction
ActiveX Solution
Creating the Project
Adding Smart Document Controls
Implementing ISmartDocument
Populating and Using the Controls
Deploying and Using the Solution
Conclusion

Introduction

Smart documents are documents in Microsoft® Office Word 2003 or workbooks in Microsoft Office Excel 2003 with XML that are programmed to know what users need to do with them and give users help along the way. The document or workbook is smart enough to know where the cursor is and present user interface specific to that location. You can enhance this experience by integrating controls such as list boxes, check boxes, and Microsoft ActiveX® controls into the solution. In this article, we look at embedding an ActiveX control into a smart document and working with the methods, properties, and events of the control. This article demonstrates how to setup a Smart Document and illustrates the code used to add functionally to the document. This article does not explain advanced topics or present the whys of a particular step. This article assumes knowledge of Microsoft .NET development and deployment.

Note   Some code examples in this article contain extra paragraph marks to facilitate publishing. To use these examples, remove the extra paragraph marks.

ActiveX Solution

The ActiveX solution described in this article (see Figure 1) embeds the calendar control into the task pane of the smart document. Then, by clicking one of dates in the control, the date and current time are displayed in a message box. Next, two buttons in the task pane allow you to display the previous or next month, respectively. This article looks at the code for each of these processes.

Aa159685.odc_ofactivexsd01(en-us,office.11).gif

Figure 1. A view of the sample smart document

Creating the Project

This project requires Microsoft Visual Basic® 6.0 or a .NET-compliant language such as C or Visual Basic .NET to create the smart document solution. In the sample for this article, Visual Basic .NET is used in Microsoft Visual Studio® .NET 2003 although you could use another language and the earlier version of Visual Studio .NET.

To create the project:

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

    Aa159685.odc_ofactivexsd02(en-us,office.11).gif

    Figure 2. Using the New Project dialog box

  2. In the project, add a reference to the Microsoft Smart Tags 2.0 Type Library. To do so:
    • In Solution Explorer, right-click References and then click Add Reference.
    • Click the COM tab, and then double-click Microsoft Smart Tags 2.0 Type Library.

      -OR-

      Click Select to add it to the Selected Components list.

    • Click OK to close the dialog box.
  3. In addition, the setting a reference to the Microsoft Smart Tags 2.0 Type Library, you'll also want to set references to the following on the COM tab:
    • Microsoft Calendar Control 11.0
    • Microsoft Office 11.0 Object Library
    • 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 with "CalendarSD.vb" and the name of the class to CalendarSmartDocument. In the Solution Explorer window, your project structure should look like Figure 3.

Aa159685.odc_ofactivexsd03(en-us,office.11).gif

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 CalendarSD.vb file should look like this:

Imports Microsoft.Office.Interop.SmartTag
Imports Word = Microsoft.Office.Interop.Word
Imports System.Windows.Forms
Public Class CalendarSmartDocument
  'Implementation code goes here
End Class

Before continuing with the code, we should provide a little more explanation of the purpose of the smart document code.

As stated before, the purpose of a smart document is to place controls in the task pane that respond to the activity of the user. Thus, the two mains jobs of the code is to place controls in the task pane based on the data element specified and to determine what those controls are to do. For example Figure 4, shows how the task pane differs depending on which node in the document the insertion point is placed.

Aa159685.odc_ofactivexsd04(en-us,office.11).gif

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

If the user clicks one of the date buttons in the calendar control in the task pane, the code responds to this event. In this case, the code displays the selected date and time in a message box, yielding a result like that shown in Figure 5.

Aa159685.odc_ofactivexsd05(en-us,office.11).gif

Figure 5. Message box displayed when date button is clicked

Adding Smart Document Controls

We 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.WordCalendarSD"
    targetNamespace="urn:schemas-microsoft-com.office11demos.WordCalendarSD"
    elementFormDefault="qualified"
    attributeFormDefault="unqualified" 
    id="CalendarCtrl">
  <xsd:element name="activex">
   <xsd:complexType>
            <xsd:simpleContent>
            <xsd:extension base="xsd:string">   
                  <xsd:attribute name="prop1" type="xsd:string" default="200"/>
                  <xsd:attribute name="prop2" type="xsd:string" default="250"/>
            </xsd:extension>
            </xsd:simpleContent>
     </xsd:complexType>
  </xsd:element>
</xsd:schema>

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

urn:schemas-microsoft-com.office11demos.WordCalendarSD

This namespace is one we have arbitrarily defined, and it, along with the element names, is 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.

When the smart document solution is attached to a document or workbook, or a document workbook with an attached smart document solution is opened the SmartDocInitialize method is called. In this method, the document type is converted from a generic document type to a Word document type.

Note   The following example and subsequent examples include line breaks to allow this code example to display correctly. To use, remove all line breaks.
Public Sub SmartDocInitialize(ByVal ApplicationName As String, ByVal Document As Object, ByVal
 SolutionPath As String, ByVal SolutionRegKeyRoot As String) 
Implements Microsoft.Office.Interop.SmartTag.ISmartDocument.SmartDocInitialize
   thisDocument = CType(Document, Word.Document)
End Sub

Implementing ISmartDocument

The first thing to do in the code is implement all of the interfaces for the ISmartDocument interface. You can do this 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.

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 2003 requires that you explicitly select each member and add its implementation.

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

    Public Const BASEURI As String = "urn:schemas-microsoft-com.office11demos.WordCalendarSD"
    Public Const ACTIVEX As String = BASEURI & "activex"
    Public Const TYPESCOUNT As Integer = 1
    Private WithEvents axCal As MSACAL.Calendar
    Private thisDocument As Word.Document
    Private xmlNode As Word.XMLNode

The first constant, BASEURI is the same as the one defined in the schema shown earlier. This is important because it is what links the data activity in the host application with the logic behind the scenes in your smart document DLL. The next constant is subsequently defined by concatenating the BASEURI constant with a unique string match the element defined in the schema. The TYPESCOUNT constant is used later on to indicate to the smart document object model how many types it needs to worry about. The final variables are 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, the element has a control assigned to it, 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 is one element, it accesses the next property, SmartDocXmlTypeName, one time.

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 one time (because the SmartDocXmlTypeCount specified that there is a single type in play). If there were multiple types defined, the first time it was called, 1 would be the value of the parameter. The second time 2 would be the value, and so forth.

Public ReadOnly Property SmartDocXmlTypeName(ByVal XMLTypeID As Integer) As String    Implements
 Microsoft.Office.Interop.SmartTag.ISmartDocument.SmartDocXmlTypeName
      Get
         Select Case XMLTypeID
            Case 1
               Return ACTIVEX
         End Select
         Exit Property
      End Get
End Property

Notice for the number, a string containing the actual element or type name is returned. Had there been multiple types, the number and type assignment is arbitrary. So instead of ACTIVEX returning the number 1, it could just as easily return the number 2. 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.

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 "ActiveX Control"
      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 ACTIVEX 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 ACTIVEX
            Return 3
         Case Else
            Return 0
      End Select
   End Get
End Property

For example, Figure 4 shows three controls in the task pane for the ActiveX element. The type in use is urn:schemas-microsoft-com.office11demos.WordCalendarSDActiveX. Therefore, this property specifies that for this type three 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 used up to this point will not do. They are just 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's take a moment to fully explain its use.

The reason you need to specify your own user-defined names is because 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, textboxes, or listboxes, and so forth) for the first type would be 1, 2, and 3. If the second type also had three controls assigned to it, their 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 let's you 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 ACTIVEX
            Return ControlIndex
         Case Else
            Return 0
      End Select
   End Get
End Property

In this code, the type here simply uses the default starting point of 1, the value of the ControlIndex parameter. If we had defined a second type, we could use the starting point of 1 and add 100. A third type would then use the same starting point but add 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 ActiveX type, its number is 2. 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 2 can referenced in the object model of the host application later on.

Each control, now that it has a number, includes 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 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 "{8E27C92B-1264-101C-8A2F-040224009C02}"
         Case 2
            Return "Next Month"
         Case 3
            Return "Last Month"
      End Select
   End Get
End Property

For ActiveX controls, the ControlCaptionFromID property provides the global unique identifier (GUID) for the ActiveX control. In this case, you add the GUID for the calendar control.

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 4 shows three controls. They include a calendar control and two buttons. Up to this point, the code has merely specified that there are three controls associated with the ActiveX type and it has assigned them numbers: 1, 2 and 3. Now, the code must make clear that 1 is the calendar control, 2 is the first button and 3 is the other 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_ACTIVEX
         Case 2
            Return C_TYPE.C_TYPE_BUTTON
         Case 3
            Return C_TYPE.C_TYPE_BUTTON
      End Select
   End Get
End Property

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 the 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 to be used, the code can populate the controls or set 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 PopulateCheckbox, PopulateRadioGroup, or PopulateListOrComboContent. The list is actually much longer, but only need to use the methods that correspond to control types you have specified for use in the DLL via the ControlTypeFromID property. In our sample, the types include:

  • C_TYPE_ActiveX
  • C_TYPE_Button

The first type in the list uses the PopulateActiveXProps method to initialize the controls. 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 PopulateActiveXProps(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, ByVal ActiveXPropBag As 
Microsoft.Office.Interop.SmartTag.ISmartDocProperties) Implements 
Microsoft.Office.Interop.SmartTag.ISmartDocument.PopulateActiveXProps
   Select Case ControlID
      Case 1
         Props.Write(Key:="W", value:="250")
         Props.Write(Key:="H", value:="200")
   End Select
End Sub

To populate the other controls our code uses the PopulateOther method. This method lets you set initial properties for things like buttons, labels, links, and separators that do not already have another specific method. Our code uses the method to set the properties of the two 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 2
         Text = "Next Month"
      Case 3
         Text = "Last Month"
   End Select
End Sub

The OnPaneUpdateComplete method is called when the Document Actions task pane has been filled with controls and all of the controls in the task pane have been initialized.

Public Sub OnPaneUpdateComplete(ByVal Document As Object) Implements 
Microsoft.Office.Interop.SmartTag.ISmartDocument.OnPaneUpdateComplete
        axCal = thisDocument.XMLNodes.Item(1).SmartTag.SmartTagActions.Item(1).ActiveXControl()
End Sub

The line of code in this method provides access to the ActiveX control in the Document Actions task pane.

Responding To User Interaction

The code for this sample has two buttons and an ActiveX control, one for each of the types defined in the schema. As the user clicks on a button, the next or previous month is displayed in the calendar control. 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
   Select Case ControlID
      Case 2
         axCal.NextMonth()
      Case 3
         axCal.PreviousMonth()
   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 lets allows your code to know which control has received an event that caused this method to fire. Then, depending on the value of the numeric identifier, either the NextMonth or PreviousMonth methods are called.

The other functionality that exists in our sample comes when a specific date is clicked in the calendar control. In this case, a message box is displayed with the date clicked and the time. The code to accomplish this is as follows:

Private Sub axCal_Click() Handles axCal.Click
   MessageBox.Show(axCal.Value.ToString())
End Sub

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, just 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 6.

Aa159685.odc_ofactivexsd06(en-us,office.11).gif

Figure 6. 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. This is done automatically based on settings in your solution's XML expansion pack manifest file.

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="http://schemas.microsoft.com/office/xmlexpansionpacks/2003">
  <version>1.0</version>
  <updateFrequency>20160</updateFrequency>
  <uri>urn:schemas-microsoft-com.office11demos.WordCalendarSD</uri>
  <solution>
    <solutionID>{1A7C74CF-B72E-4d24-A133-AEC5F74FF4F7}</solutionID>
    <type>smartDocument</type>
    <alias lcid="1033">Calendar Ctrl</alias>
    <documentSpecific>False</documentSpecific>
    <targetApplication>Word.Application.11</targetApplication>
    <file>
      <type>solutionActionHandler</type>
      <version>1.0</version>
      <filePath>CalendarCtrl.dll</filePath>
      <CLSNAME>CalendarCtrl.CalendarSmartDocument</CLSNAME>
      <managed/>
      <runFromServer>True</runFromServer>
    </file>
  </solution>
  <solution>
    <solutionID>schema</solutionID>
    <type>schema</type>
    <alias lcid="1033">Calendar Ctrl</alias>
    <file>
      <type>schema</type>
      <version>1.0</version>
      <filePath>CalendarCtrl.xsd</filePath>
    </file>
  </solution>
</manifest>

All of the elements in this file are discussed in greater detail in the Smart Documents Development Overview article. Here, we focus on the elements with settings particular to the current solution. The <alias> element contains a user-friendly name for the solution, in this case "Calendar Ctrl." 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 just 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 only used for managed solutions and contains the progID 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, CalendarCtrl.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

Once you have the .NET security properly configured, you need to run your solution. Before you do, make sure that no instances of Microsoft Word are running. If you use Microsoft Word as the email editor for Microsoft Outlook, then you must also close Outlook. Then, start Microsoft Word and create a new, blank document. From the Tools menu choose Templates and Add-Ins. The dialog box lets you add XML expansion packs and manage your schema library. Click the XML Expansion Packs tab, and click the Add button. Navigate to the directory that contains your manifest.xml file. Select the file and click Open. Click on the XML Schema tab, and you should see the Calendar Ctrl schema listed there. 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 in order for the Document Actions pane to have any contents. Add elements by causing the XML Structure pane to appear and click on one of the elements that appears (see Figure 1). You can add the other elements at different locations in the document if you wish. When you have added desired elements, cause the Document Actions pane to appear, 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.

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. These include a managed runtime with more powerful security and the type safe attributes of the runtime that can make your code more crash-proof. The ability to add ActiveX controls to a smart document makes the document even more versatile. Smart documents take a big step forward in making Office applications more responsive to user actions.

Show:
© 2014 Microsoft