Writing a word add in

Introduction

Microsoft Word, along with the rest of the Office applications, has an API that can be called by a program to perform operations on the app’s document. If you’ve given this API any thought, you’ve probably considered it something only power users writing VBA scripts would use.

However, that’s not the case: in Word, the powerful API calls the app to perform just about any action the app can perform on its own. Further, you can write an add-in in C# that does everything a regular C# program does. That’s right—the entire .NET Framework is available to your add-in. You end up with a C# program that not only has all of .NET’s functionality, but also Word’s functionality too.

The first question you face when you create a Word add-in is which of two approaches to use: Visual Studio Tools for Office (VSTO) or an add-in that extends the IDTExtensibility2 interface. According to Microsoft, VSTO isn’t for commercial add-ins. (And I’m not sure of when you would want to use it. In fact, when I asked Microsoft P.R. for examples of when you would use VSTO – they could not give me examples either.) However, you should know it is an option. For more information about VSTO, visit MSDN.

In this article, I’ll show you how to create an IDTExtensibility2 Word add-in. To avoid duplicating details the Microsoft Knowledge Base articles explain at great length, I’ll refer you to the articles instead. I’ll also cover the bugs I ran across and the workarounds for them.

First, to create the Word add-in, read Knowledge Base article 302901. Note: as I demonstrate in this article, you don’t create a Microsoft Office System Projects project in Visual Studio. That’s where you go to create a VSTO project.

Now that you have your Word add-in, you can play around with it. However, you’ll soon discover you can’t turn on themes for buttons the way you do for a Windows application. But a solution exists. Knowledge Base article 830033 comes to the rescue. Once again, when you follow the instructions, turning on themes for buttons works perfectly.

Visual Studio performs a bit of magic so it can run your add-in in the debugger. There is a long explanation for this that will appear in Part II of this article.

So create your initial add-in, build it, and install it. Next, go to the Solution Explorer in Visual Studio, right-click on your project, then select Properties. In the Properties dialog, select Configuration Properties | Debugging, and go to the Start Application property. For that property, set the location of WINWORD.exe (for example, it’s C:Program FilesMicrosoft OfficeOFFICE11WINWORD.EXE on my system). Now, you can run or debug from Visual Studio.

I also recommend you never again run the installer on your development machine. Windows seems to get confused by having registry entries for both running the add-in from the debugger and running it as a standard Word add-in. Even if you uninstall the add-in, it appears to cause problems. And if you have to rename your add-in, create a new one with the new name instead, then copy the other files over. Renaming an existing add-in will break because the add-in won’t have any of the registry settings made during the creation process.

Write Your Add-In

Now you’re ready to start writing your add-in. The first step is to look at the documentation for the API so you can see what you can do. You look in the MSDN library. The documentation isn’t there. You try the Word online help. The documentation isn’t there. You search on Microsoft.com, Google, and post in the newsgroups … and the answer is: there is no documentation. Documentation exists for VBA, but not for C#.

It gets worse. The VBA documentation is only available through Word’s online help; the documentation on MSDN is incomplete. And the format for the documentation is likely one you’re not used to, making it hard for you to drill down to the info you need.

You’ll also come across properties that don’t exist. In those cases, you’ll have to call get_Property(). But again, this approach isn’t documented, so you’ll have to try it and see if it works. (Refer to the MSDN article, “Automating Word Using the Word Object Model”.) But wait—there’s more. The .NET API is a COM API designed for VB. So a method that takes two integers, such as Range(int start, int end) is actually declared as Range(ref object start, ref object end). You have to declare two object (not int) variables, assign int values to them, then pass them in. Yet, the int values are not changed by the call and only an int can be passed in.

