Creating Document-Level Smart Tags with VSTO


The simplest way to create a Smart Tag is to use the support in Visual Studio 2005 Tools for Office (VSTO) for document-level Smart Tags. VSTO provides some classes that enable you to create a Smart Tag easily. First, VSTO provides a class called SmartTag in the Microsoft.Office.Tools.Word namespace and the Microsoft.Office.Tools.Excel namespace. You create an instance of this SmartTag class to define a new Smart Tag. The constructor of the SmartTag object takes two parameters: a unique identifier and the caption that will display in the Smart Tag menu. The unique identifier is constructed using a namespace URI such as "http://vsto.aw.com" and a tag type name such as "mytagtypename" separated by a number sign, resulting in "http://vsto.aw.com#mytagtypename".

The SmartTag object has several important properties and methods. The SmartTag object's Terms property returns a StringCollection to which you can add words you want to recognize. The SmartTag object's Actions property must be set to an array of Action objects representing the actions (the menu items) you want displayed for your Smart Tag. VSTO provides a class called Action in the Microsoft.Office.Tools.Word namespace and the Microsoft.Office.Tools.Excel namespace that you can instantiate. The constructor of the Action object takes one parameter: the caption that will display in the Smart Tag menu for the action. After you have created an Action object for each action you want to make available for your Smart Tag, you can set the SmartTag.Actions property to an array containing all the Action objects you want to provide. Finally, you can handle the Action.Click event for each Action to be called back by Word or Excel when the user selects that action from the Smart Tag menu.

After you have created a SmartTag object, set the SmartTag.Terms collection, created one or more Action objects, and set SmartTag.Actions, you must remember to add the newly created SmartTag to the VstoSmartTags collection on the VSTO Document object for Word and on the VSTO Workbook object for Excel.

Listing 16.1 shows a simple Word VSTO customization that illustrates these steps. First, it creates a SmartTag instance passing "http://vsto.aw.com#fish" as the identifier and "Fish Catcher" as the caption. Then it adds two terms to recognize using SmartTag.Terms: "Mackerel" and "Halibut". Note that a term cannot contain a space. A term such as "Eric Carter" could not be added to the terms collection, for example.

Two actions are created: one with the caption "&Fishing///&Catch a fish" and the other with the caption "&Fishing///&Throw it back". The ampersand (&) in these strings indicates which letter to use as an accelerator for the menu. The use of the three forward slashes tells Word to create a menu called Fishing with a child menu called Catch a fish and a second child menu called Throw it back. These actions are added to the SmartTag.Actions property by creating a new array of Actions containing both actions. Click events raised by the two actions are handled by the code. Finally, the SmartTag instance that was created is added to the VstoSmartTags collection associated with the document object.

Listing 16.1. A VSTO Word Customization That Adds a Smart Tag

Imports Microsoft.Office.Tools.Word Public Class ThisDocument   Private Sub ThisDocument_Startup(ByVal sender As Object, _     ByVal e As System.EventArgs) Handles Me.Startup     Dim mySmartTag As New SmartTag( _       "http://vsto.aw.com#fish", "Fish Catcher")     mySmartTag.Terms.Add("Mackerel")     mySmartTag.Terms.Add("Halibut")     Dim myAction As New Action("&Fishing///&Catch a fish...")     Dim myAction2 As New Action("&Fishing///&Throw it back...")     mySmartTag.Actions = New Action() {myAction, myAction2}     AddHandler myAction.Click, AddressOf myAction_Click     AddHandler myAction2.Click, AddressOf myAction2_Click     Me.VstoSmartTags.Add(mySmartTag)   End Sub   Private Sub myAction_Click(ByVal sender As Object, _     ByVal e As ActionEventArgs)     MsgBox(String.Format( _       "You caught a fish at position {0}.", _       e.Range.Start))   End Sub   Private Sub myAction2_Click(ByVal sender As Object, _     ByVal e As ActionEventArgs)     MsgBox(String.Format( _       "You threw back a fish at position {0}.", _       e.Range.Start))   End Sub End Class 


