Smart Tags are a technology introduced with Office XP that
provide Office users more interactivity with the content of their Office
documents. A Smart Tag is an element of text in an Office document that is
recognized as having custom actions associated with it. An example of one of
these special elements of text might be an e-mail name that is typed into a
Word document or an Excel workbook. If the e-mail name is recognized as a Smart
Tag, the user is presented with one or more actions to perform on that text.
Possible actions that are associated with an e-mail name are to look up
additional contact information or to send a new e-mail message to that
contact.
You can extend the capabilities of Office XP by developing
your own Smart Tag Recognizer/Action dynamic-link library (DLL) for use in
Office documents. This article describes how to build a Smart Tag DLL by using
Active Template Library (ATL), and discusses what registry settings are
required for Office XP to identify and use your Smart Tag DLL.
NOTE: Excel 2002 and Word 2002 are the only Office XP applications
that support Smart Tags. However, the information that is presented in this
article can be applied to Smart Tag development for any application that adopts
the Smart Tag technology. A Smart Tag DLL is a standard Component Object Model
(COM) DLL that implements two special interfaces:
ISmartTagRecognizer and
ISmartTagAction. The
ISmartTagRecognizer interface recognizes text that is typed into a document as a
Smart Tag. The
ISmartTagAction interface performs actions on a particular Smart Tag string at
the user's request. It is not required that these interfaces be implemented in
the same DLL. You can have a recognizer DLL, and then one or more action DLLs
that extend a single Smart Tag type for different actions.
Steps to Create the Smart Tag DLL in ATL
The following steps create a simple Smart Tag
DLL that recognizes the Microsoft Network (MSN) Instant Messenger contacts and
gives the user the ability to send e-mail or instant messages to a recognized
contact. Instant Messenger is required to use this sample. If you do not have
Instant Messenger, you can obtain a copy from the following MSN Web site:
- In Visual C++, create a new ATL COM AppWizard project. Name
the project MessengerSmartTag.
- Click OK to start the ATL COM wizard. In the next dialog box, make sure
that Dynamic Link Library is selected and click Finish. Click OK to create the project.
- To create the Recognizer class, on the Insert menu, click New ATL Object. Select Simple Object and click Next. For the short name, type Recognizer and
click OK.
- To create the Action class, follow the instructions in step 3, but type Action for the short name.
- Open the ClassView and expand MessengerSmartTag classes. Right-click the CRecognizer class and select Implement Interface. Click OK when the warning dialog box appears. Click Browse and select Microsoft Smart Tags 1.0 Type Library. Select the ISmartTagRecognizer and the ISmartTagRecognizer2 interface and press OK.
NOTE: The default location for the Microsoft Smart Tags 1.0 Type
Library is C:\Program Files\Common Files\Microsoft Shared\Mstag.tlb.
- In ClassView, right-click the CAction class and select Implement Interface. Click OK when the warning dialog box appears. Click Browse and select Microsoft Smart Tags 1.0 Type Library. Select the ISmartTagAction and the ISmartTagAction2 interface and click OK.
- Open the Recognizer.h file and replace the class contents
with the following code:
/////////////////////////////////////////////////////////////////////////////
// CRecognizer
#include "Resource.h"
#import "C:\Program Files\Common Files\Microsoft Shared\Smart Tag\MSTAG.TLB" raw_interfaces_only, raw_native_types, no_namespace, named_guids
class ATL_NO_VTABLE CRecognizer :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CRecognizer, &CLSID_Recognizer>,
public IDispatchImpl<IRecognizer, &IID_IRecognizer, &LIBID_MESSENGERSMARTTAGLib>,
public IDispatchImpl<ISmartTagRecognizer, &IID_ISmartTagRecognizer, &LIBID_SmartTagLib>,
public IDispatchImpl<ISmartTagRecognizer2, &IID_ISmartTagRecognizer2, &LIBID_SmartTagLib>
{
public:
CRecognizer();
~CRecognizer();
DECLARE_REGISTRY_RESOURCEID(IDR_RECOGNIZER)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CRecognizer)
COM_INTERFACE_ENTRY(IRecognizer)
//Removed -- COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY2(IDispatch, IRecognizer)
COM_INTERFACE_ENTRY(ISmartTagRecognizer)
COM_INTERFACE_ENTRY(ISmartTagRecognizer2)
END_COM_MAP()
// IRecognizer
public:
// ISmartTagRecognizer
STDMETHOD(get_ProgId)(BSTR * ProgId);
STDMETHOD(get_Name)(INT LocaleID, BSTR * Name);
STDMETHOD(get_Desc)(INT LocaleID, BSTR * Desc);
STDMETHOD(get_SmartTagCount)(INT * Count);
STDMETHOD(get_SmartTagName)(INT SmartTagID, BSTR * Name);
STDMETHOD(get_SmartTagDownloadURL)(INT SmartTagID, BSTR * DownloadURL);
STDMETHOD(Recognize)(BSTR Text, IF_TYPE DataType, INT LocaleID,
ISmartTagRecognizerSite * RecognizerSite);
private:
long lCount;
SAFEARRAY *psa;
// ISmartTagRecognizer2
STDMETHOD(Recognize2)(BSTR Text, IF_TYPE DataType, INT LocaleID, ISmartTagRecognizerSite2 * RecognizerSite2, BSTR ApplicationName, ISmartTagTokenList * TokenList)
{
return E_NOTIMPL;
}
STDMETHOD(get_PropertyPage)(INT SmartTagID, INT LocaleID, VARIANT_BOOL * HasPropPage)
{
if (HasPropPage == NULL)
return E_POINTER;
return E_NOTIMPL;
}
STDMETHOD(DisplayPropertyPage)(INT SmartTagID, INT LocaleID)
{
return E_NOTIMPL;
}
STDMETHOD(SmartTagInitialize)(BSTR ApplicationName)
{
return E_NOTIMPL;
}
};
- Open the Recognizer.cpp file and add the following code to
end of the file:
CRecognizer::CRecognizer()
{
Messenger::IMsgrObject2Ptr oMsgrObj = NULL;
Messenger::IMsgrUsersPtr oUsers = NULL;
Messenger::IMsgrUserPtr oUser = NULL;
SAFEARRAYBOUND rgsaBound[1];
long rgIndices[1];
HRESULT hr;
// Create an instance of Instant Messenger.
oMsgrObj.CreateInstance("Messenger.MsgrObject");
// Get the list of contacts
oUsers = oMsgrObj->GetList(Messenger::MLIST_CONTACT);
// Store the number of contacts you have.
lCount = oUsers->GetCount();
rgsaBound[0].lLbound = 0;
rgsaBound[0].cElements = lCount;
// Create a SAFEARRAY to hold the list of contacts.
psa = SafeArrayCreate(VT_VARIANT, 1, rgsaBound);
// Loop through all contacts.
for (long l=0; l<lCount-1; l++)
{
rgIndices[0] = l;
// Set the specific user.
oUser = oUsers->Item(l);
// Convert the Friendly Name to lower case
// and store it in a VARIANT.
_variant_t v = _wcslwr(oUser->GetFriendlyName());
// Put the VARIANT into the SAFEARRAY.
hr = SafeArrayPutElement(psa, rgIndices, &v);
}
}
CRecognizer::~CRecognizer()
{
// Destroy the SAFEARRAY.
SafeArrayDestroy(psa);
}
HRESULT CRecognizer::get_ProgId(BSTR * ProgId)
{
// Set the ProgID of the Recognizer interface.
*ProgId = SysAllocString(L"MessengerSmartTag.Recognizer");
return S_OK;
}
HRESULT CRecognizer::get_Name(INT LocaleID, BSTR * Name)
{
// Set a short title about the recognizer.
*Name = SysAllocString(L"Microsoft Messenger Contacts Visual C++ Recognizer");
return S_OK;
}
HRESULT CRecognizer::get_Desc(INT LocaleID, BSTR * Desc)
{
// Set a long description of the recognizer.
*Desc = SysAllocString(L"Microsoft Messenger recognizes your Instant Messenger Contacts");
return S_OK;
}
HRESULT CRecognizer::get_SmartTagCount(INT * Count)
{
// Set the number of Smart Tags that are supported.
*Count = 1;
return S_OK;
}
HRESULT CRecognizer::get_SmartTagName(INT SmartTagID, BSTR * Name)
{
// This method is called the same number of times as you
// return in SmartTagCount. This method sets a unique name
// for the Smart Tag.
*Name = SysAllocString(L"microsoft/messenger#contacts");
return S_OK;
}
HRESULT CRecognizer::get_SmartTagDownloadURL(INT SmartTagID, BSTR * DownloadURL)
{
// Set the URL that gets embedded in documents.
*DownloadURL = NULL;
return S_OK;
}
HRESULT CRecognizer::Recognize(BSTR Text, IF_TYPE DataType, INT LocaleID,
ISmartTagRecognizerSite * RecognizerSite)
{
// The Recognize method is called and passed a text value.
// You should recognize strings in the text and set up the actions.
WCHAR *pch, *strText = _wcslwr(Text);
ISmartTagProperties *pSmartTagProp = NULL;
long rgIndices[1];
HRESULT hr;
// Look through all contacts
for (long l = 0; l<lCount; l++)
{
rgIndices[0] = l;
// Get the contact name.
_variant_t v;
hr = SafeArrayGetElement(psa,rgIndices,&v);
// Convert the VARIANT to a BSTR.
_bstr_t bstrContact = v;
// Loop through the string looking for contacts.
for (pch = strText; (pch = wcsstr(pch, bstrContact))!=NULL; pch++)
{
// Create a new property bag.
hr = RecognizerSite->GetNewPropertyBag(&pSmartTagProp);
if (SUCCEEDED(hr)) {
// Commit the Smart Tag to the property bag.
hr = RecognizerSite->CommitSmartTag(
_bstr_t("microsoft/messenger#contacts"),
pch - strText+1, wcslen(bstrContact),
pSmartTagProp);
if (pSmartTagProp != NULL)
pSmartTagProp->Release();
}
}
}
return S_OK;
}
- Open the Action.h file and replace the contents of the
class with the following code:
/////////////////////////////////////////////////////////////////////////////
// CAction
#include "Resource.h"
#import "C:\Program Files\Common Files\Microsoft Shared\Smart Tag\MSTAG.TLB" raw_interfaces_only, raw_native_types, no_namespace, named_guids
class ATL_NO_VTABLE CAction :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CAction, &CLSID_Action>,
public IDispatchImpl<IAction, &IID_IAction, &LIBID_MESSENGERSMARTTAGLib>,
public IDispatchImpl<ISmartTagAction, &IID_ISmartTagAction, &LIBID_SmartTagLib>,
public IDispatchImpl<ISmartTagAction2, &IID_ISmartTagAction2, &LIBID_SmartTagLib>
{
public:
CAction(){}
DECLARE_REGISTRY_RESOURCEID(IDR_ACTION)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CAction)
COM_INTERFACE_ENTRY(IAction)
//Removed -- COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY2(IDispatch, IAction)
COM_INTERFACE_ENTRY(ISmartTagAction)
COM_INTERFACE_ENTRY(ISmartTagAction2)
END_COM_MAP()
// IAction
public:
// ISmartTagAction
STDMETHOD(get_ProgId)(BSTR * ProgId);
STDMETHOD(get_Name)(INT LocaleID, BSTR * Name);
STDMETHOD(get_Desc)(INT LocaleID, BSTR * Desc);
STDMETHOD(get_SmartTagCount)(INT * Count);
STDMETHOD(get_SmartTagName)(INT SmartTagID, BSTR * Name);
STDMETHOD(get_SmartTagCaption)(INT SmartTagID, INT LocaleID,
BSTR * Caption);
STDMETHOD(get_VerbCount)(BSTR SmartTagName, INT * Count);
STDMETHOD(get_VerbID)(BSTR SmartTagName, INT VerbIndex, INT * VerbID);
STDMETHOD(get_VerbCaptionFromID)(INT VerbID, BSTR ApplicationName,
INT LocaleID, BSTR * Caption);
STDMETHOD(get_VerbNameFromID)(INT VerbID, BSTR * Name);
STDMETHOD(InvokeVerb)(INT VerbID, BSTR ApplicationName,
IDispatch * Target, ISmartTagProperties * Properties,
BSTR Text, BSTR Xml);
// ISmartTagAction2
STDMETHOD(get_VerbCaptionFromID2)(INT VerbID, BSTR ApplicationName, INT LocaleID, ISmartTagProperties * Properties, BSTR Text, BSTR Xml, IDispatch * Target, BSTR * Caption)
{
if (Caption == NULL)
return E_POINTER;
return E_NOTIMPL;
}
STDMETHOD(InvokeVerb2)(INT VerbID, BSTR ApplicationName, IDispatch * Target, ISmartTagProperties * Properties, BSTR Text, BSTR Xml, INT LocaleID)
{
return E_NOTIMPL;
}
STDMETHOD(get_IsCaptionDynamic)(INT VerbID, BSTR ApplicationName, INT LocaleID, VARIANT_BOOL * Dynamic)
{
if (Dynamic == NULL)
return E_POINTER;
return E_NOTIMPL;
}
STDMETHOD(get_ShowSmartTagIndicator)(INT VerbID, BSTR ApplicationName, INT LocaleID, VARIANT_BOOL * Visible)
{
if (Visible == NULL)
return E_POINTER;
return E_NOTIMPL;
}
STDMETHOD(SmartTagInitialize)(BSTR ApplicationName)
{
return E_NOTIMPL;
}
};
- Open the Action.cpp file and add the following code to the
end of the file:
HRESULT CAction::get_ProgId(BSTR * ProgId)
{
// Set the ProgID of the Action interface.
*ProgId = SysAllocString(L"MessengerSmartTag.Action");
return S_OK;
}
HRESULT CAction::get_Name(INT LocaleID, BSTR * Name)
{
// Set a short name describing the Action.
*Name = SysAllocString(L"Messenger Smart Tag");
return S_OK;
}
HRESULT CAction::get_Desc(INT LocaleID, BSTR * Desc)
{
// Set a long description describing the action.
*Desc = SysAllocString(L"Provides actions for the Messenger Smart Tag");
return S_OK;
}
HRESULT CAction::get_SmartTagCount(INT * Count)
{
// Set the number of smart tags this action supports.
*Count = 1;
return S_OK;
}
HRESULT CAction::get_SmartTagName(INT SmartTagID, BSTR * Name)
{
// This method is called the same number of times as you
// return in SmartTagCount. This method sets a unique name
// for the smart tag.
*Name = SysAllocString(L"microsoft/messenger#contacts");
return S_OK;
}
HRESULT CAction::get_SmartTagCaption(INT SmartTagID, INT LocaleID, BSTR * Caption)
{
// This caption is displayed on the menu for the smart tag.
*Caption = SysAllocString(L"Messenger Smart Tag");
return S_OK;
}
HRESULT CAction::get_VerbCount(BSTR SmartTagName, INT * Count)
{
// Return the number of verbs we support.
if (wcsstr(SmartTagName,L"microsoft/messenger#contacts") != 0) {
*Count = 2;
}
return S_OK;
}
HRESULT CAction::get_VerbID(BSTR SmartTagName, INT VerbIndex, INT * VerbID)
{
// Return a unique ID for each verb we support.
*VerbID = VerbIndex;
return S_OK;
}
HRESULT CAction::get_VerbCaptionFromID(INT VerbID, BSTR ApplicationName,
INT LocaleID, BSTR * Caption)
{
// Set a caption for each verb. This caption is displayed
// on the Smart Tag menu.
switch (VerbID) {
case 1:
*Caption = SysAllocString(L"Send this contact an Instant Message");
break;
case 2:
*Caption = SysAllocString(L"Send email to this contact");
break;
default:
*Caption = NULL;
break;
}
return S_OK;
}
HRESULT CAction::get_VerbNameFromID(INT VerbID, BSTR * Name)
{
// Set a string name for each verb.
switch (VerbID) {
case 1:
*Name = SysAllocString(L"SendInstantMessage");
break;
case 2:
*Name = SysAllocString(L"SendEmail");
break;
}
return S_OK;
}
HRESULT CAction::InvokeVerb(INT VerbID, BSTR ApplicationName,
IDispatch * Target, ISmartTagProperties * Properties,
BSTR Text, BSTR Xml)
{
// This method is called when a user invokes a verb
// from the Smart Tag menu.
Messenger::IMessengerApp2Ptr oMessenger = NULL;
Messenger::IMsgrObject2Ptr oMsgrObj = NULL;
Messenger::IMsgrUsersPtr oUsers = NULL;
Messenger::IMsgrUserPtr oUser = NULL;
_variant_t v;
// Create an instance of Instant Messenger.
oMessenger.CreateInstance("Messenger.MessengerApp");
oMsgrObj.CreateInstance("Messenger.MsgrObject");
// Get a list of contacts.
oUsers = oMsgrObj->GetList(Messenger::MLIST_CONTACT);
// Loop through all contacts.
for (long l=0; l<(oUsers->GetCount()-1); l++)
{
// Get a specific contact.
oUser = oUsers->Item(l);
// Check to see if the contact is the correct one.
if (wcscmp(_wcslwr(oUser->GetFriendlyName()),_wcslwr(Text)) == 0)
{
switch (VerbID) {
case 1:
// The user wants to display the Instant Message
// box to send the contact a message.
v = oUser.GetInterfacePtr();
oMessenger->LaunchIMUI(v);
break;
case 2:
// Shell the "mailto" protocol to start the
// user's mail program and create a new message.
_bstr_t bstrTemp = "mailto:";
bstrTemp += oUser->GetEmailAddress();
ShellExecute(0,"open",bstrTemp,NULL,NULL,1);
break;
}
}
}
return S_OK;
}
- Open the Stdafx.h file and add the following line after the
line that reads #include <atlcom.h>:
#import "C:\Program Files\Messenger\msmsgs.exe"
NOTE: Change the path of the Msmsgs.exe file to the installation point
for Instant Messenger. The default location for Instant Messenger is C:\Program
Files\Messenger. - Press F7 to build the DLL.
Steps to Register the Smart Tag DLL
Before you can use any Smart Tag DLL, you
must register it on the system. Normal COM registration is done for you when
you compile the project or call Regsvr32.exe with the DLL name. You must create
additional registry entries that are not part of normal COM registration so
that Office applications can identify the DLL as a Smart Tag DLL. To do this,
follow these steps:
- From a command line, start Regedit.exe.
- At HKEY_CURRENT_USER\Software\Microsoft\Office\Common\Smart Tag\Actions, add a new subkey named MessengerSmartTag.Action.
- At HKEY_CURRENT_USER\Software\Microsoft\Office\Common\Smart Tag\Recognizers, add a new subkey named MessengerSmartTag.Recognize.
- Close the Registry Editor.
Steps to Test the Smart Tag DLL
Smart Tags obey the same security model as
macros. If the security settings of the application are set to High, the Smart
Tag DLL does not load unless the DLL is digitally signed (as is also the case
with VBA macros). For more information on digital signing, see the "References"
section.
To test the custom Smart Tag Recognizer/Action DLL in Word,
follow these steps:
- Start Instant Messenger and log on.
NOTE: The sample Smart Tag requires that you log on to Instant
Messenger; if you do not log on to Instant Messenger, the custom DLL loads but
does not recognize any contacts. - Start Word 2002. On the Tools menu, point to Macro and click Security. Set the macro security to Medium and click OK. If the macro security setting was previously set to High, restart Word.
- Type the friendly name of a contact in a new document (for
example, John Smith) and press ENTER. A faint line appears beneath the friendly
name to indicate that it is recognized as a Smart Tag. Move the mouse over the
friendly name, and the Smart Tag Action button appears.
- Click Smart Tag Action and select one of the custom action items from the drop-down
menu. You can send an e-mail or instant message to the contact from your new
document.
You can use similar steps to test the Smart Tag DLL in Excel
2002.
Troubleshooting
If you have problems getting your custom
Smart Tags to work, first make sure that the custom Smart Tag DLL is being
loaded. In Word or Excel, on the
Tools menu, click
Auto Correct Options, click the
Smart Tag tab, and ensure that
Label Text with Smart Tags is selected and that your Smart Tag DLL is listed and selected.
If your Smart Tag is not listed, it may not be properly registered.
If the execution of the custom recognizer or action class is the source of the
problem, you can debug a Smart Tag DLL as you would any Visual C++ DLL. Set a
breakpoint in the constructor for the Recognizer class. When you press F5 to
debug the application, a dialog box appears and asks for an executable file for
the debug session. Select either
Winword.exe or
Excel.exe. When Excel or Word starts and loads the Smart Tag, your code
breaks at the breakpoint and you can step through the code for
debugging.
REFERENCES
Documentation for these interfaces, along with
the needed type library that defines them, is provided in the Smart Tag
Software Development Kit (SDK). If you have not already done so, you should
install the Smart Tag SDK before you proceed with the steps to create the
sample Smart Tag. You can obtain the Smart Tag SDK from the Microsoft Office XP
Developer (MOD) CD-ROM, or you can download it from the following Microsoft
Developer Network (MSDN) Web site:
For more information on creating custom Smart Tag
Recognizer/Action DLLs, see the Smart Tag Development Help file that is
included with Smart Tag SDK.
For additional information on digital signatures, click the
article number below to view the article in the Microsoft Knowledge Base:
247257�
INFO: Steps for Signing a .cab File
For information on using CLSIDs to register the
Smart Tag DLL instead of ProgIDs, see the following article in the Microsoft
Knowledge Base:
294422�
BUG: Status Flag Is Not Updated When You Enable or Disable Smart Tags