But wait—there’s even more. I’ve only found this in one place so far but it probably holds elsewhere: there is no Application.NewDocument event (because there is an Application.NewDocument method in the Word API—and C# doesn’t support having an event and a method with the same name). However, you can cast an Application object to an ApplicationClass object, and you can then call ApplicationClass.NewDocument. Problem solved … well, actually, it’s not. The ApplicationClass solution works on some systems, but not on others. (I have no idea why – and could never get a straight answer on this from Microsoft.) But there is a solution. You can also cast the Application object to an ApplicationEvents4_Event object, and you then call the ApplicationEvents4_Event.NewDocument event (ApplicationEvents3_Event.NewDocument in Word 2002). (While this appears to work on all the systems I’ve tested thus far, you might come across systems where it doesn’t work.) So don’t cast objects to ApplicationClass; instead, cast them to ApplicationEvents4_Event objects. And the IntelliSense doesn’t work for the ApplicationEvents4_Event class, so you’ll have to type in the entire line of code, but it will compile and run fine.

Application.WindowSelectionChange is an event I haven’t found a solution for yet. It can always be set, but sometimes it doesn’t fire. And I can’t find any reason for this. I can start Word and it doesn’t fire, but when I exit and immediately restart, it works. It might not work two or three times in a row, but then work ten times in a row. Even when it works, if you enter text or press undo/redo, it doesn’t fire even though it changes the selection. So, it’s not an all selection changes event so much as a some selection changes event.

Enable and Disable Menu Items

Now, say you want to enable or disable menu items, like Word does for Edit, Copy and Edit, and Paste (only enabled if text is selected). An event is fired before the RMB pop-up menu appears. But for the main menu, there is no event before a menu is dropped down. (I know it seems like there must be an event for this, but there isn’t.)

The WindowSelectionChange event is the only method I’ve found to use. However, it’s inefficient because it either fires a lot or doesn’t fire at all, and it doesn’t fire when you enter text (for which there is no event).

So, you’ll need to do two things: first, enable or disable menu items when the selection event fires; and second, in the event handler for menu items that can be enabled or disabled, check when you first enter the event handler, and if the menu items should be disabled, call your menu update method and return. This way, after the user tries to execute that menu item, the menu is correct.

When you create menu objects, you’ll need to keep a couple of things in mind. First, you must store the returned menu objects in a location that will exist for the life of the program. The menus are COM objects, and if no persistent C# object is holding them, they are designated for garbage collection and your events will stop working the next time the garbage collector runs.

Second, the call CommandBarPopup.Controls.Add(MsoControlType.msoControlButton, Type.Missing, Type.Missing, Type.Missing, false) must have false for the final parameter. If this is set to true, as soon as any other add-in sets Application.CustomizationContext to another value, all your menus go away.

Apparently, temporary (the 5th parameter) isn’t the life of the application, but the life of the present CustomizationContext set as the CustomizationContext. If another add-in changes the CustomizationContext, your menu disappears. Given a user can normally have several add-ins, you can never set the 5th parameter to true. The downside is you’re adding your menus to the default template (normal if you don’t change it) permanently. I don’t think you can have menus exist for the life of the application, but not have them added to the template.

Give Users a Template

Another approach is to give users a template to use with your add-in. The template doesn’t have to do anything, but on startup, you look for that template and add your menus only if the template exists. You also add your menus to your template. In essence, you’re using the existence of the template as an Attribute. This is a clean way to have your add-in appear only when you want it to and have it not touch any other part of Word.

Each time your program starts, you need to determine if your menu has already been added (CommandBarControl.Tag is useful for this). If it isn’t there, add it. If it is there, either delete it and then add it, or set your events on the existing items. I delete and add it because over the course of writing the program, the menu items change at times. If you delete and add it, save the location of the menu and add it there. If a user customizes her menu by moving the location of your menu, you don’t want to force it back to the original position the next time Word runs.

When you set the menus, this changes the template, and Word normally asks the user when she exits if she wants to save the changed template. To avoid this, get the value of the default Template.Saved before making the changes, and set Template.Saved to that value after you’re done. If the template was not dirty when you first got the value, it will be set back to the clean value upon completion:

private Template TemplateOn 
{ 
  get
  {
    
    
    Templates tpltColl = ThisApplication.Templates; 

    foreach (Template tpltOn in tpltColl)
      if (tpltOn.Name == "MY_TEMPLATE.DOT")
        return tpltOn;
      return ThisApplication.NormalTemplate;
  }
}

...


Template thisTemplate = TemplateOn;


bool clean = thisTemplate.Saved;
ThisApplication.CustomizationContext = thisTemplate;
...

thisTemplate.Saved = clean;

...

One warning: don’t look at the Template.Dirty value using the debugger. The act of looking at it sets it to dirty. This is a true Heisenbug. (The Heisenberg theory is that the act of observing a particle affects the particle.)

And even after you take all these measures, there is one more issue. Sometimes, when you close all documents so you have Word running but no document open, the menu events still fire fine, but calls to CommandBarControl.Enabled throw exceptions. Once you create a new document, the problem usually goes away—unless you have two instances of Word, close the first one, then bring up a document in the second one. Then the problem remains. The solution to this is covered in Part II.

Find Text Within Range

Now for the last bug (in this article, at least, and one I’ve only seen when searching for ranges within table cells). You need to find some text within a certain range. So you set the range and call Range.Find.Execute(). However, this call sometimes returns matching text found outside the range you selected. It can find text before or after the range and return it. If it finds it before, it doesn’t mean the text doesn’t exist in your range, just that Range.Find.Execute() hasn’t gotten here yet. (And yes, it is only supposed to return text inside the passed range – but it will return text outside the range.)

To fix this, you’ll need to set range.Find.Wrap = WdFindWrap.wdFindContinue and keep calling it until you get a find in your range, or it goes past the end of your range. However, there is another problem with this approach. The Find can first return text after your range even though there is text inside your range. In this case, you need to cycle through the entire document until it wraps back to your range to find the text. While this can burn a lot of clock cycles (think of a 200-page document where you have to walk all the way around), it only happens in cases where this bug occurs and, fortunately, those cases are rare.

public Range Find (int startOffset, int endOffset, bool forward, string text) 
{
    
    
    
    int rangeStart = Math.Max(0, startOffset - 1);

    
    int rangeEnd = Math.Min(endOffset + 1, ThisDocument.Content.End);

    object start = rangeStart;
    object end = rangeEnd;
    Range range = ThisDocument.Range (ref start, ref end);
    range.Find.ClearFormatting();
    range.Find.Forward = forward;
    range.Find.Text = text;
    range.Find.Wrap = WdFindWrap.wdFindStop;

    object missingValue = Type.Missing;
    range.Find.Execute(ref missingValue, ref missingValue, 
                ref missingValue, ref missingValue, ref missingValue, 
                ref missingValue, ref missingValue, ref missingValue, 
                ref missingValue, ref missingValue, ref missingValue, 
                ref missingValue, ref missingValue, ref missingValue, 
                ref missingValue);

    
    if (range.Find.Found && (startOffset <= range.Start) 
                             && (range.End <= rangeEnd))
        return range;

    
    
    
    
    
    start = startOffset;
    end = rangeEnd;
    range = ThisDocument.Range (ref start, ref end);
    string docText = range.Text;

    if (docText == null)
        return null;

    int ind = forward ? docText.IndexOf(text) : docText.LastIndexOf(text);
    if (ind == -1)
        return null;

    
    

    int _start = forward ? startOffset : rangeEnd - text.Length;
    while ((startOffset <= _start) && (_start < rangeEnd))
    {
        start = _start;
        end = rangeEnd;
        range = ThisDocument.Range (ref start, ref end);
        docText = range.Text;

        
        

        if ((docText != null) && docText.StartsWith(text))
        {
            
            int endPos = range.Start + text.Length;

            while (endPos < endOffset) 
            {
                range.End = endPos;
                docText = range.Text;
                if (docText == text)
                    break;
                endPos ++;
            }
            return range;
        }
        _start += forward ? 1 : -1;
    }
    return null;
}

All in all, I’d say you should view the Word .NET façade as fragile. Aside from the Find bug, these problems are probably due to the façade and not Word itself. But keep in mind, you might have to experiment to find a way to talk to Word in a way that is solid.

Beyond the bugs I’ve discussed, you’ll want to keep these issues in mind for your add-in. If you need to tie data to the document, use Document.Variables. Two notes about this: first, Document.Variable only accepts strings (I uuencode my data); and second, it has a size limit between 64,000 and 65,000 bytes. Also, if you serialize your data, be aware that .NET sometimes has trouble finding the assembly of your add-in when deserializing the objects. This is a .NET issue, not a Word one.

If you’re going to call the Help methods from your add-in, they require a Control object for the parent window. Given Word isn’t a .NET program, you have no way to convert an hwnd (Word’s main window) to a Control. So for Help, you’ll need to pass a null parent window.

Occasionally (in my case, it’s about once a week), when you’re building the COM add-in shim, you’ll get the error:

stdafx.h(48): error C3506: there is no typelib registered 
            for LIBID '{AC0714F2-3D04-11D1-AE7D-00A0C90F26F4}'

When you get this, go to C:Program FilesCommonFilesDesigner and run the regsvr32 msaddndr.dll command. I have no idea why this happens (sometimes it occurs between two builds when all I’ve done is edit a CS file), but it’s easy to fix.

The add-in you create is a COM add-in, not an automation add-in. To see it, add the tool menu item called curiously enough “COM Add-Ins” to the Tools menu.

Exit All Instances of Word

One issue that can sideswipe you in a myriad of ways is that if you use Outlook, it uses Word for composing messages. Word loads add-ins when the first instance starts, and uses that set of add-ins for all other instances. The only way to reload instances is to exit all instances of Word.

Word also locks the files it’s using, such as the DLL files for add-ins. Again, the executable files for your add-in are locked. If you run the debugger, it appears to lock the PDB files too. So if things become strange—builds fail, files are locked, new changes aren’t executing—make sure you’ve exited Outlook as well as all instances of Word. This isn’t a bug; it’s a correct operation, but it’s not obvious and can be frustrating to figure out.

Winword.exe can also run as an orphan process at times. No copy of Word will be on the screen; Outlook isn’t running; somehow an instance of Word never exited. Again, in this case, you can’t build until you kill that orphan process.

You’ll also come across the standard Visual Studio issue where occasionally you’ll need to exit and restart Visual Studio. This isn’t a Word issue (my work colleagues have the same problem when they’re writing C# Windows application code), and if all else fails, reboot.

Remember that Outlook starts Word without a document. Make sure your add-in handles this. Also keep in mind that because the API was originally from VB, all arrays are 1-based. This is a major pain to remember, but Microsoft does need to keep the API consistent across all .NET languages, and VB was used first for the API, so it wins.

Another issue to watch for is that Word doesn’t display exceptions thrown in your add-in. You can catch exceptions and act on them, but if you don’t catch the exception, it’s never displayed. You have to set your debugger to break on managed exceptions so you can see them, or put a try/catch in every event handler.

An IDTExtensibility2 add-in can run on Word 2002 (VSTO is limited to Word 2003). However, MSDN’s article on installing the Office 10 Primary Interop Assemblies, or PIAs, (see Additional Resources) is wrong or incomplete. You get the PIAs added to the GAC, but you can’t find them with add references – and you cannot select a file in the GAC. So copy the files out of the GAC, add those copied files, and it will then point at the PIAs in the GAC.

What about Word 2000? Supposedly, PIAs can be built for it. I’m still trying to figure this out, and if I do figure it out, look for it to be covered in Part II or Part III. But Microsoft does not provide Word 2000 PIAs.

Okay, you’ve got your add-in working; just a few more minutes and you can ship it to the world—or so it would seem. First comes the fact that if you Authenticode sign your C# DLL, it doesn’t matter to Word. You have to create a COM shim DLL that then calls your C# DLL. The MSDN article “Using the COM Add-in Shim Solution to Deploy Managed COM Add-ins in Office XP” covers this. Note: this does not work for VSTO applications. (To vent for a second: WHY, Why, why didn’t Microsoft set it up so Word accepts signed .NET DLLs as a signed Word add-in?) Follow the article very closely. In a number of cases, if your changes to the shim are just slightly wrong, the shim won’t work. And figuring out what is wrong is not easy. In fact, the only way to fix problems is to keep trying different approaches. There is a lot to cover about the shim add-in, and it is covered in Part II.

IMHO, this is the one place Microsoft really blew it. The shim solution adds a lot of work for every developer (as opposed to Microsoft doing it once up front), makes the whole solution more complex, and there is no way to debug problems with it.

Once you have the shim working, you’ll need to strong name all .NET DLLs, then Authenticode sign all DLLs. This is necessary to run on systems where security is set to High/do not trust add-ins.

There is one final issue you must address for your code to install correctly. Your initial setup program has registry settings in the HKCU for your add-in. These need to be deleted as you are not setting up your .NET code to be the add-in, but the shim instead. In your shim, the ConnectProxy.rgs file sets its add-in registry entries in the HKCU. If you always want to install your add-in for the current user only, this is fine. But if you want to give your users the choice of installing for everyone or installing for just me (and for 98% of you, you should), place the registry settings in the setup program under User/Machine Hive and delete them from ConnectProxk.rgs. (There will be a longer explanation of this in Part II.)

The biggest problem the Word add-in C# API suffers from is neglect, and its issues reside almost entirely in the .NET/C# wrapper and its associated features (such as documentation, signing security, and the shim layer). This means, once you figure out how to work around these issues, you’re left with a solid, powerful engine.

Additional Resources:

  • Microsoft Knowledge Base article 302901: “How To Build an Office COM Add-in by Using Visual C# .NET”.
  • Microsoft Knowledge Base article 830033: “How to apply Windows SP themes to Office COM add-ins”.
  • MSDN Library: “Automating Word Using the Word Object Model”.
  • MSDN Library: “Using the COM Add-in Shim Solution to Deploy Managed COM Add-ins in Office XP” by Misha Shneerson and Siew-Moi Khor.
  • MSDN Library: “Working with the Office XP Primary Interop Assemblies” by Paul Cornell.

Environment: VC++/ATL COM

Introduction

After having written about developing Office COM addins in an earlier article, I get a lot of mail from people trying to write Word addins. Through this article we will discuss common development issues regarding Word addins in general. First, we learn how to build a simple WORD 2000 ATL COM addin. Later in the article, we will get down to Word’s VBA macro side of things, and write an addin that works for all versions of Word, independent of COM addin support.

I’m assuming you have read my previous article on Office addins and taken a look at the sample project. There are a lot of issues in addin development, such as adding custom menu/toolbars, handling events, property pages, and so forth that are common for all Office addins, including Word. These have already been discussed in that article. Here we are going to write a Word 2000 COM addin, to begin with. Later, we’ll delve into Office and VBA in general, and with respect to with C++ addins.

Writing a Word 2000 Addin

To begin, create a new ATL COM Appwizard-generated DLL project called WordAddin. Next, insert an ATL Simple Object called Addin, as in the previous article. Next, use the Implement Interface ATL Wizard to implement _IDTExtensibility2. This, as you know, is the at the heart of COM addin support for all Office applications. Next, to program against Word, we need to import Word’s typelib. That means, we need to import three interdependent typelibs. In your project’s stdafx.h, add the following code:


#import “C:\Program Files\Microsoft Office\Office\mso9.dll”
rename_namespace(“Office2000”)
using namespace Office2000;

#import “C:\Program Files\Common Files\Microsoft Shared\VBA
\VBA6\VBE6EXT.olb” rename_namespace(“VBE6”)
using namespace VBE6;

In your project’s Addin.h file, towards the top, add:


#import “C:\Program Files\Microsoft Office\Office\MSWORD9.olb”
rename(“ExitWindows”,”MyExitWindows”),named_guids,
rename_namespace(“MSWord”)
using namespace MSWord;

Make sure that you change the path to point to the correct locations of the files for your system.

The different locations for the #import statements are needed for the compiler to recognize everything, generate the correct set of wrappers, and compile and link correctly. Moving on, to register your addin with Word 2000, add the following code to the Addin.rgs Registry script file (under FileView->Resource Files) and add the following to the end of the file.


HKCU
{
Software
{
Microsoft
{
Office
{
Word
{
Addins
{
‘WordAddin.Addin’
{
val FriendlyName = s ‘WORD Custom Addin’
val Description = s ‘Word Custom Addin’
val LoadBehavior = d ‘00000003’
val CommandLineSafe = d ‘00000001’
}
}
}
}
}
}
}

Yes, we’d like our addin to be called ‘Word Custom Addin’ (‘I love CodeGuru.com Addin’ would have been too blatant!), and loaded when Word starts up. So, what else is new?

If everything has gone right, you now have a working Word addin to which you should add your own implementation code. By now, you already know how to add buttons and menu items in your addin, property sheets, and so forth; all that has been discussed in the previous article holds true. The only thing different is the Word Object Model, and from the code in the Outlook addin example, is that there is no ActiveExplorer object in Word. You should get the CommandBars interface directly from Application, the topmost in the object model.

Handling Events

Probably in your addin, you’d also be interested in handling some of Word’s events. A case in point is the Application object’s DocumentOpen event, with DISPID=4, which is handled here. Word has a complex object model and you will find a host of other such events. If you use the good old OLE/COM Object Viewer to view msword9.olb, you’d find an IDL like this:


….

[id(0x00000003), helpcontext(0x00061a83)]
void DocumentChange();
[id(0x00000004), helpcontext(0x00061a84)]
void DocumentOpen([in] Document* Doc);

……

As before, we will use ATL’s IDispEventSimpleImpl<> template class to implement our sink. For brevity, only the changes necessary to the earlier code have been mentioned.


extern _ATL_FUNC_INFO DocumentOpenInfo;

class ATL_NO_VTABLE CAddin :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CAddin, &CLSID_Addin>,
public ISupportErrorInfo,
public IDispatchImpl<IAddin, &IID_IAddin,
&LIBID_WORDADDINLib>,
public IDispatchImpl<_IDTExtensibility2,
&IID__IDTExtensibility2,
&LIBID_AddInDesignerObjects>,
public IDispEventSimpleImpl<1,CAddin,
&__uuidof(MSWord::ApplicationEvents2)>

{
public:
….
….

void __stdcall DocumentOpen(IDispatchPtr ptr)
{
CComQIPtr<_Document> spDoc(ptr);
ATLASSERT(spDoc);
….
….

}

BEGIN_SINK_MAP(CAddin)
SINK_ENTRY_INFO(1,__uuidof(MSWord::ApplicationEvents2),4,
DocumentOpen,&DocumentOpenInfo)
END_SINK_MAP()

private:
CComPtr<MSWord::_Application> m_spApp;
};

DocumentOpenInfo is defined at the top of CAddin.cpp as:


_ATL_FUNC_INFO DocumentOpenInfo = {CC_STDCALL,VT_EMPTY,1,
{VT_DISPATCH|VT_BYREF}};

All that remains for us to do is to add the code to set up and break down the connection. Using the ATL template class, therefore, all we have to do is call DispEventAdvise() and DispEventUnadvise(). Our CAddin’s OnConnection() and OnDisconnection(), needless to say, is the right place to do this.


CComQIPtr<_Application> spApp(Application);
ATLASSERT(spApp);
m_spApp = spApp;
HRESULT hr = DispEventAdvise(m_spApp);
if(FAILED(hr))
return hr;

and in OnDisconnection(),


DispEventUnadvise(m_spApp);
m_spApp = NULL;

Now you have a working Word COM addin template, proudly under your belt. To this, you should add your own implementation, error-handling routines, and so on. Moving on to something different, let’s explore VBA side of WORD. Next, I’ll discuss a very specific scenario where I use a mix of C++ code and VBA macros in my addin.

Macros and the Visual Basic Editor

I was working on a project where the COM addin model of things suited us fine, except that the client had a large number of Word97 users, and Word97 has no support for COM addins, specifically the IDTExtensibility2 interface. Basically, in my addin, I needed to add a few custom menu items and buttons, clicking which usually meant displaying a couple of dialogs. Now, a COM addin would be perfect for Word 2000 users. But was there some way we could make the addin Word97 compatible?

This leads us to Visual Basic for Applications (VBA). Most Office developers know that MS Office components and applications support a rich scripting object model and a scripting interface known as VBA. Before Office version 4, each application in the suite was very distinct. For developers, it wasn’t easy to create integrated solutions using multiple Office applications, because each application had a unique programming environment.

This problem was addressed through MS Visual Basic for Applications (VBA), which made its debut in Office 95—albeit in a few applications. By Office 97, every app in the suite supported standard VBA interfaces. The set of functions that a VBA routine, or macro, can use to control its host application is the same set of functions that the OLE Automation client can use to control the application externally, regardless of the programming language for the controller. Word 97 includes Visual Basic 5.0, a sophisticated development environment that is shared across Office applications: Word, Excel, PowerPoint, and Access.

In Word, VBA and Visual Basic go beyond being merely a macro language—it is a full-featured programming development environment. The Visual Basic Editor (VBE) uses the familiar programming interface of Microsoft Visual Basic 4.0 as a base for creating and editing script code. Through VBA, Word supported everything from macros to addins to document templates. So, what is a document template? A document template (*.dot) is simply a document with macro code (script) embedded in it. In Word, macros are persisted in documents and templates as Visual Basic modules. Although macros are ordinarily stored in the user’s default template, Normal.dot, Word allows you to store and use macros in any document or template. Moreover, any such document template in the Office installation’s Startup folder, would be autorun. Additional templates and macros can be loaded by using the Templates & Add-ins or Macros dialog box in the Tools menu. Macros and document templates (also supported for WordPerfect) can also be shared across users in a Workgroup.

What is also interesting is that MS Word97 onwards also supports a group of global macros that gets invoked with the host application. These AutoXXX macros, such as AutoExec(), AutoNew(), AutoOpen(), AutoClose(), and AutoExit() get executed along with the host application, just as their names suggest. This sounds most useful and it is.

How? If we were to create a document template, handle such Auto macros in it, and then create and destroy our addin (which is written as an ActiveX server) through VBA macros—then we should be on the home stretch. This is what IDTExtensibility2 does for COM addins—primarily a way to connect and disconnect to the topmost Application object. So, how can we substitute something like this in our Word97 addin? Through document templates. Document templates, by themselves, can be described as VBA addins. Every document template is set up to interact with the Document object associated with the project through the ThisDocument property. We must also remember that all macros implicitly reference the Application object.

Back to the task at hand, suppose we were to write a document template and in it, add code to our AutoExec() and AutoExit() handlers. In the handlers, we create and release our addin COM class object and subsequently call its methods. Moreoever, our ATL COM class should implement two methods, Init() and Uninit(), through which the Application object is set in the C++ addin. Our macros ought to look like this:


Dim o as Application
Dim obj as Object

Sub AutoExec()
Set obj = CreateObject(“Word97Addin.Addin”)
Set o = ThisDocument.Application
obj.Init o
End Sub

Sub AutoExit()
Set o = ThisDocument.Application
obj.Uninit o
Set obj = Nothing
Set o = Nothing
End Sub

We can choose to automatically load and unload our template by placing it in the Office Startup folder. Similarly, you can handle the AutoClose(), AutoNew(), and AutoOpen() macros, which are more document related. For the moment, because I’m handling all button/menu creation and events in my C++ COM object, the communication is unidirectional (i.e. macro->addin), but bidirectional communication is not too difficult. Suppose you have a macro called “NewMacro” defined in your template; one way to trigger this macro from a C++ addin is through Application::Run() or Application::RunOld(), depending on whether you need to pass any parameters. What is interesting is that you can also trigger the macro through a button click, by setting the OnAction property of the CommandBarButton object of your button or menuitem.



CComQIPtr < Office::CommandBarButton> spCmdButton(spNewBar);
ATLASSERT(spCmdButton);
spCmdButton->PutCaption(_T(“Button”));
spCmdButton->put_OnAction(OLESTR(“NewMacro”));

spCmdButton->PutVisible(VARIANT_TRUE);

This is just what I did for my project. We have a single solution, consisting of a DLL and a DOT file, that runs across all versions of Word—from 97 to XP, exposing and encapsulating most of its functionality through compiled C++ code. Unfortunately, this VBA support brings up the security issue, and in later versions of Office, such as XP, the user can determine the security level in the macro execution context. Although this was a non-issue with us, you need to be aware.

All of what we have learned so far has been espoused in the accompanying VC++ 6.0 Universal Addin project, which I will briefly describe next.

Universal Addin

Universal Addin is a simple ATL/COM AppWizard generated DLL project, to which I inserted an ATL COM IDispatch-based Simple Object called Addin. While the name sounds very pretentious, ‘Universal’ refers to its ability to run across all versions of Word.

What lends it such universality is the addin.dot Word document template that is a part of the addin, and its AutoXXX macros. Our Addin class has two member methods, Init() and Uninit(), that take a single IDispatch* to the Application object. Our Uninit() implementation is very simple and we could have done without the IDispatchPtr parameter; might not be so for your implementation. In the project, we add a single button through the addin, clicking which triggers a VBA named macro, that in turn calls a method of our COM class. By all means, you can connect to CommandBarButtonEvents and write C++ code to handle button click, as done previously.

Out aim was to write an addin for Word, bypassing the the COM addin architecture and IDTExtensibility2. That concluded, all that remains for me to say is that whatever you can do with VBA, you can do from C++ and vice versa—the magic is in OLEAutomation.

And of course, happy coding!

Acknowledgements

Everything I know about Office development, I learned at MSDN. You, too, can find a horde of technical articles and short KB code snippets related to everything from Office development to COM and OLE Automation. MSDN rules—as we all know.

Thanks to Peter Hauptmann (a.k.a. Peterchen) for his comments and suggestions.

Downloads

Download demo project – 21 Kb

Introduction

Microsoft Word, along with the rest of the Office applications, has an API that can be called by a program to perform operations on the app’s document. If you’ve given this API any thought, you’ve probably considered it something only power users writing VBA scripts would use.

However, that’s not the case: in Word, the powerful API calls the app to perform just about any action the app can perform on its own. Further, you can write an add-in in C# that does everything a regular C# program does. That’s right—the entire .NET Framework is available to your add-in. You end up with a C# program that not only has all of .NET’s functionality, but also Word’s functionality too.

The first question you face when you create a Word add-in is which of two approaches to use: Visual Studio Tools for Office (VSTO) or an add-in that extends the IDTExtensibility2 interface. According to Microsoft, VSTO isn’t for commercial add-ins. (And I’m not sure of when you would want to use it. In fact, when I asked Microsoft P.R. for examples of when you would use VSTO – they could not give me examples either.) However, you should know it is an option. For more information about VSTO, visit MSDN.

In this article, I’ll show you how to create an IDTExtensibility2 Word add-in. To avoid duplicating details the Microsoft Knowledge Base articles explain at great length, I’ll refer you to the articles instead. I’ll also cover the bugs I ran across and the workarounds for them.

First, to create the Word add-in, read Knowledge Base article 302901. Note: as I demonstrate in this article, you don’t create a Microsoft Office System Projects project in Visual Studio. That’s where you go to create a VSTO project.

Now that you have your Word add-in, you can play around with it. However, you’ll soon discover you can’t turn on themes for buttons the way you do for a Windows application. But a solution exists. Knowledge Base article 830033 comes to the rescue. Once again, when you follow the instructions, turning on themes for buttons works perfectly.

Visual Studio performs a bit of magic so it can run your add-in in the debugger. There is a long explanation for this that will appear in Part II of this article.

So create your initial add-in, build it, and install it. Next, go to the Solution Explorer in Visual Studio, right-click on your project, then select Properties. In the Properties dialog, select Configuration Properties | Debugging, and go to the Start Application property. For that property, set the location of WINWORD.exe (for example, it’s C:Program FilesMicrosoft OfficeOFFICE11WINWORD.EXE on my system). Now, you can run or debug from Visual Studio.

I also recommend you never again run the installer on your development machine. Windows seems to get confused by having registry entries for both running the add-in from the debugger and running it as a standard Word add-in. Even if you uninstall the add-in, it appears to cause problems. And if you have to rename your add-in, create a new one with the new name instead, then copy the other files over. Renaming an existing add-in will break because the add-in won’t have any of the registry settings made during the creation process.

Write Your Add-In

Now you’re ready to start writing your add-in. The first step is to look at the documentation for the API so you can see what you can do. You look in the MSDN library. The documentation isn’t there. You try the Word online help. The documentation isn’t there. You search on Microsoft.com, Google, and post in the newsgroups … and the answer is: there is no documentation. Documentation exists for VBA, but not for C#.

It gets worse. The VBA documentation is only available through Word’s online help; the documentation on MSDN is incomplete. And the format for the documentation is likely one you’re not used to, making it hard for you to drill down to the info you need.

You’ll also come across properties that don’t exist. In those cases, you’ll have to call get_Property(). But again, this approach isn’t documented, so you’ll have to try it and see if it works. (Refer to the MSDN article, “Automating Word Using the Word Object Model”.) But wait—there’s more. The .NET API is a COM API designed for VB. So a method that takes two integers, such as Range(int start, int end) is actually declared as Range(ref object start, ref object end). You have to declare two object (not int) variables, assign int values to them, then pass them in. Yet, the int values are not changed by the call and only an int can be passed in.

But wait—there’s even more. I’ve only found this in one place so far but it probably holds elsewhere: there is no Application.NewDocument event (because there is an Application.NewDocument method in the Word API—and C# doesn’t support having an event and a method with the same name). However, you can cast an Application object to an ApplicationClass object, and you can then call ApplicationClass.NewDocument. Problem solved … well, actually, it’s not. The ApplicationClass solution works on some systems, but not on others. (I have no idea why – and could never get a straight answer on this from Microsoft.) But there is a solution. You can also cast the Application object to an ApplicationEvents4_Event object, and you then call the ApplicationEvents4_Event.NewDocument event (ApplicationEvents3_Event.NewDocument in Word 2002). (While this appears to work on all the systems I’ve tested thus far, you might come across systems where it doesn’t work.) So don’t cast objects to ApplicationClass; instead, cast them to ApplicationEvents4_Event objects. And the IntelliSense doesn’t work for the ApplicationEvents4_Event class, so you’ll have to type in the entire line of code, but it will compile and run fine.

Application.WindowSelectionChange is an event I haven’t found a solution for yet. It can always be set, but sometimes it doesn’t fire. And I can’t find any reason for this. I can start Word and it doesn’t fire, but when I exit and immediately restart, it works. It might not work two or three times in a row, but then work ten times in a row. Even when it works, if you enter text or press undo/redo, it doesn’t fire even though it changes the selection. So, it’s not an all selection changes event so much as a some selection changes event.

Enable and Disable Menu Items

Now, say you want to enable or disable menu items, like Word does for Edit, Copy and Edit, and Paste (only enabled if text is selected). An event is fired before the RMB pop-up menu appears. But for the main menu, there is no event before a menu is dropped down. (I know it seems like there must be an event for this, but there isn’t.)

The WindowSelectionChange event is the only method I’ve found to use. However, it’s inefficient because it either fires a lot or doesn’t fire at all, and it doesn’t fire when you enter text (for which there is no event).

So, you’ll need to do two things: first, enable or disable menu items when the selection event fires; and second, in the event handler for menu items that can be enabled or disabled, check when you first enter the event handler, and if the menu items should be disabled, call your menu update method and return. This way, after the user tries to execute that menu item, the menu is correct.

When you create menu objects, you’ll need to keep a couple of things in mind. First, you must store the returned menu objects in a location that will exist for the life of the program. The menus are COM objects, and if no persistent C# object is holding them, they are designated for garbage collection and your events will stop working the next time the garbage collector runs.

Second, the call CommandBarPopup.Controls.Add(MsoControlType.msoControlButton, Type.Missing, Type.Missing, Type.Missing, false) must have false for the final parameter. If this is set to true, as soon as any other add-in sets Application.CustomizationContext to another value, all your menus go away.

Apparently, temporary (the 5th parameter) isn’t the life of the application, but the life of the present CustomizationContext set as the CustomizationContext. If another add-in changes the CustomizationContext, your menu disappears. Given a user can normally have several add-ins, you can never set the 5th parameter to true. The downside is you’re adding your menus to the default template (normal if you don’t change it) permanently. I don’t think you can have menus exist for the life of the application, but not have them added to the template.

Give Users a Template

Another approach is to give users a template to use with your add-in. The template doesn’t have to do anything, but on startup, you look for that template and add your menus only if the template exists. You also add your menus to your template. In essence, you’re using the existence of the template as an Attribute. This is a clean way to have your add-in appear only when you want it to and have it not touch any other part of Word.

Each time your program starts, you need to determine if your menu has already been added (CommandBarControl.Tag is useful for this). If it isn’t there, add it. If it is there, either delete it and then add it, or set your events on the existing items. I delete and add it because over the course of writing the program, the menu items change at times. If you delete and add it, save the location of the menu and add it there. If a user customizes her menu by moving the location of your menu, you don’t want to force it back to the original position the next time Word runs.

When you set the menus, this changes the template, and Word normally asks the user when she exits if she wants to save the changed template. To avoid this, get the value of the default Template.Saved before making the changes, and set Template.Saved to that value after you’re done. If the template was not dirty when you first got the value, it will be set back to the clean value upon completion:

private Template TemplateOn 
{ 
  get
  {
    
    
    Templates tpltColl = ThisApplication.Templates; 

    foreach (Template tpltOn in tpltColl)
      if (tpltOn.Name == "MY_TEMPLATE.DOT")
        return tpltOn;
      return ThisApplication.NormalTemplate;
  }
}

...


Template thisTemplate = TemplateOn;


bool clean = thisTemplate.Saved;
ThisApplication.CustomizationContext = thisTemplate;
...

thisTemplate.Saved = clean;

...

One warning: don’t look at the Template.Dirty value using the debugger. The act of looking at it sets it to dirty. This is a true Heisenbug. (The Heisenberg theory is that the act of observing a particle affects the particle.)

And even after you take all these measures, there is one more issue. Sometimes, when you close all documents so you have Word running but no document open, the menu events still fire fine, but calls to CommandBarControl.Enabled throw exceptions. Once you create a new document, the problem usually goes away—unless you have two instances of Word, close the first one, then bring up a document in the second one. Then the problem remains. The solution to this is covered in Part II.

Find Text Within Range

Now for the last bug (in this article, at least, and one I’ve only seen when searching for ranges within table cells). You need to find some text within a certain range. So you set the range and call Range.Find.Execute(). However, this call sometimes returns matching text found outside the range you selected. It can find text before or after the range and return it. If it finds it before, it doesn’t mean the text doesn’t exist in your range, just that Range.Find.Execute() hasn’t gotten here yet. (And yes, it is only supposed to return text inside the passed range – but it will return text outside the range.)

To fix this, you’ll need to set range.Find.Wrap = WdFindWrap.wdFindContinue and keep calling it until you get a find in your range, or it goes past the end of your range. However, there is another problem with this approach. The Find can first return text after your range even though there is text inside your range. In this case, you need to cycle through the entire document until it wraps back to your range to find the text. While this can burn a lot of clock cycles (think of a 200-page document where you have to walk all the way around), it only happens in cases where this bug occurs and, fortunately, those cases are rare.









public Range Find (int startOffset, int endOffset, bool forward, string text) 
{
    
    
    
    int rangeStart = Math.Max(0, startOffset - 1);

    
    int rangeEnd = Math.Min(endOffset + 1, ThisDocument.Content.End);

    object start = rangeStart;
    object end = rangeEnd;
    Range range = ThisDocument.Range (ref start, ref end);
    range.Find.ClearFormatting();
    range.Find.Forward = forward;
    range.Find.Text = text;
    range.Find.Wrap = WdFindWrap.wdFindStop;

    object missingValue = Type.Missing;
    range.Find.Execute(ref missingValue, ref missingValue, 
                ref missingValue, ref missingValue, ref missingValue, 
                ref missingValue, ref missingValue, ref missingValue, 
                ref missingValue, ref missingValue, ref missingValue, 
                ref missingValue, ref missingValue, ref missingValue, 
                ref missingValue);

    
    if (range.Find.Found && (startOffset <= range.Start) 
                             && (range.End <= rangeEnd))
        return range;

    
    
    
    
    
    start = startOffset;
    end = rangeEnd;
    range = ThisDocument.Range (ref start, ref end);
    string docText = range.Text;

    if (docText == null)
        return null;

    int ind = forward ? docText.IndexOf(text) : docText.LastIndexOf(text);
    if (ind == -1)
        return null;

    
    

    int _start = forward ? startOffset : rangeEnd - text.Length;
    while ((startOffset <= _start) && (_start < rangeEnd))
    {
        start = _start;
        end = rangeEnd;
        range = ThisDocument.Range (ref start, ref end);
        docText = range.Text;

        
        

        if ((docText != null) && docText.StartsWith(text))
        {
            
            int endPos = range.Start + text.Length;

            while (endPos < endOffset) 
            {
                range.End = endPos;
                docText = range.Text;
                if (docText == text)
                    break;
                endPos ++;
            }
            return range;
        }
        _start += forward ? 1 : -1;
    }
    return null;
}

All in all, I’d say you should view the Word .NET faзade as fragile. Aside from the Find bug, these problems are probably due to the faзade and not Word itself. But keep in mind, you might have to experiment to find a way to talk to Word in a way that is solid.

Beyond the bugs I’ve discussed, you’ll want to keep these issues in mind for your add-in. If you need to tie data to the document, use Document.Variables. Two notes about this: first, Document.Variable only accepts strings (I uuencode my data); and second, it has a size limit between 64,000 and 65,000 bytes. Also, if you serialize your data, be aware that .NET sometimes has trouble finding the assembly of your add-in when deserializing the objects. This is a .NET issue, not a Word one.

If you’re going to call the Help methods from your add-in, they require a Control object for the parent window. Given Word isn’t a .NET program, you have no way to convert an hwnd (Word’s main window) to a Control. So for Help, you’ll need to pass a null parent window.

Occasionally (in my case, it’s about once a week), when you’re building the COM add-in shim, you’ll get the error:

stdafx.h(48): error C3506: there is no typelib registered 
            for LIBID '{AC0714F2-3D04-11D1-AE7D-00A0C90F26F4}'

When you get this, go to C:Program FilesCommonFilesDesigner and run the regsvr32 msaddndr.dll command. I have no idea why this happens (sometimes it occurs between two builds when all I’ve done is edit a CS file), but it’s easy to fix.

The add-in you create is a COM add-in, not an automation add-in. To see it, add the tool menu item called curiously enough “COM Add-Ins” to the Tools menu.

Exit All Instances of Word

One issue that can sideswipe you in a myriad of ways is that if you use Outlook, it uses Word for composing messages. Word loads add-ins when the first instance starts, and uses that set of add-ins for all other instances. The only way to reload instances is to exit all instances of Word.

Word also locks the files it’s using, such as the DLL files for add-ins. Again, the executable files for your add-in are locked. If you run the debugger, it appears to lock the PDB files too. So if things become strange—builds fail, files are locked, new changes aren’t executing—make sure you’ve exited Outlook as well as all instances of Word. This isn’t a bug; it’s a correct operation, but it’s not obvious and can be frustrating to figure out.

Winword.exe can also run as an orphan process at times. No copy of Word will be on the screen; Outlook isn’t running; somehow an instance of Word never exited. Again, in this case, you can’t build until you kill that orphan process.

You’ll also come across the standard Visual Studio issue where occasionally you’ll need to exit and restart Visual Studio. This isn’t a Word issue (my work colleagues have the same problem when they’re writing C# Windows application code), and if all else fails, reboot.

Remember that Outlook starts Word without a document. Make sure your add-in handles this. Also keep in mind that because the API was originally from VB, all arrays are 1-based. This is a major pain to remember, but Microsoft does need to keep the API consistent across all .NET languages, and VB was used first for the API, so it wins.

Another issue to watch for is that Word doesn’t display exceptions thrown in your add-in. You can catch exceptions and act on them, but if you don’t catch the exception, it’s never displayed. You have to set your debugger to break on managed exceptions so you can see them, or put a try/catch in every event handler.

An IDTExtensibility2 add-in can run on Word 2002 (VSTO is limited to Word 2003). However, MSDN’s article on installing the Office 10 Primary Interop Assemblies, or PIAs, (see Additional Resources) is wrong or incomplete. You get the PIAs added to the GAC, but you can’t find them with add references – and you cannot select a file in the GAC. So copy the files out of the GAC, add those copied files, and it will then point at the PIAs in the GAC.

What about Word 2000? Supposedly, PIAs can be built for it. I’m still trying to figure this out, and if I do figure it out, look for it to be covered in Part II or Part III. But Microsoft does not provide Word 2000 PIAs.

Okay, you’ve got your add-in working; just a few more minutes and you can ship it to the world—or so it would seem. First comes the fact that if you Authenticode sign your C# DLL, it doesn’t matter to Word. You have to create a COM shim DLL that then calls your C# DLL. The MSDN article “Using the COM Add-in Shim Solution to Deploy Managed COM Add-ins in Office XP” covers this. Note: this does not work for VSTO applications. (To vent for a second: WHY, Why, why didn’t Microsoft set it up so Word accepts signed .NET DLLs as a signed Word add-in?) Follow the article very closely. In a number of cases, if your changes to the shim are just slightly wrong, the shim won’t work. And figuring out what is wrong is not easy. In fact, the only way to fix problems is to keep trying different approaches. There is a lot to cover about the shim add-in, and it is covered in Part II.

IMHO, this is the one place Microsoft really blew it. The shim solution adds a lot of work for every developer (as opposed to Microsoft doing it once up front), makes the whole solution more complex, and there is no way to debug problems with it.

Once you have the shim working, you’ll need to strong name all .NET DLLs, then Authenticode sign all DLLs. This is necessary to run on systems where security is set to High/do not trust add-ins.

There is one final issue you must address for your code to install correctly. Your initial setup program has registry settings in the HKCU for your add-in. These need to be deleted as you are not setting up your .NET code to be the add-in, but the shim instead. In your shim, the ConnectProxy.rgs file sets its add-in registry entries in the HKCU. If you always want to install your add-in for the current user only, this is fine. But if you want to give your users the choice of installing for everyone or installing for just me (and for 98% of you, you should), place the registry settings in the setup program under User/Machine Hive and delete them from ConnectProxk.rgs. (There will be a longer explanation of this in Part II.)

The biggest problem the Word add-in C# API suffers from is neglect, and its issues reside almost entirely in the .NET/C# wrapper and its associated features (such as documentation, signing security, and the shim layer). This means, once you figure out how to work around these issues, you’re left with a solid, powerful engine.

Additional Resources:

  • Microsoft Knowledge Base article 302901: “How To Build an Office COM Add-in by Using Visual C# .NET”.
  • Microsoft Knowledge Base article 830033: “How to apply Windows SP themes to Office COM add-ins”.
  • MSDN Library: “Automating Word Using the Word Object Model”.
  • MSDN Library: “Using the COM Add-in Shim Solution to Deploy Managed COM Add-ins in Office XP” by Misha Shneerson and Siew-Moi Khor.
  • MSDN Library: “Working with the Office XP Primary Interop Assemblies” by Paul Cornell.

Introduction

For a long time, businesses have relied on Microsoft’s
Office applications to help running their daily routines.
Word is used to write e-mails and letters, Excel does the
calculations, and Outlook communicates. Everyone has seen a
slideshow made with PowerPoint. During the years, Office has
grown, and today the number of applications part of the
suite has grown to over ten.

Although many businesses can run their business with
stock versions of these products, it is common to repeat the
same steps again and again. For instance, sales people might
copy and paste information from their sales system to a Word
document, write their offer, send it out, and then return to
the sales system to mark their offer as done. And then they
start all over again.

As with many other Microsoft products, Office
applications are also highly customizable. In the past few
releases, extending these applications has become easier,
and you as a developer are not anymore limited to writing
COM DLLs with complex interfaces. Today, .NET
developers can also easily get started.

In this article, you are going to learn how you can use
Visual Studio 2008 and .NET to write custom add-ins for
Word. Along the way, you will explore the possibilities for
writing such applications. For instance, you can integrate
databases, web services and even blogs to your add-ins.

Say hello to VSTO

Developing applications for the Office products, and
especially for Office 2003 and 2007, is best done using
Visual Studio Tools for Office, or VSTO. With VSTO in Visual
Studio 2008, you can create different types of applications
that utilize the Office products as a platform. For
instance, you can create application-level add-ins,
document-level customizations, templates, workflows, and so
on. And as you might guess, you can develop your
applications from the convenience of the Visual Studio
IDE.

Love or hate it, the Office 2007 release bought
developers the Ribbon (Figure 1). With VSTO, you can also
create your own customizations to the ribbon, for example by
adding buttons or creating new groups of commands. Although
ribbon customizations are not the focus of this article, it
is important to realize that this kind of user interface is
getting more and more common. Windows 7 also contains a
ribbon interface in many of the built-in applications such
as Paint and Wordpad.

Office applications have had a user interface element
called a task panel for a long time already (Figure 2). With
VSTO, you can create your custom task panels, and for the
purpose of this article, they are a great way to expose the
add-in’s user interface, but more on that shortly.

If you have studied Office application development
before, you might have the impression that such development
work is hard. With previous versions of Visual Studio, this
arguably was the case, as you had to fiddle with different
COM interfaces, primary interoperability assemblies (PIAs),
and so on. But if you are using Visual Studio 2008 and are
targeting Office 2007 applications, things have gotten much
easier.

Installing VSTO usually happens along with Visual Studio.
When selecting the features to be installed, you will also
have the chance to select VSTO components (Figure 3). Note
however, that VSTO is only available from Visual Studio
Professional upwards. It is not available in the Visual
Studio Standard or Expression editions, nor can it be
purchased separately.

Figure 3. VSTO components are part of Visual Studio 2008
Professional and Team editions.

Creating a custom solution for Word

A technology like VSTO is best demonstrated with the help
of a sample application. To demonstrate the possibilities of
expanding Office applications with a custom solution, this
article walks you through in creating an add-in for Word
with VSTO in Visual Studio 2008.

The add-in itself contains a custom task panel. The
purpose of this panel is to let a sales person send quotes
to customers more efficiently. The sales person can simply
enter a customer ID, and automatically fetch customer’s
basic information into the active Word document. The panel
also contains a little utility to convert U.S. dollar amount
to euros using a web service. Finally, once the document is
saved, the add-in posts an update to a public blog. This
way, other sales persons are aware of the updated quote.

To begin developing your custom task panel, you first
need to fire up Visual Studio 2008. Choose to create a new
project (with the File/New/Project menu command), and in the
subsequent New Project dialog box, navigate to the
Office/2007 project type node on the left (Figure 4). From
there, select a Word Add-in, enter a name for your project
(such as WordSalesToolsAddIn), and click OK.

Once Visual Studio has finished creating your project
from the template, you should see a project with several
files and references (Figure 5). For instance, under the
Word folder, you should see a file named ThisAddIn.cs. This
file acts like the Global.asax.cs file in ASP.NET web
applications: the code in the file is executed when the add-
in first loads, and also when add-in is unloaded, i.e. when
Word closes down.

If you run the project immediately after creating it,
Visual Studio will automatically register your add-in so
that Word can load it, and will then start Word. This makes
development and testing very easy. However, unless you have
written additional code to the project, you cannot actually
see anything in Word when you run your project. Even on the
Ribbon, the Add-ins tab (if visible) doesn’t contain any
hints about your creation.

Of course, creating a custom task panel will create
something that’s visible in Word. At this point, you might
be tempted to return to Visual Studio, open the Add New Item
dialog box (Project/Add New Item) and look for a custom task
panel object. Unfortunately, such an object does not exist.
Instead, to create custom task panels for Word and other
compatible Office applications, you need user controls.

Task panels as user controls

To create a new custom task panel for Word, you need to
add a Windows Forms user control to your project. To do
this, open the Add New Item dialog box and select the User
Control template from the Windows Forms group (Figure 6).
Alternatively, you can use the Project menu’s “Add User
Control” command.

Once added, the user control will show as a
blank, gray area in the Visual Studio designer. The next
step would be to add controls to the designer surface. You
will also need a small piece of code in the ThisAddIn.cs
file to register your task panel with Word, and make it
visible. The code is similar to the following:


SalesToolsUserControl userControl =
new SalesToolsUserControl();
CustomTaskPane myCustomTaskPane =
this.CustomTaskPanes.Add(
userControl, “Sales Tools”);
myCustomTaskPane.Visible = true;

This code is added to the ThisAddIn_Startup method in the
ThisAddIn.cs file. In the code, an instance of the user
control (here called SalesToolsUserControl) is created, and
then it is added to the list of custom task panes that Word
controls. Finally, the pane is shown on the screen.

Implementing the functionality for the add-in

Once you have the basic user control skeleton created for
your custom task pane, it is time to add some functionality.
You can easily design the user interface of your pane in
Visual Studio, as a full-blown form designer is already
there. By default, Word sets the pane’s width to equal 200
pixels, with a height dependent on the size of Word’s main
window. You might wish to set the width of your user control
to equal 200 pixels so that designing would be easier.

Figure 7 shows the designed user interface for the sample
user control. Overall, the user interface itself is very
basic, but it’s the underlying code that makes the add-in
useful. The first feature in the add-in allows the user to
type in a customer ID, and then the add-in will fetch that
customer’s details from the Northwind SQL Server sample
database, and add them to the active Word document.

Figure 7. The sample application’s task panel in Visual Studio designer.
The code to fetch the details is straightforward: an SQL
connection is opened, a command is executed, and finally a
SqlDataReader object is used to fetch the results of the
query: string connStr = Properties.Settings.


Default.DatabaseConnection;
SqlConnection conn = new SqlConnection(
connStr);
try
{
conn.Open();
string sql = “SELECT [companyname], “+
“[contactname], [address], [city], ” +
“[region], [postalcode] “+
“FROM [customers] ” +
“WHERE [customerid] = @custid”;
SqlCommand cmd = new SqlCommand(
sql, conn);
cmd.Parameters.AddWithValue(
“@custid”, customerId);
try
{
SqlDataReader reader =
cmd.ExecuteReader();
try
{
if (reader.Read())
{
CustomerDetails cust =
new CustomerDetails();
cust.CompanyName = reader.GetString(0);
cust.ContactName = reader.GetString(1);
cust.Address = reader.GetString(2);
cust.City = reader.GetString(3);
if (!reader.IsDBNull(4))
{
cust.Region = reader.GetString(4);
}
cust.ZipCode = reader.GetString(5);

// finished
return cust;
}
}

Here, the connection string is read from the add-in’s XML
configuration file, and the given customer ID is passed as a
parameter to the SqlCommand object. The results are returned
in a custom class instance, which contains simple public
properties for each value.

Once the query has been executed, it is time to insert
the results into the active Word document.

This is done
with the following code:


CustomerDetails cust =
DataAccess.GetCustomerDetails(
customerIdTextBox.Text);
// format text
StringBuilder buffer = new StringBuilder();
buffer.AppendLine(cust.CompanyName);
buffer.AppendLine(cust.ContactName);
buffer.AppendLine(cust.Address);
buffer.AppendLine(cust.City);
buffer.AppendLine(cust.Region);
buffer.AppendLine(cust.ZipCode);
// insert text at the current cursor location
Microsoft.Office.Interop.Word.Application
word = Globals.ThisAddIn.Application;
Word.Range selection = word.Selection.Range;
selection.Text = buffer.ToString();

The main interaction with Word happens in the last three
lines of code. The Globals class is a designer-created class
that is part of every Word add-in project, and is created
automatically by Visual Studio when you start the project.
It lives in the hidden ThisAddIn.Designer.cs file. Through
the Globals object, you can access your add-in’s ThisAddIn
class. In turn, this class then contains the Application
reference, which points to Word’s automation interface
master object. The Word object is defined in the
Microsoft.Office.Interop.Word.Application namespace, which
has a rather lengthy name to type. Thus, you might wish to
use the following C# using statements to help you manage the
long names:


using Word = Microsoft.Office.Interop.Word;
using Office = Microsoft.Office.Core;
using Microsoft.Office.Tools.Word;
using Microsoft.Office.Tools.Word.Extensions;
using Microsoft.Office.Tools;

These same using statements are available in
ThisAddIn.cs, from which you might wish to copy them to your
user control file.

Converting currency values

The next part of functionality in the sample add-in is
the ability to convert currencies. In this case, the sample
application can take a USD dollar amount, and convert it to
Euros (€). This is done with the help of a free currency
conversion service available at Nimacon.net.

To use this service, you will first need to add a service
reference to the project. This is done easily using the Add
Service Reference dialog box in Visual Studio, launched for
example using the similarly named command in the Project
menu. Once the reference has been added, the following code
can be used to convert a USD amount into Euros:


float usdAmount = float.Parse(usdAmountTextBox.Text);
CurrencyRateService.CurrencyRateServiceClient
client = new CurrencyRateService.CurrencyRateServiceClient();
float euroAmount = client.GetEuroAmount(
“USD”, usdAmount);
MessageBox.Show(“$” + usdAmount + ” is ” +
euroAmount + ” €.”);

First, the code takes the USD amount the user has
entered, and converts the string to a float value. Then, an
instance of the web service client class is constructed
(Visual Studio automatically creates the
CurrencyRateServiceClient class when the service reference
is added). The next step is to make the actual web service
(SOAP) call using the GetEuroAmount method of the service.
This method takes in a currency amount in any supported
currency, and a string specifying which currency the value
is in. The return value is the amount in Euros with the
current exchange rate.

Publishing to a blog

In a corporate setting, sharing information is a
necessity. For instance, if a member of a sales team creates
or updates an important offer, the other members of the team
should know about it. However, letting other people know
that you’ve updated a document is an additional, often
manual step. Why couldn’t the system automatically send a
status update to colleagues whenever an important document
changes?

In the sample application, this need to inform others is
addressed via a blog. Whenever the user saves the offer
(i.e. commits the updates made), the add-in automatically
publishes a new entry on a Google Blogger blog, which is
available at www.blogspot.com (Figure 8). The Blogger
service uses a free HTTP based API, which is divided into
two major parts: authentication with the generic Google
authentication APIs, and the specific Blogger interfaces to
submit blog posts.

Since the focus of this article is in Word add-ins and
not the Google interfaces, only a short description follows.
As mentioned previously, the first part of using the Blogger
service is to authenticate properly. This is done by sending
a HTTP POST request to the authentication URL, and passing
in the username and password for the service. In exchange,
the service returns an authentication token, which must be
passed when submitting a post to the user’s blog with the
Blogger API.

The following is a snippet of the code to authenticate
with the service. The Google developer documentation for the
service is publicly available on the Internet; see the Links
section for details.

WebClient web = new WebClient();
try
{
NameValueCollection postParams =
new NameValueCollection();
postParams.Add(“accountType”, accountType);
postParams.Add(“Email”, email);
postParams.Add(“Passwd”, password);
postParams.Add(“service”, service);
postParams.Add(“source”, sourceApp);
byte[] result = web.UploadValues(
loginUrl, “POST”, postParams);
string resultStr =
Encoding.ASCII.GetString(result);
string[] tokens = resultStr.Split();
bloggerAuthToken = tokens[2].Substring(5);
}
finally
{
web.Dispose();
}

With the authentication token available (the
bloggerAuthToken member at the end of the code snippet), the
next step is to call the actual posting function. This can
be done with code similar to the following:


public static void SubmitBlogPost(
string title, string body)
{
string xml;
MemoryStream xmlStream =
new MemoryStream();
try
{
XmlWriterSettings settings =
new XmlWriterSettings();
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(
xmlStream, settings);
try
{
writer.WriteStartElement(
“entry”, “http://www.w3.org/2005/Atom”);

writer.WriteEndElement();
}
finally
{
writer.Close();
}
xml = Encoding.ASCII.GetString(xmlStream.ToArray());
}
finally
{
xmlStream.Dispose();
}
WebClient web = new WebClient();
try
{
web.Headers.Add(“Content-Type”, “application/atom+xml”);
web.Headers.Add(“Authorization”, “GoogleLogin auth=” +
bloggerAuthToken);
web.Headers.Add(“GData-Version”, “2”);
web.Encoding = Encoding.UTF8;
string response = web.UploadString(submitUrl, “POST”, xml);
}
finally
{
web.Dispose();
}
}

Here, the code first constructs an XML based Atom entry
that the Blogger service accepts (the actual lines are
omitted for brevity), and then uses a WebClient to connect
to the HTTP service. Note how several custom headers have to
be added to the HTTP request for the Google API to accept
it.

Now that the code to submit a blog entry is in place,
next you need to notice when the Word document is being
saved. The Word object model supports a set of events, and
one of them is called DocumentBeforeSave. You can hook into
this event, and then execute the necessary code to update
the blog whenever the document has been saved.

The logical place to hook into this event is in
ThisAddIn.cs’ ThisAddIn_Startup method. Here is the code to
hook and handle the event:


private void ThisAddIn_Startup(
object sender, System.EventArgs e)
{
userControl = new SalesToolsUserControl();

ApplicationEvents4_DocumentBeforeSaveEventHandler
handler = new ApplicationEvents4_
DocumentBeforeSaveEventHandler(
Application_DocumentBeforeSave);
this.Application.DocumentBeforeSave += handler;
}

private void Application_DocumentBeforeSave(
Microsoft.Office.Interop.Word.Document Doc,
ref bool SaveAsUI, ref bool Cancel)
{
// the document is about to be saved
if (!SaveAsUI)
{
// not saving with a new name
// or for the first time
userControl.PublishSaveToBlog(Doc.FullName);
}
}

Once the event handler has been assigned to the event,
the Application_DocumentBeforeSave method is called each
time the user saves the document. Notice how a presentation
of the active document is passed to the event handler as the
Doc parameter. This object contains a property called
FullName, which is used when creating the blog entry text.
The code in the user control’s PublishSaveToBlog method
looks like the following:


public void PublishSaveToBlog(string filename)
{
if (publishToBlogCheckBox.Checked)
{
BloggerClient.SubmitBlogPost(“Offer updated”,
“The offer “” + filename +
“” has been updated.”);
MessageBox.Show(“Blog entry published!”);
}
}

The end result is a blog entry similar to the one in
Figure 9.

Figure 9. A submitted blog entry by the sample application.

Conclusion

In this article, you saw how you can use Visual Studio
2008 to create add-in applications for the Office 2007 suite
of products, and more specifically Word 2007. Visual Studio
2008 provides ready-made templates for developing such add-
ins, and this gives you a head start. Creating for example a
custom task panel is easy as adding a new user control to
the project, and then registering it with Word.

The sample application (Figure 10) showed you how you can
easily extend Word to transform it to a powerful application
platform: you can access SQL databases, call web services,
and even associate your code with events in Word. From this
perspective, the rule of thumb is that if you can do it
from .NET code, you can also do it from an Office add-in.

If you are looking to build your custom solutions to help
businesses run more efficiently, think about whether you
could integrate the needed functionality into Office
applications like Word, Excel, PowerPoint or Outlook. The
possibilities in both business and personal settings are
many. And once Visual Studio 2010 and Office 2010 become
available, the story will get even better. In the mean time,
let’s empower the office!

Resource Links

Office Development with Visual Studio
Office Developer Center
Word 2007 Developer Reference
Word Object Model Reference
Google Data APIs Overview

About the Author

Jani Järvinen is a software development trainer and
consultant in Finland. He is a Microsoft C# MVP and a
frequent author and has published three books about software
development. He is the group leader of a Finnish software
development expert group at ITpro.fi and a board member of
the Finnish Visual Studio Team System User Group. His blog
can be found at http://www
.saunalahti.fi/janij/. You can send him mail by clicking
on his name at the top of the article.

What is a Word add-in?

Word add-in is a user developed application that can be integrated with  Microsoft word and can interact with the content of the document using the word javascript API.

What  can we do using Microsoft word add-in?

  • Adding different type of content to document
  • bring external data in to office
  • automate office documents
  • Add breaks (line break, paragraph break etc)
  • Text formatting (highlight, bold etc..)
  • custom search options
  • embed maps ,charts.
  • Custom Ui design with fabric and many more things.

Why we use Word add-ins?

Some times our Requirements does not fulfil with pre-defined functions of the Microsoft word. At that time we can easily build functions as we needed and can integrate with those in to the Microsoft word. And office web add-ins are cross platform , So that we can run in office for windows, Mac, ios and office online.

How to start a project?

We can easily create word add-in using visual studio. In this tutorial I am using Microsoft visual studio 2015

Start new project and then select Templates -> visual c# -> office/sharepoint -> web add-ins -> word add-ins

Capture

if you do not see these templates, then you need to download office developer tools for visual studio 2015.

After creating your project, you will see lot of files. Office add-in consists with two main components.

Components of office add-in

DK2_AgaveOverview01

Manifest

This is a xml file. This file consist of settings and configuration of the add-in. You can change the display name etc in that file

In the solution explorer we can see these files, under test-add-in part. You can see the manifest. open that file and see the xml contents.

xml

 <DefaultSettings>
 <SourceLocation DefaultValue="~remoteAppUrl/Home.html" />
 </DefaultSettings>

Actually in a word add-in,  a html file is loaded in to the task pane of the Microsoft word. You can see the configuration for that part in above code. In the <sourceLocation> tag we have set the Home.html file as the default html view.

Webpage

In the solution explorer we can see these files, under test add in web part. There are two main files under web app. Those are home.html and home.js.

home

In the home.html we write normal html that we all know to design the content of the task pane. In the Home.js, we write functions to handle the word web add in.we will se how to write our own function in a later post.

How to run  the project ?

Click run arrow in visual studio to see the word web add-in. Then It will automatically generate self sign certificate and you will be asked to trust it.

certificate

Your html files are hosted in localhost. To load html file to Microsoft word, you need to trust the domain in your browser settings.  You can see this is a self sign certificate because of issued-by and issued-to are from same person. Click yes, then you will not face certificate problems.

This certificate is only for testing purposes. We will see how to deploy project in a later post.

Then It will open Microsoft word automatically and you can see a task pane like this

task

OMG!. we have build our first web add-in successfully. This is the default template we will get into the task pane at we run the default project. we can change those things and add new contents as we like. According to this template we can display the selected word in the task pane. Try it your self.

In the next post, we will see how to customise and how to  create our own design with the word add-in.

Понравилась статья? Поделить с друзьями:
  • Writing a table of contents in word
  • Writing a screenplay on word
  • Writing a resume using word
  • Writing a number squared in word
  • Writing a novel in word