Home arrow C++ arrow Page 2 - Writing an MS Word Addin
C++

Writing an MS Word Addin


Writing a MS Word 2000 ATL COM addin is now possible with the help of Amit using either VBA or C++. Make sure you have read Amit's earlier article on Office addins.

Author Info:
By: Amit Dey
Rating: 4 stars4 stars4 stars4 stars4 stars / 18
April 16, 2003
TABLE OF CONTENTS:
  1. · Writing an MS Word Addin
  2. · Writing a Word2000 Addin
  3. · Acknowledgements

print this article
SEARCH DEVARTICLES

Writing an MS Word Addin - Writing a Word2000 Addin
(Page 2 of 3 )

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 3 interdependent typelibs. In your project's stdafx.h add the following code:

//for Office XP 
//C:\Program Files\\Common Files\\Microsoft Shared\\Office10\\MSO.DLL
#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:

//for Office XP
//C:\\Program Files\\Micorosft Office\\Office10\\MSWORD.olb
#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 is 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 Word2000, add the following code to 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 CodeProject.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 etc; all that has been discussed in the previous article holds true.

The only thing different in the Word Object Model, and from the code in the Outlook addin example, is that there is no ActiveExplorer object in Word, and 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 objects 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 IDL like :

      ....
     [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 has been mentioned.

extern _ATL_FUNC_INFO DocumentOpenInfo;
class ATL_NO_VTABLE CAddin : 
public CComObjectRootEx,
public CComCoClass,
public ISupportErrorInfo,
public IDispatchImpl,
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 setup 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 for doing 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 etc. 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 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 menuiems and buttons, clicking which ususally meant displaying a couple of dialogs. Now, a COM addin would be perfect for Word2000 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, 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 it's debut in Office 95 - albeit,in a few applications. By Office97, 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 goes 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 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 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, MS Word97 onwards also supports a group of global macros that gets invoked with the host application. These AutoXXX macros like AutoExec(), AutoNew(),AutoOpen(),AutoClose() and AutoExit() gets 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 templateis 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 it's 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:

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's Startup folder.Similarly you can handle AutoClose(),AutoNew() and AutoOpen() macros, which are more document related. For the moment, since 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 defined in your template called "NewMacro"; 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.

// get CommandBarButton interface so we can specify button styles
CComQIPtr < Office::CommandBarButton> spCmdButton(spNewBar);
ATLASSERT(spCmdButton);
spCmdButton->PutCaption(_T("Button")); 
spCmdButton->put_OnAction(OLESTR("NewMacro")); 
//set other button properties
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 it's functionilty through compiled C++ code. Unfortunately, this VBA support brings up the security issue, and in later versions of Office like XP, the user can determine the security level in macro execution context. Although this was a non-issue with us, you need to be aware.
All of what we have learnt so far. has been espoused in the accompanied 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 it's 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 it's 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.

Our 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 viceversa - the magic is in OLEAutomation.

And of course, happy coding!


blog comments powered by Disqus
C++ ARTICLES

- Intel Threading Building Blocks
- Threading Building Blocks with C++
- Video Memory Programming in Text Mode
- More Tricks to Gain Speed in Programming Con...
- Easy and Efficient Programming for Contests
- Preparing For Programming Contests
- Programming Contests: Why Bother?
- Polymorphism in C++
- Overview of Virtual Functions
- Inheritance in C++
- Extending the Basic Streams in C++
- Using Stringstreams in C++
- Custom Stream Manipulation in C++
- General Stream Manipulation in C++
- Serialize Your Class into Streams in C++

Watch our Tech Videos 
Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 
Support 

Developer Shed Affiliates

 




© 2003-2017 by Developer Shed. All rights reserved. DS Cluster - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials