Creating Document-Level Smart Tags with VSTO

The simplest way to create a Smart Tag is by using VSTO's support for document-level Smart Tags. VSTO provides some classes that enable you to easily create a Smart Tag. 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 parameterthe 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. It first creates a SmartTag instance passing "http://vsto.aw.com#fish" as the identifier and "Fish Catcher" as the caption. It then adds two terms to recognize using SmartTag.Terms: "Mackerel" and "Halibut". Note that a term cannot contain a spacefor example, a term such as "Eric Carter" could not be added to the terms collection.

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 Vsto-SmartTags collection associated with the document object.

Listing 16-1. A VSTO Word Customization That Adds a Smart Tag

using System;
using System.Windows.Forms;
using Microsoft.VisualStudio.Tools.Applications.Runtime;
using Word = Microsoft.Office.Interop.Word;
using Office = Microsoft.Office.Core;
using Microsoft.Office.Tools.Word;

namespace WordDocument1
{
 public partial class ThisDocument
 {
 private void ThisDocument_Startup(object sender, EventArgs e)
 {
 SmartTag mySmartTag = new SmartTag(
 "http://vsto.aw.com#fish", "Fish Catcher");
 mySmartTag.Terms.Add("Mackerel");
 mySmartTag.Terms.Add("Halibut");

 Action myAction =
 new Action("&Fishing///&Catch a fish...");
 Action myAction2 =
 new Action("&Fishing///&Throw it back...");
 mySmartTag.Actions =
 new Action[] { myAction, myAction2 };

 myAction.Click +=
 new ActionClickEventHandler(myAction_Click);
 myAction2.Click +=
 new ActionClickEventHandler(myAction2_Click);

 this.VstoSmartTags.Add(mySmartTag);
 }

 void myAction_Click(object sender, ActionEventArgs e)
 {
 MessageBox.Show(String.Format(
 "You caught a fish at position {0}.",
 e.Range.Start));
 }

 void myAction2_Click(object sender, ActionEventArgs e)
 {
 MessageBox.Show(String.Format(
 "You threw back a fish at position {0}.",
 e.Range.Start));
 }

 #region VSTO Designer generated code
 private void InternalStartup()
 {
 this.Startup += new EventHandler(ThisDocument_Startup);
 }
 #endregion
 }
}

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 of 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

using System;
using System.Windows.Forms;
using Microsoft.VisualStudio.Tools.Applications.Runtime;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.Office.Tools.Excel;

namespace ExcelWorkbook1
{
 public partial class Sheet1
 {
 private void Sheet1_Startup(object sender, EventArgs e)
 {
 SmartTag mySmartTag = new
 SmartTag("http://vsto.aw.com#fish", "Fish Catcher");
 mySmartTag.Terms.Add("Mackerel");
 mySmartTag.Terms.Add("Halibut");

 Action myAction = new Action("Catch a fish...");
 Action myAction2 = new Action("Throw it back...");
 mySmartTag.Actions =
 new Action[] { myAction, myAction2 };

 myAction.Click +=
 new ActionClickEventHandler(myAction_Click);
 myAction2.Click +=
 new ActionClickEventHandler(myAction2_Click);

 Globals.ThisWorkbook.VstoSmartTags.Add(mySmartTag);
 }

 void myAction2_Click(object sender, ActionEventArgs e)
 {
 MessageBox.Show(String.Format(
 "You threw back a fish at address {0}.",
 e.Range.get_Address(missing, missing,
 Excel.XlReferenceStyle.xlA1, missing, missing)));
 }

 void myAction_Click(object sender, ActionEventArgs e)
 {
 MessageBox.Show(String.Format(
 "You caught a fish at address {0}.",
 e.Range.get_Address(missing, missing,
 Excel.XlReferenceStyle.xlA1, missing, missing)));
 }

 #region VSTO Designer generated code
 private void InternalStartup()
 {
 this.Startup += new EventHandler(Sheet1_Startup);
 }
 #endregion

 }
}

 

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.

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 ActionEvent-Args 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 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

using System;
using System.Windows.Forms;
using Microsoft.VisualStudio.Tools.Applications.Runtime;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.Office.Tools.Excel;

namespace ExcelWorkbook1
{
 public partial class Sheet1
 {
 Action myAction;
 Action myAction2;

 private void Sheet1_Startup(object sender, EventArgs e)
 {
 SmartTag mySmartTag = new SmartTag(
 "http://vsto.aw.com#fish", "Fish Catcher");
 mySmartTag.Terms.Add("Mackerel");
 mySmartTag.Terms.Add("Halibut");

 myAction = new Action("Catch a fish...");
 myAction2 = new Action("Throw it back...");
 mySmartTag.Actions =
 new Action[] { myAction, myAction2 };

 myAction.Click +=
 new ActionClickEventHandler(myAction_Click);

 myAction.BeforeCaptionShow +=
 new BeforeCaptionShowEventHandler(
 myAction_BeforeCaptionShow);

 myAction2.Click +=
 new ActionClickEventHandler(myAction2_Click);

 Globals.ThisWorkbook.VstoSmartTags.Add(mySmartTag);
 }