The code to add a Smart Tag in Excel is very similar and is shown in Listing 16.2. The main changes are to use the SmartTag and Action classes from the Microsoft.Office.Tools.Excel namespace and to use the VstoSmartTags collection off the Workbook object. Because the code in Listing 16.2 is written in Sheet1, the Workbook object is accessed using Globals.ThisWorkbook.

Listing 16.2. A VSTO Excel Customization That Adds a Smart Tag

Imports Microsoft.Office.Tools.Excel Public Class Sheet1   Private Sub Sheet1_Startup(ByVal sender As Object, _     ByVal e As System.EventArgs) Handles Me.Startup     Dim mySmartTag As New SmartTag( _       "http://vsto.aw.com#fish", "Fish Catcher")     mySmartTag.Terms.Add("Mackerel")     mySmartTag.Terms.Add("Halibut")     Dim myAction As New Action("&Fishing///&Catch a fish...")     Dim myAction2 As New Action("&Fishing///&Throw it back...")     mySmartTag.Actions = New Action() {myAction, myAction2}     AddHandler myAction.Click, AddressOf myAction_Click     AddHandler myAction2.Click, AddressOf myAction2_Click     Globals.ThisWorkbook.VstoSmartTags.Add(mySmartTag)   End Sub   Private Sub myAction_Click(ByVal sender As Object, _     ByVal e As ActionEventArgs)     MsgBox(String.Format("You caught a fish at position {0}.", _       e.Range.Address( _       ReferenceStyle:=Excel.XlReferenceStyle.xlA1))   End Sub   Private Sub myAction2_Click(ByVal sender As Object, _     ByVal e As ActionEventArgs)     MsgBox(String.Format( _       "You threw back a fish at position {0}.", _       e.Range.Address( _       ReferenceStyle:=Excel.XlReferenceStyle.xlA1)))   End Sub End Class 


Action Events

In Listing 16.1 and Listing 16.2, we handled the click event of the Action object. The code that handled the click event used the ActionEventArgs argument e and accessed the ActionEventArgs. Range property to get a Word.Range object for Word and an Excel.Range object for Excel. The Range property allows you to access the range of text that was recognized in Word or the Excel cell that contains the recognized text.

The ActionEventArgs.Text property returns the text that was recognized. This proves useful when you are matching multiple string values with a single Smart Tag class.

The ActionEventArgs.Properties property allows you to access a property bag associated with the actions pane. This property bag can be used to store additional information about the text that was recognized. We consider this further in the "Creating a Custom Smart Tag Class" section later in this chapter.

The Action object also raises a BeforeCaptionShow event before the caption for an Action is shown in the actions pane menu. This event is also passed an ActionEventArgs argument e, which can be used to access information about what was recognized just as with the click event. You can use this event to change the caption of the action before it is shown.

Listing 16.3 shows a VSTO Excel customization that handles the Click and BeforeCaptionShow event. You must add a reference to the Microsoft Smart Tags 2.0 Type Library, as shown in Figure 16.7 later in this chapter, to access the types associated with the property bag.

Listing 16.3. A VSTO Excel Customization That Handles the Click and BeforeCaptionShow Events and Uses the ActionEventArgs Argument

Imports Microsoft.Office.Tools.Excel Public Class Sheet1   Private WithEvents myAction As Action   Private WithEvents myAction2 As Action   Private Sub Sheet1_Startup(ByVal sender As Object, _     ByVal e As System.EventArgs) Handles Me.Startup     Dim mySmartTag As New SmartTag( _       "http://vsto.aw.com#fish", "Fish Catcher")     mySmartTag.Terms.Add("Mackerel")     mySmartTag.Terms.Add("Halibut")     myAction = New Action("&Fishing///&Catch a fish...")     myAction2 = New Action("&Fishing///&Throw it back...")     mySmartTag.Actions = New Action() {myAction, myAction2}     Globals.ThisWorkbook.VstoSmartTags.Add(mySmartTag)   End Sub   Private Sub myAction_BeforeCaptionShow( _     ByVal sender As Object, _     ByVal e As ActionEventArgs) _     Handles myAction.BeforeCaptionShow     Dim r As New Random()     myAction.Caption = "Test caption " & r.NextDouble()   End Sub   Private Sub myAction2_Click(ByVal sender As Object, _     ByVal e As ActionEventArgs) Handles myAction2.Click     MsgBox(String.Format( _       "You threw back a fish at position {0}.", _       e.Range.Address( _       ReferenceStyle:=Excel.XlReferenceStyle.xlA1)))     MsgBox(e.Text)     MsgBox(e.Properties.Count.ToString())     Dim i As Integer     For i = 0 To e.Properties.Count - 1 Step i + 1       MsgBox(String.Format("Prop({0},(1})", _         e.Properties.KeyFromIndex(i), _         e.Properties.ValueFromIndex(i)))     Next   End Sub   Private Sub myAction_Click(ByVal sender As Object, _     ByVal e As ActionEventArgs) Handles myAction.Click     MsgBox(String.Format("You caught a fish at position {0}.",_       e.Range.Address( _       ReferenceStyle:=Excel.XlReferenceStyle.xlA1)))   End Sub End Class 


Using Varying Numbers of Terms

It is possible to vary the number of terms recognized at runtime by adding terms to and removing terms from the SmartTag.Terms collection. Listing 16.4 shows this approach. Note that instances of terms that have already been typed in the document and recognized will continue to be recognized even when you remove that term from the Terms collection. But new instances of the removed term that you type will no longer be recognized.

Listing 16.4. A VSTO Excel Customization That Varies the Number of Terms Recognized

Imports Microsoft.Office.Tools.Excel Public Class Sheet1   Private WithEvents myAction As Action   Private mySmartTag As SmartTag   Private Sub Sheet1_Startup(ByVal sender As Object, _     ByVal e As System.EventArgs) Handles Me.Startup     mySmartTag = New SmartTag( _       "http://vsto.aw.com#variableterms", _       "Varying Number of Terms")     mySmartTag.Terms.Add("Hello")     myAction = New Action("Add a new term...")     mySmartTag.Actions = New Action() {myAction}     Globals.ThisWorkbook.VstoSmartTags.Add(mySmartTag)   End Sub   Private Sub myAction_Click(ByVal sender As Object, _     ByVal e As ActionEventArgs) Handles myAction.Click     Dim r As New Random()     Dim numberOfActionsToShow As Integer = r.Next(5)     If mySmartTag.Terms.Contains( _       numberOfActionsToShow.ToString()) Then       mySmartTag.Terms.Remove(numberOfActionsToShow.ToString())       MsgBox(String.Format("Removed the term {0}.", _         numberOfActionsToShow))     Else       mySmartTag.Terms.Add(numberOfActionsToShow.ToString())       MsgBox(String.Format("Added the term {0}.", _         numberOfActionsToShow))     End If   End Sub End Class 


Using Regular Expressions

Although the Terms collection provides a way to recognize specific words, you will inevitably want to have more power in the text patterns that are recognized. The SmartTag class allows you to use regular expressions to recognize text in a Word document or Excel spreadsheet. This book does not cover how to construct a regular expression; if regular expressions are new to you, try looking at the documentation in the .NET Framework for the Regex class.

We are going to construct a regular expression that will match stock symbols in a document. A stock symbol will be defined as any three- or four-letter combination that is in all caps, such as IBM or MSFT. The regular expression we will use is shown below and will match a word (\b indicates a word boundary) that is composed of three to four characters (specified by {3,4}) composed of capital letters from A to Z ([A-Z]):

\b[A-Z]{3,4}\b

This regular expression string is passed to the constructor of a Regex object. Then the Regex object is added to the SmartTag.Expressions collection, as shown in Listing 16.5.

Listing 16.5. A VSTO Excel Customization That Adds a Smart Tag Using a Regular Expression

Imports Microsoft.Office.Tools.Excel Imports System.Text.RegularExpressions Public Class Sheet1   Private WithEvents myAction As Action   Private Sub Sheet1_Startup(ByVal sender As Object, _     ByVal e As System.EventArgs) Handles Me.Startup     Dim mySmartTag As New SmartTag(_       "http://vsto.aw.com#stock", "Stock Trader")     Dim myRegex As New Regex("\b[A-Z]{3,4}\b")     mySmartTag.Expressions.Add(myRegex)     myAction = New Action("Trade this stock...")     mySmartTag.Actions = New Action() {myAction}     Globals.ThisWorkbook.VstoSmartTags.Add(mySmartTag)   End Sub   Private Sub myAction_Click(ByVal sender As Object, _     ByVal e As ActionEventArgs) Handles myAction.Click     MsgBox(String.Format( _       "The stock symbol you selected is {0}", _       e.Text))   End Sub End Class 


Another great feature when you use regular expressions is VSTO's support for named groups in a regular expression. When you create a regular expression with a named group, VSTO creates a namevalue pair in the property bag for each recognized term with the name and value of each named group recognized by the regular expression. You can use the ActionEventArgs object's Properties object to retrieve the value of a named group by using the group name as a key.

Using Varying Numbers of Actions

You might have wondered why the SmartTag object has an Actions property that must be set to a fixed array of Actions. After all, wouldn't it be easier if you could write the code mySmartTag.Actions.Add(myAction)? The reason the Actions property was designed this way is to enforce the notion that the maximum number of actions for a given Smart Tag is fixed at the time you add the SmartTag object to the VstoSmartTags collection. This is a limitation of the Office Smart Tags architecture.

There is a way to have a varying number of actions. There is still the limitation that the maximum number of actions is fixed at the time you first add it to the VstoSmartTags collection. But then, at runtime, you can set actions within the array to Nothing to vary the number of available actions up to the maximum number of actions. Listing 16.6 shows this approach. The maximum number of actions is set to be five actions by setting the initial array of actions to contain five actions. But each time an action is selected, the number of actions is changed by setting the items in the actions array to Nothing or to an Action object.

Listing 16.6. A VSTO Excel Customization with a Varying Number of Actions

Imports Microsoft.Office.Tools.Excel Imports System.Text.RegularExpressions Public Class Sheet1   Private WithEvents myAction As Action   Private mySmartTag As SmartTag   Private Sub Sheet1_Startup(ByVal sender As Object, _     ByVal e As System.EventArgs) Handles Me.Startup     mySmartTag = New SmartTag( _       "http://vsto.aw.com#variableactions", _       "Varying Number of Actions")     Dim myRegex As New Regex("\b[A-Z]{3,4}\b")     mySmartTag.Expressions.Add(myRegex)     myAction = New Action("Change Number of Actions...")     mySmartTag.Actions = New Action() _       {myAction, myAction, myAction, myAction, myAction}     Globals.ThisWorkbook.VstoSmartTags.Add(mySmartTag)   End Sub   Private Sub myAction_Click(ByVal sender As Object, _     ByVal e As ActionEventArgs) Handles myAction.Click     Dim r As New Random()     Dim numberOfActionsToShow As Integer = 1 + r.Next(4)     MsgBox(String.Format("Changing to have {0} actions.", _       numberOfActionsToShow))     Dim i As Integer     For i = 0 To numberOfActionsToShow - 1       mySmartTag.Actions(i) = myAction     Next     For i = numberOfActionsToShow To 4       mySmartTag.Actions(i) = Nothing     Next   End Sub End Class 


Creating a Custom Smart Tag Class

When the Terms collection and the Expressions collection are not sufficient to meet your Smart Tag recognition needs, you also have the option of creating your own custom Smart Tag class that derives from the Word or Excel SmartTag class. This gives you some additional capability. First of all, you get to write your own code to process text that Word or Excel passes to your Smart Tag class to recognize. Second, you can use the ISmartTagProperties collection to associate custom Smart Tag properties in the property bag associated with each instance of recognized text.

Suppose that you are writing a Smart Tag that recognizes part numbers stored in a database. You know that part numbers are in a format such as PN1023, with a PN preface and four following digits. Just because that pattern is found in the text, however, does not mean that it is a valid part number; it might be a part number that has been deleted or does not exist in the database. So after finding a match for the expected part-number format, you want to make a call into the database to make sure that a row exists for the given part number. If the part number is not in the database, you do not want to tag it.

You can do this by writing your own custom Smart Tag class. Your class must derive from the Word or Excel SmartTag class in the Microsoft.Office.Tools.Word or Microsoft.Office.Tools.Excel namespace. Your class must have a constructor that calls into the base class constructor passing the Smart Tag type name and the caption for the Smart Tag. The custom class must also override the Recognize method of the base class shown here:

  Protected Overrides Sub Recognize(ByVal text As String, _     ByVal site As SmartTag.ISmartTagRecognizerSite, _     ByVal tokenList As SmartTag.ISmartTagTokenList) 


The Recognize method passes the text to recognize as a String, an ISmartTagRecognizerSite object that your code will use if it associates custom Smart Tag properties with an instance of recognized text, and a tokenList parameter. Your implementation of Recognize could find the basic part-number format, and if a match is found, it can look up the part number in a database to verify that it is a valid part number. If it is a valid part number, your implementation of Recognize must call into the base class's PersistTag method to specify the index within the text where the part number occurred, the length of the part number, and optionally specify custom Smart Tag properties to associate with the text that will be tagged.

Custom Smart Tag properties are useful when you need to cache additional information that was determined at recognize time and that might be used later when an action associated with a tag is executed. In our example, we have talked to a database to get the row out of the database corresponding to the part number. Perhaps one of the actions available will be to display the price of the part. Because we have accessed the database row for the part, we have the price already. Rather than have to look up the price in the database again when the action displaying the price is invoked, you could choose to create custom Smart Tag properties and add the price to the recognized text as a custom property. You can create a custom Smart Tag properties collection of type ISmartTagProperties by calling the GetNewPropertyBag method on the ISmartTagRecognizerSite object passed into the Recognize method. To get the definition of ISmartTagProperties and ISmartTagRecognizerSite, you must add a reference to your project to the Microsoft Smart Tags 2.0 Type Library, as shown in Figure 16.7

Figure 16.7. A reference to the Microsoft Smart Tags 2.0 Type Library is required to use the ISmartTagProperties and ISmartTagRecognizerSite interfaces in your code.


The code in Listing 16.7 illustrates these ideas by defining a custom Smart Tag class that recognizes part numbers of the format PN1023 and uses ISmartTagRecognizerSite, ISmartTagProperties, and the PersistTag method to associate the custom property "Price" with a part number that has been recognized. Our class CustomSmartTag derives from the SmartTag class in the Microsoft.Office.Tools.Word namespace because this custom Smart Tag will be used with Word. It implements a simple constructor that calls into the base constructor passing an identifier and caption. An action is created and added to the Smart Tag that will display the part cost already stored in the tagged text. It does this by accessing the ISmartTagProperties associated with the tagged text, using the Properties property of the ActionEventArgs argument passed to the Action.Click event.

We override the Recognize method to write custom logic that looks for the part number and then calls IsValidPart to find out whether the part number is in the database and to get the price of the part, if available. The implementation of IsValidPart does not actually connect to a database for this sample but requires that a part number be greater than 1000. To simulate getting a price from a database, it generates a random price that will be saved in the document when the text is tagged. You can easily imagine this function being rewritten to query a database instead.

Listing 16.7. A Custom Smart Tag Class for Word

Imports System Imports System.Collections.Generic Imports System.Text Imports Microsoft.Office.Tools.Word Imports System.Windows.Forms Imports SmartTag = Microsoft.Office.Interop.SmartTag Friend Class CustomSmartTag   Inherits Microsoft.Office.Tools.Word.SmartTag   Private WithEvents customAction As Action   Friend Sub New()     MyBase.New("http://www.aw-bc.com/VSTO#customsmarttag", _       "Custom Smart Tag")     customAction = New Action("Get Part Cost...")     MyBase.Actions = New Action() {customAction}   End Sub   Private Sub customAction_Click(ByVal sender As Object, _     ByVal e As ActionEventArgs) Handles customAction.Click     Dim props As SmartTag.ISmartTagProperties = e.Properties     Dim i As Integer     For i = 0 To props.Count - 1       MsgBox(String.Format("{0} - {1}", props.KeyFromIndex(i), _         props.ValueFromIndex(i)))     Next   End Sub   Protected Overrides Sub Recognize(ByVal text As String, _     ByVal site As SmartTag.ISmartTagRecognizerSite, _     ByVal tokenList As SmartTag.ISmartTagTokenList)     Dim textToFind As String = "PN"     Dim startIndex As Integer = 0     Dim index As Integer = 0     While text.IndexOf(textToFind, startIndex) >= 0       index = text.IndexOf(textToFind, startIndex)       If index + 6 < text.Length Then         Dim partNumber As String = text.Substring(index, 6)         Dim price As String = ""         If IsValidPart(partNumber, price) Then           Dim props As SmartTag.ISmartTagProperties = _             site.GetNewPropertyBag()           props.Write("Price", price)           MyBase.PersistTag(index, 6, props)         End If       End If       startIndex = index + textToFind.Length     End While   End Sub   Private Function IsValidPart(ByVal partNumber As String, _     ByRef price As String) As Boolean     Dim numericPartNumber As Int32 = 0     Try       numericPartNumber = Convert.ToInt32( _         partNumber.Substring(2, 4))     Catch     End Try     ' Only part numbers greater than 1000 are valid     If numericPartNumber > 1000 Then       Dim rnd As New Random()       price = rnd.Next(100).ToString()       Return True     End If     price = "N/A"     Return False   End Function End Class 


To add this custom Smart Tag to the document, you must put this code in the Startup method of your document:

Me.VstoSmartTags.Add(New CustomSmartTag()) 


Using Smart Tag Properties Wisely

You must consider some other issues when using Smart Tag properties. These properties are serialized into the document, and the recognizer is not given a chance to re-recognize text that has already been recognized. You might type in the part number on May 1, for example, and the Recognize method runs. Then you save the document, and the price is saved with the document. When you reopen the document on May 31 and click the Smart Tag menu to select the Get Part Cost action, the action will go to the Smart Tag property created on May 1 and display the May 1 price. Therefore, if the prices of parts change frequently, the part price stored as a custom property may be out of date when the action is invoked at some time later than when the Recognize method was called.

Also, remember that any Smart Tag properties you put in the document for recognized text will be visible in the saved document file format. So be sure not to put Smart Tag properties containing sensitive information in the document. You could have a document full of part numbers that you send to a competitor, for example. If the custom Smart Tag in Listing 16.7 has recognized all the part numbers in the document before you save the document and send it to the competitor, the prices of all those parts will also be embedded in the document with each tagged part number.




Visual Studio Tools for Office(c) Using Visual Basic 2005 with Excel, Word, Outlook, and InfoPath
Visual Studio Tools for Office: Using Visual Basic 2005 with Excel, Word, Outlook, and InfoPath
ISBN: 0321411757
EAN: 2147483647
Year: N/A
Pages: 221

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net