 void myAction_BeforeCaptionShow(object sender,
 ActionEventArgs e)
 {
 Random r = new Random();

 myAction.Caption = "Test caption " + r.NextDouble();
 }

 void myAction2_Click(object sender, ActionEventArgs e)
 {
 MessageBox.Show(String.Format(
 "You threw back a fish at address {0}.",
 e.Range.get_Address(missing, missing,
 Excel.XlReferenceStyle.xlA1, missing, missing)));

 MessageBox.Show(e.Text);
 MessageBox.Show(e.Properties.Count.ToString());
 for (int i = 0; i < e.Properties.Count; i++)
 {
 MessageBox.Show(String.Format(
 "Prop({0},(1})",
 e.Properties.get_KeyFromIndex(i),
 e.Properties.get_ValueFromIndex(i)));
 }
 }

 void myAction_Click(object sender, ActionEventArgs e)
 {
 MessageBox.Show(String.Format(
 "You caught a fish at address {0}.",
 e.Range.get_Address(missing, missing,
 Excel.XlReferenceStyle.xlA1, missing, missing)));
 }

 #region VSTO Designer generated code
 private void InternalStartup()
 {
 this.Startup += new System.EventHandler(Sheet1_Startup);
 }
 #endregion
 }
}

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.

 

Using Varying Numbers of Terms

It is possible to vary the number of terms recognized at runtime by adding and removing terms from the SmartTag.Terms collection. Listing 16-4 shows this approach. Note that instances of terms that have already been typed into 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

using System;
using System.Windows.Forms;
using Microsoft.VisualStudio.Tools.Applications.Runtime;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.Office.Tools.Excel;
using System.Text.RegularExpressions;

namespace ExcelWorkbook1
{
 public partial class Sheet1
 {
 Action myAction;
 SmartTag mySmartTag;

 private void Sheet1_Startup(object sender, EventArgs e)
 {
 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};

 myAction.Click +=
 new ActionClickEventHandler(myAction_Click);

 Globals.ThisWorkbook.VstoSmartTags.Add(mySmartTag);
 }

 void myAction_Click(object sender, ActionEventArgs e)
 {
 Random r = new Random();
 int numberOfActionsToShow = r.Next(5);

 if (mySmartTag.Terms.Contains(
 numberOfActionsToShow.ToString()) == true)
 {
 mySmartTag.Terms.Remove(
 numberOfActionsToShow.ToString());
 MessageBox.Show(String.Format(
 "Removed the term {0}.",
 numberOfActionsToShow));
 }
 else
 {
 mySmartTag.Terms.Add(
 numberOfActionsToShow.ToString());
 MessageBox.Show(String.Format(
 "Added the term {0}.",
 numberOfActionsToShow));
 }
 }

 #region VSTO Designer generated code
 private void InternalStartup()
 {
 this.Startup += new System.EventHandler(Sheet1_Startup);
 }
 #endregion
 }
}

 

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 expressionif 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 ( 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]):

[A-Z]{3,4}

This regular expression string is passed to the constructor of a Regex object. The Regex object is then 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

using System;
using System.Windows.Forms;
using Microsoft.VisualStudio.Tools.Applications.Runtime;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.Office.Tools.Excel;
using System.Text.RegularExpressions;

namespace ExcelWorkbook4
{
 public partial class Sheet1
 {
 Action myAction;

 private void Sheet1_Startup(object sender, EventArgs e)
 {
 SmartTag mySmartTag = new SmartTag(
 "http://vsto.aw.com#stock", "Stock Trader");
 Regex myRegex = new Regex(@"[A-Z]{3,4}");

 mySmartTag.Expressions.Add(myRegex);

 myAction = new Action("Trade this stock...");
 mySmartTag.Actions = new Action[] { myAction };

 myAction.Click +=
 new ActionClickEventHandler(myAction_Click);

 Globals.ThisWorkbook.VstoSmartTags.Add(mySmartTag);
 }

 void myAction_Click(object sender, ActionEventArgs e)
 {
 MessageBox.Show(String.Format(
 "The stock symbol you selected is {0}", e.Text));
 }

 #region VSTO Designer generated code
 private void InternalStartup()
 {
 this.Startup += new EventHandler(Sheet1_Startup);
 }
 #endregion
 }
}

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 name value 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.

However, 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 you can then at runtime set actions within the array to null 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 null or to an Action object.

Listing 16-6. A VSTO Excel Customization with a Varying Number of Actions

using System;
using System.Windows.Forms;
using Microsoft.VisualStudio.Tools.Applications.Runtime;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.Office.Tools.Excel;
using System.Text.RegularExpressions;

namespace ExcelWorkbook4
{
 public partial class Sheet1
 {
 Action myAction;
 SmartTag mySmartTag;

 private void Sheet1_Startup(object sender, EventArgs e)
 {
 mySmartTag = new SmartTag(
 "http://vsto.aw.com#variableactions",
 "Varying Number of Actions");
 Regex myRegex = new Regex(@"[A-Z]{3,4}");

 mySmartTag.Expressions.Add(myRegex);

 myAction = new Action("Change Number of Actions...");
 mySmartTag.Actions = new Action[]
 { myAction, myAction, myAction, myAction, myAction};

 myAction.Click +=
 new ActionClickEventHandler(myAction_Click);

 Globals.ThisWorkbook.VstoSmartTags.Add(mySmartTag);
 }

 void myAction_Click(object sender, ActionEventArgs e)
 {
 Random r = new Random();
 int numberOfActionsToShow = 1 + r.Next(4);

 MessageBox.Show(String.Format(
 "Changing to have {0} actions.",
 numberOfActionsToShow));

 for (int i = 0; i < numberOfActionsToShow; i++)
 {
 mySmartTag.Actions[i] = myAction;
 }

 for (int i = numberOfActionsToShow; i < 5; i++)
 {
 mySmartTag.Actions[i] = null;
 }

 }

 #region VSTO Designer generated code
 private void InternalStartup()
 {
 this.Startup += new EventHandler(Sheet1_Startup);
 }
 #endregion

 }
}

 

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.

For example, suppose you are writing a Smart Tag that recognizes part numbers that are stored in a database. You know that part numbers are in a format such as PN1023, with a PN preface and four following digits. However, just because that pattern is found in the text does not mean 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 also want to make a call into the database to make sure 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 namespaces. 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 override void Recognize(string text,
 Microsoft.Office.Interop.SmartTag.ISmartTagRecognizerSite site,
 Microsoft.Office.Interop.SmartTag.ISmartTagTokenList tokenList)
{
}

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 then look up the part number in a database to verify 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 that 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 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 again in the database when the action displaying the price is invoked, you could choose to create custom Smart Tag properties and add the price as a custom property to the recognized text. A custom Smart Tag properties collection of type ISmartTagProperties can be created 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.

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 Custom-SmartTag 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 get the price for the part if available. The implementation of IsValidPart does not actually connect to a database for this sample, but instead requires 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

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Office.Tools.Word;
using System.Windows.Forms;
using SmartTag = Microsoft.Office.Interop.SmartTag;

namespace WordDocument1
{
 internal class CustomSmartTag : SmartTag
 {
 Action customAction;

 internal CustomSmartTag()
 : base(
 "http://www.aw-bc.com/VSTO#customsmarttag",
 "Custom Smart Tag")
 {
 customAction = new Action("Get Part Cost...");
 base.Actions = new Action[] { customAction };
 customAction.Click +=
 new ActionClickEventHandler(customAction_Click);
 }

 void customAction_Click(object sender, ActionEventArgs e)
 {
 ISmartTagProperties props = e.Properties;
 for (int i = 0; i < props.Count; i++)
 {
 MessageBox.Show(String.Format(
 "{0} - {1}", props.get_KeyFromIndex(i),
 props.get_ValueFromIndex(i)));
 }
 }

 protected override void Recognize(string text,
 ISmartTagRecognizerSite site,
 ISmartTagTokenList tokenList)
 {
 string textToFind = "PN";

 int startIndex = 0;
 int index = 0;

 while ((index = text.IndexOf(
 textToFind, startIndex)) >= 0)
 {
 if (index + 6 < text.Length)
 {
 string partNumber = text.Substring(index, 6);
 string price = "";
 if (IsValidPart(partNumber, out price))
 {
 ISmartTagProperties props =
 site.GetNewPropertyBag();
 props.Write("Price", price);
 base.PersistTag(index, 6, props);
 }
 }

 startIndex = index + textToFind.Length;
 }
 }

 private bool IsValidPart(string partNumber, out string price)
 {
 int numericPartNumber = 0;
 try
 {
 numericPartNumber = Convert.ToInt32(
 partNumber.Substring(2, 4));
 }
 catch { };

 // Only part numbers greater than 1000 are valid
 if (numericPartNumber > 1000)
 {
 Random rnd = new Random();
 price = rnd.Next(100).ToString();
 return true;
 }

 price = "N/A";
 return false;
 }
 }
}

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

private void ThisDocument_Startup(object sender, EventArgs e)
{
 this.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. For example, you might type in the part number on May 1 and the Recognize method runs. You then 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 price of parts frequently changes, 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 in the document containing sensitive information. For example, you could have a document full of part numbers that you send to a competitor. 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 C# with Excel, Word, Outlook, and InfoPath
Visual Studio Tools for Office(c) Using C# with Excel, Word, Outlook, and InfoPath
ISBN: 321334884
EAN: N/A
Year: N/A
Pages: 214
Flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net