Word add in programming

Word add-ins programming guide

Applies to: Word 2016

Microsoft Word 2016 (this list will be updated when we get new hosts) introduces a new object model for working with Word objects. This object model is an addition to the existing object model provided by OfficeJS to create add-ins for Word. This object model is accessed via JavaScript hosted by a web application.

Manifest

The new Word add-in Javascript API uses the same manifest format as is used for the old Word add-in model. The manifest describes where the add-in is hosted, how it is displayed, permissions, and other information. Learn more about how you can customize the add-in manifests.

You have a few options for publishing Word add-in manifests. Read about how you can publish your Office add-in to a network share, and app catalog, or to the Office store.

Understanding the Javascript API for Word

The Javascript API for Word is loaded by Office.js. It provides a set of proxy objects that are used to queue a set of commands that interact with the contents of a Word document. These commands are run as a batch. The results of the batch are actions taken on the Word document like inserting content, and synchronizing the Word objects with the Javascript proxy objects.

Running your add-in

Let’s take a look at what you’ll need when you run your add-in. All add-ins should have an Office.initialize event handler. Read Understanding the API for more information about add-in initialization. Your Word add-in executes by passing a function into the Word.run() method. The function passed into the run method has a context argument. This context object is different than the context object you get from the Office object, although it is used for the same purpose which is to interact with the Word runtime enviroment. The context object provides access to the Word Javascript object model. Let’s take a look at the comments and code of a basic Word add-in:

Example 1. Initialization and execution of a Word add-in

    (function () {
        "use strict";

        // The initialize event handler is run each time a the page is loaded.
        Office.initialize = function (reason) {
            
            // Checks for the DOM to load using the jQuery ready function.
            $(document).ready(function () {
                // Set your initialization code. You can use the reason argument
                // to determine how the add-in was loaded.
            });
        };

        // Run a batch operation against the Word object model.
        // Use the context argument to get access to the Word document.
        Word.run(function (context) {

            // Create a proxy object for the document.
            var thisDocument = context.document;
        })
    })();

Proxy objects

The Word Javascript object model is loosely coupled with the objects in Word. The Word Javascript objects are proxy objects for the real objects in a Word document. All actions taken on proxy objects are not realized in Word, and the state of the Word document is not realized in the proxy objects, until the document state has been synchronized. The document state is synchronized when context.sync() is run. Example 2. shows the creation of a proxy object, a queued command to load the text property on the body proxy object, and then the synchronization of the body contents in the Word document with the body proxy object.

Example 2. Synchronization of the document body with the body proxy object.

    // Run a batch operation against the Word object model.
    Word.run(function (context) {

        // Create a proxy object for the document body.
        // The body object hasn't been set with any property values. 
        var body = context.document.body;

        // Queue a commmand to load the text property for the proxy document body object.
        context.load(body, 'text');

        // Synchronize the document state by executing the queued-up commands, 
        // and return a promise to indicate task completion.
        return context.sync().then(function () {
            console.log("Body contents: " + body.text);
        });  
    })

Command queue

The Word proxy objects have methods for accessing and updating the object model. These methods are executed sequentially in the order in which they were queued in the batch. In example 3., we demonstrate how the queue of commands works. When context.sync() is run, the first thing that happens is that the commmand to load the body text is executed in Word. Then, the command to insert text into the body on Word occurs. The results are then returned to the body proxy object. The value of the body.text property in the Word Javascript will be the value of the Word document body before the text was inserted into Word document.

Example 3. Executing a batch of commands.

    // Run a batch operation against the Word object model.
    Word.run(function (context) {

        // Create a proxy object for the document body.
        var body = context.document.body;

        // Queue a commmand to load the text in document body.
        context.load(body, 'text');

        // Queue a command to insert text into the end of the body.
        body.insertText('This is text inserted after loading the body.text property',
                        Word.InsertLocation.end);

        // Synchronize the document state by executing the queued-up commands, 
        // and return a promise to indicate task completion.
        return context.sync().then(function () {
            console.log("Body contents: " + body.text);
        });  
    })

— The methods, how they are used to send instruction to the document, how they, how there is a queue, when they executed

Synchronize the document and proxy objects

  • synchronize from the document to object model. sync from object model to the document.

The Basics

This section introduces key concepts that you need to understand to work with the Word API.

RequestContext()

All actions that target a Word document start by using the client request context returned by the Word.RequestContext method. The client request context serves two major roles:

  • Contains the queue of commands that will be performed on the contents of a Word document.
  • Provide the bridge between the Office add-in and the Word application since they run in two different processes. The JavaScript runs in the user’s browser within the task pane. Word runs in a different process, and in the case of Word Online, on a remote server cluster.

Here’s how you get the request context:

    var ctx = new Word.RequestContext();

You can now create a queue of commands that will target the contents of a Word document. For example, let’s create a set of commands that will get the current selection and add some text to the selection. The selection will be contained in a range object returned by document.getSelection(). We are going to add some text at
the end of the selection. We’ll use the context given in the previous line of code.

    var range = ctx.document.getSelection();
    range.insertText("Hello World!", Word.InsertLocation.end);

At this point, no changes have occurred. You have specified a set of commands that will run in the future. Let’s expand on this by looking at the load method.

executeAsync()

The Word JavaScript objects created in the add-ins are local proxy objects. Invoking methods and setting properties queues the set of commands in JavaScript, but does not submit them until executeAsync() is called. executeAsync submits the request queue to Word and returns a promise object, which can be used for chaining further commands. executeAsync runs each command in the order in which they were added to the queue. A best practice is to group related commands into a single executeAsync call.

executeAsync() example

This example shows how to insert text at the end of a selection. The queue is filled with two commands: getting the user’s selection and inserting text at the end of the user’s selection. These commands are ran when ctx.executeAsync() is called. executeAsync() returns a promise which can be used to chain it with other operations.

    var ctx = new Word.RequestContext();

    // Queue: get the user's current selection and create a range object named range.
    // Queue: insert 'Hello World!' at the end of the selection.
    var range = ctx.document.getSelection();
    range.insertText("Hello World!", Word.InsertLocation.end);

    // Run the set of commands in the queue. In this case, we are inserting text
    // at the end of the range. 
    ctx.executeAsync()
        .then(function () {
            console.log("Done");
        })
        .catch(function(error){
            console.log("ERROR: " + JSON.stringify(error));
        });

load()

The load method specifies which collections, objects, and properties will be loaded into the object model. You use the client request context to specify the load options and the object to load. There are two options for using the load method. We’ll use the client request context we created above:

    ctx.load(object, options); 
    // or
    object.load(options);

object identifies the object that will be loaded into the object model.

options identifies which properties are loaded and the paging arguments. Properties to load can be specified as either a string, a string of comma-separated values, an array of strings, or in a loadOption object.

Note — You can use multiple load statements that will be dispatched in a single executeAsync() call. Do this instead of creating complicated select and expand statements.

For example, we’ll use the context given in the previous code to load the text content of all of the paragraphs contained in the current selection that was captured in the range object.

    ctx.load(range.paragraphs, 'text');

Here is key information for using the load method:

  • You SHOULD specify the property set you want to load for the object in the options parameter. Not including the options parameter is the equivalent of using a «SELECT * from Table1», which will affect performance and SHOULD NOT be done for production applications.
  • If the loaded object is a collection, then the specified properties will be loaded for all objects in the collection.
loadOption object

The loadOption object specifies which properties to load and how to page through a collection. There are four loading options:

  • select
  • expand
  • top
  • skip

select

You use the select option to load properties that are primitive types. You can use either a string or an object literal to specify which properties to load. For example, if you are going to make simple load statement, you don’t need to create an object literal to specify the property. The following code will load the text string for a range object:

Use commas to separate properties if you use the string form.

    ctx.load(range, 'text, style, font');

You can specify the property set in the following object literal forms:

    {select: 'propertyName'}
    {select: "propertyName1, propertyName2"}
    {select: ['propertyName1', 'propertyName2']}

Let’s build on the last code snippet and load the style property on the range object.

    ctx.load(range, 'style');

If you take a look at the range object documentation, you can see that you can select the style, and text properties as they are all primitive types. You use methods to load HTML and OOXML properties.

There’s also a select path notation to access properties on objects specified by the expand statement.

expand

You use the expand option to load properties that are in nested Word API objects and collections. Using the range object from the previous examples, we can load the paragraphCollection and the font object for the range by specifying the objects in the expand option. We identify which properties are returned in the select statement.

    ctx.load(range, {select: 'font/color, paragraphs/text', 
                     expand: 'font, paragraphs'});

Notice how we specify a path to the selected properties in the select statement. The select statement can be used not only to specify the properties on the loaded object, but also to specify the properties loaded on the child objects identified by the expand option. We would have gotten all the properties for the font object and paragraphs collection if we hadn’t added the select statement. It is a best practice to always use the select statement with the expand statement.

Use multiple load method calls if you find that your loadOption objects are getting too complex.

Pulling it all together

Let’s put it all together by taking a look at a simple example that shows how you can use the client request context, load method, references, and the executeAsync() method.

Example: How to load the font color and paragraph text for all fonts and paragraphs in a range

    // Run a batch operation against the Word object model.
    Word.run(function (ctx) {

        // Create a proxy object for the document.
        var thisDocument = ctx.document;

        // Queue a command to get the user's current selection.
        // Create a proxy range object for the selection.
        var range = thisDocument.getSelection();

        // Queue a command to insert text into the selection.
        range.insertText("Hello World!", Word.InsertLocation.end);

        // Queue a command to load the range object's font color and the text 
        // for all paragraphs in the paragraph collection. 
        ctx.load(range, {select: 'font/color, paragraphs/text', 
                         expand: 'font, paragraphs'});    

        // Synchronize the document state by executing the queued-up commands, 
        // and return a promise to indicate task completion.
        return ctx.sync().then(function () {

            // The document has been updated with text inserted in to the selection.
            // The proxy range object that was created based on the selection 
            // has been loaded. You can access the font color and the text content 
            // in the paragraph collection on the proxy range object. 

            var contents = '';

            for (i=0; i < range.paragraphs.items.length; i++) {
                contents = contents + range.paragraphs.items[i].text;
            }

            // Show the contents of the paragraphs 
            console.log("OUTPUT: paragraph text in the range object: " + contents);

            // Queue a command to add a paragraph to the end of the range. 
            range.insertParagraph("This is a new paragraph.", Word.InsertLocation.after);

            // Synchronize the document state by executing the queued-up commands, 
            // and return a promise to indicate task completion. In this case,
            // we are inserting a paragraph into the selection.
            return ctx.sync();
        });  
    })
    .catch(function (error) {
        console.log("Error: " + JSON.stringify(error));
        if (error instanceof OfficeExtension.Error) {
            console.log("Debug info: " + JSON.stringify(error.debugInfo));
        }
    });

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.

Table of Contents

  • Introduction
  • Building the Sample

You can download the source code from this link Source
Code Link

Introduction

The main purpose of this article is to explain how to create simple Excel and Microsoft Word Add-Ins using Visual
Studio Tools for Office
 (VSTO).
VSTO is available as an add-in tool with Microsoft Visual Studio. Using Visual Studio we can develop our own custom controls for Office tools like Excel, Word and and so on.
In
our demo program We have used Visual Studio 2010 and Office 2007.

Building the Sample

This article explains a few basic things to create our own Custom Add-Ins for Excel and Word as follows.

1. Excel Add-Ins

  • Add text to any Excel selected active Excel cell.
  • Add an image to Excel from our Custom Control.
  • Load data from a database and display the search result data in Excel.

2. Word Add-Ins

  • Export Word to PDF.
  • Add Image to Word Document.
  • Add Table to Word document.

Description

Creating Excel Add-Ins

To create our own Custom Control Add-Ins for Excel.

Step 1

Create a new project and select Office 2007 Excel Add-In as in the following Image. Select your Project Folder and enter your Project Name.

Step 2

Now we can see that the Excel ThisAddIn.Cs file has been created in our project folder and we can find two default methods in this class as in the following image. “ThisAddIn_Startup
In this event we can display our own custom Control Add-Ins to Excel. We can see the details in the code part.

Step 3

Add a new UserControl to your project to create your own Custom Excel Control Add-In.

Right-click your project->Click Add New Item->Add User Control and Name the control as you wish. Add all your Controls and design your user control depending on your requirement.

In our example,We are performing 3 types of actions in User Controls.

  1. Add Text: In this button click event I will insert the text from the Text box to the Active Selected Excel Cell. Using “Globals.ThisAddIn.Application.ActiveCell
    we can get the current active Excel cell. We store the result in an Excel range and now using the range, value and color we can set our own text and colors to the active Excel Cell.

private void btnAddText_Click(object sender, EventArgs e)  

{  

    Excel.Range objRange = Globals.ThisAddIn.Application.ActiveCell;  

    objRange.Interior.Color = Color.Pink; //Active Cell back Color  

    objRange.Borders.Color = Color.Red;// Active Cell border Color  

    objRange.Borders.LineStyle = Excel.XlLineStyle.xlContinuous;  

    objRange.Value = txtActiveCellText.Text; //Active Cell Text Add  

    objRange.Columns.AutoFit();   

}

2.  Add Image: using
the Open File Dialog we can select our own image that needs to be added to the Excel file. Using the Excel.Shape we can add our selected image to the Excel file.

private void btnAddImage_Click(object sender, EventArgs e)  

        {  

            OpenFileDialog dlg = new OpenFileDialog();  

            dlg.FileName = "*";  

            dlg.DefaultExt = "bmp";  

            dlg.ValidateNames = true;  

            dlg.Filter = "Bitmap Image (.bmp)|*.bmp|Gif Image (.gif)|*.gif|JPEG Image (.jpeg)|*.jpeg|Png Image (.png)|*.png";  

            if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)  

            {  

                Bitmap dImg = new Bitmap(dlg.FileName);  

                  Excel.Shape IamgeAdd = Globals.ThisAddIn.Application.ActiveSheet.Shapes.AddPicture(dlg.FileName,  

      Microsoft.Office.Core.MsoTriState.msoFalse,            Microsoft.Office.Core.MsoTriState.msoCTrue,  

          20, 30, dImg.Width, dImg.Height);  

            }  

            System.Windows.Forms.Clipboard.Clear();  

        }

Search and bind Db Data to Excel: Now
we can create our own Custom Search control to be used in Excel to search our data from the database and bind the result to the Excel file.

Creating the table

-- Create Table ItemMaster in your SQL Server - This table will be used for search and bind result to excel.  

CREATE
TABLE
[dbo].[ItemMasters](  

[Item_Code] [varchar](20)
NOT NULL,  

[Item_Name] [varchar](100)
NOT NULL)  

-- insert sample data to Item Master table  

INSERT
INTO
[ItemMasters] ([Item_Code],[Item_Name])  

VALUES
('Item001','Coke')  

INSERT
INTO
[ItemMasters] ([Item_Code],[Item_Name])  

VALUES
('Item002','Coffee')  

INSERT
INTO
[ItemMasters] ([Item_Code],[Item_Name])  

VALUES
('Item003','Chiken Burger')  

INSERT
INTO
[ItemMasters] ([Item_Code],[Item_Name])  

VALUES
('Item004','Potato Fry')

In the button search click event we search for the data from the database and bind the result to an Excel cell using “Globals.ThisAddIn.Application.ActiveSheet.Cells”.
This will add the result to the active Excel sheet.

private
void
btnSearch_Click(
object
sender, EventArgs e)  

{  

    try  

    {  

        System.Data.DataTable dt =
new
System.Data.DataTable();  

        String ConnectionString =
"Data Source=YOURDATASOURCE;Initial Catalog=YOURDATABASENAME;User id = UID;password=password";  

        SqlConnection con =
new
SqlConnection(ConnectionString);  

        String Query =
" Select Item_Code,Item_Name FROM ItemMasters Where Item_Name LIKE '"
+ txtItemName.Text.Trim() + "%'";  

        SqlCommand cmd =
new
SqlCommand(Query, con);  

        cmd.CommandType = System.Data.CommandType.Text;  

        System.Data.SqlClient.SqlDataAdapter sda =
new
System.Data.SqlClient.SqlDataAdapter(cmd);  

        sda.Fill(dt);  

        if
(dt.Rows.Count <= 0)  

        {  

            return;  

        }  

        Globals.ThisAddIn.Application.ActiveSheet.Cells.ClearContents();    

        Globals.ThisAddIn.Application.ActiveSheet.Cells[1, 1].Value2 =
"Item Code";     

        Globals.ThisAddIn.Application.ActiveSheet.Cells[1, 2].Value2 =
"Item Name";     

        for
(int
i = 0; i <= dt.Rows.Count - 1; i++)  

        {     

            Globals.ThisAddIn.Application.ActiveSheet.Cells[i + 2, 1].Value2 = dt.Rows[i][0].ToString();        

            Globals.ThisAddIn.Application.ActiveSheet.Cells[i + 2, 2].Value2 = dt.Rows[i][1].ToString();  

        }  

    }  

    catch
(Exception ex)  

    {  

    }  

}

Step 4

Now we have created our own User Control to be added to our Excel Add-Ins. To add this user control to our Excel Add-In as we have already seen that the Excel Addin class “ThisAddIn.Cs” has start and stop events. Using
the Office “CustomTaskpane” we can add our user control to Excel as an Add-In as in the following.

private
Microsoft.Office.Tools.CustomTaskPane customPane;  

private
void
ThisAddIn_Startup(
object
sender, System.EventArgs e)  

{  

    ShowShanuControl();  

}  

public
void
ShowShanuControl()  

{  

    var txtObject =
new
ShanuExcelADDIn();  

    customPane =
this.CustomTaskPanes.Add(txtObject,
"Enter Text");  

    customPane.Width = txtObject.Width;  

    customPane.Visible =
true;  

}

Step 5

Run your program and now we can see our user control has been added in the Excel File as an Add-In.
Next we will see how to create Add-Ins for Word Documents using a Ribbon Control.

Creating Word Add-Ins: 
In my example I have used Visual Studio 2010 and Office 2007.
The following describes how to create our own Custom Control Add-Ins for Word.

Step 1

Create a new project and select Office 2007 Word AddIn as in the following Image. Select your Project Folder and enter your Project Name.

Step 2

Add a new Ribbon Control to your project to create your own Word Control Add-In.

Right-click your project then click Add New Item -> Add Ribbon Control and name the control as you wish. 

Add all your controls and design your user control depending on your requirements. By default in our Ribbon Control we can see a “RibbonGroup”. We can add all our controls to the Ribbon Group. Here
in my example I have changed the Group Label Text to “SHANU Add-In”. I have added three Ribbon Button Controls to the group. We can add an image to the Ribbon Button Controls and set the properties of the Button Control Size as “
RibbobControlSizeLarge”. 

Here I have added three Button Controls for export the Word as a PDF, add an image to Word and add a table to the Word file.

Step 3

Export to PDF File Button Click.

Using the “Globals.ThisAddIn.Application.ActiveDocument.ExportAsFixedFormat” we can save the Word document to the PDF file. I have used the Save file dialog to save the PDF file into our selected path.

private
void
btnPDF_Click(
object
sender, RibbonControlEventArgs e)  

{  

    SaveFileDialog dlg =
new
SaveFileDialog();  

    dlg.FileName =
"*";  

    dlg.DefaultExt =
"pdf";  

    dlg.ValidateNames =
true;  

    if
(dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)  

    {  

        Globals.ThisAddIn.Application.ActiveDocument.ExportAsFixedFormat(dlg.FileName, word.WdExportFormat.wdExportFormatPDF, OpenAfterExport:
true);  

    }              

}

Step 4

Here we will add an image to Word. Using the Open File Dialog we can select our own image to be added to the Word file. Using the “Globals.ThisAddIn.Application.ActiveDocument.Shapes.AddPicture” method we can add our
selected image to the Word file.

private
void
btnImage_Click(
object
sender, RibbonControlEventArgs e)  

{  

    OpenFileDialog dlg =
new
OpenFileDialog();  

    dlg.FileName =
"*";  

    dlg.DefaultExt =
"bmp";  

    dlg.ValidateNames =
true;  

    dlg.Filter =
"Bitmap Image (.bmp)|*.bmp|Gif Image (.gif)|*.gif|JPEG Image (.jpeg)|*.jpeg|Png Image (.png)|*.png";  

    if
(dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)  

    {  

        Globals.ThisAddIn.Application.ActiveDocument.Shapes.AddPicture(dlg.FileName);  

    }  

}

Step 5

Here we will add a table to Word. Using the “Globals.ThisAddIn.Application.ActiveDocument.Tables” method we can add a table to the Word file. In my example I have created a table with 4 columns and 3 rows.

private
void
button1_Click(
object
sender, RibbonControlEventArgs e)  

{  

    Globals.ThisAddIn.Application.ActiveDocument.Tables.Add(Globals.ThisAddIn.Application.ActiveDocument.Range(0, 0), 3, 4);  

.ThisAddIn.Application.ActiveDocument.Tables[1].Range.Shading.BackgroundPatternColor = Microsoft.Office.Interop.Word.WdColor.wdColorSeaGreen;  

    Globals.ThisAddIn.Application.ActiveDocument.Tables[1].Range.Font.Size = 12;  

    Globals.ThisAddIn.Application.ActiveDocument.Tables[1].Rows.Borders.Enable = 1;  

}

Step 6

Run your program and now you will see your own Ribbon Control has been added to the Word file as an Add-In.

  

You can download the source code from this link Source
Code Link

Время на прочтение
14 мин

Количество просмотров 13K

VSTO расшифровывается как Visual Studio Tools for Office. Эти средства позволяют довольно легко скрещивать ужа с ежом — писать .NET приложения, исполняемые CLR в среде Microsoft Office. В частности, программисты обладают возможностью создавать подключаемые модули (плагины) и «кастомизированные» шаблоны для документов почти ко всему основному семейству продуктов Microsoft Office.

В статье приведена инфраструктура Windows Forms проекта, в котором Microsoft Word воспринимается приложением в качестве шелла. В статье раскрыты несколько интересных моментов использования Composite UI Application Block, в частности подключение инфраструктуры доменной модели Word в сервисам расширения каркаса, а так же приведены некоторые факты и особенности разработки с использованием средств VSTO.

Задача

Предположим, у нас есть несколько десятков людей, которые пишут документы и постоянно работают с их редакциями. Sharepoint и другие порталы по каким-то причинам не подходят, поэтому требуется реализация собственной бизнес-логики. Усложним задачу — люди работают в области, очень далекой от компьютерной тематики и хорошо знают только пакет Microsoft Office. Еще усложним — контора бедная, Microsoft Office у большинства народа версии 2003-ей. Кое где, у больших начальников и Главного босса стоит 2007ой. Парк машин разнородный — начиная от 2000-ой винды и до Windows Vista.

Решение

Одним из решений является плагин для Microsoft Office. Его можно написать на COM-е, через голый Office Interop, или же через такой же интероп, но по хитрому обернутый в средства VSTO. Эти средства и задействуем. Пользователь будет запускать Word, среда исполнения VSTO будет подцеплять сборки .NET и каким-то образом дополнять элементы управления Microsoft Word, позволяя пользователю выполнить нужную в задаче бизнес-логику.

А к чему все это?

(Это кому не терпиться узнать, чем все закончится). На выходе я выложу Visual Studio solution, который позволит в полпинка создать свое приложение, подключаемое в Microsoft Word. По ходу дела я объясню практически все, что и как в этом солюшене используется.

Что потребуется для работы

  1. Microsoft Word 2003 SP3
  2. VSTO 2005 SE
  3. VSTO 2005 SE Runtime
  4. CAB
  5. Visual Studio 2005 или 2008

Зачем CAB?

Для того, чтобы определить явное разделение функционала, связанного с Microsoft Word от простой Windows Forms логики. Проще говоря, наш плагин будет являться модулем CAB, и при желании, мы легко сможем подключить эту логику в другие приложения CAB. Еще проще говоря, использование CAB минимизирует количество glue-кода.

Есть еще две причины, по которым я включил CAB в решение. Первая состоит в том, что я уже давал описание этого каркаса здесь, а теперь хочу привести полноценный работающий пример. Вторая причина более банальная — мне нравится CAB, он делает мир и вещи в нем проще:)

Подробнее про VSTO

Вообще говоря, VSTO не только плагины для Word-а умеет создавать. Это мощная система, сейчас она уже есть в третьей версии. VSTO — это врата для разработчика в мир автоматизации и программирования Office-based решений. В двух словах — разработчик .NET получает доступ к доменной модели приложения Office и делает с ней что хочет.

Все эти решения можно разделить на два типа:

  1. Уровень документа (Document Level Customization)
  2. Уровень приложения (Application Level Customization)

Это независимые уровни. На первом создаются шаблоны документов для приложений Microsoft Office. Можно взять шаблон Excel, разнообразить его разными пользовательскими элементами управлениями (кнопками, гридами), и добавить всякую логику — например, чтобы по событию нажатия кнопки собранные пользователем данные отправлялись через веб-сервис на какой-то сервер и обрабатывались там. На этом уровне разработчику доступен Action Pane (в 2003-ей версии), куда можно подключать свои элементы управления, плюс еще очень много всего в 2007-ой версии.

Application Level Customization означает то, что в среду приложения Microsoft Office подключается модуль расширения (т.н. add-in). Собственно, про него я изначально и начал говорить. Этот модуль расширения является локомотивом бизнес-приложения и позволяет подключать в Microsoft Office все возможности платформы .NET. Я буду показывать именно решение второго уровня.

Если кто заинтересовался возможностями VSTO — очень много про них есть в msdn.

Псевдозадача

Создадим маленький пример, иллюстрирующий все вышесказанное. Пусть есть требование, согласно которому пользователь может выделять текст из документа Microsoft Word и получать этот выделенный текст в окошке пользовательского элемента (знаю, дурацкий пример, но более сложного делать не хочется, а менее сложный уже трудно придумать).

Более подробно. Пользователь запускает Microsoft Word. После запуска среди тулбаров пользователю будет доступен ТР — тулбар расширения (т.е., нашего расширения). На нем находится кнопка, по нажатию которой пользователю показывется окно с кнопкой «Получить текст» и элементом управления multiline textbox. Пользователь нажимает на кнопку «Получить текст», выделенный в документе текст копируется в textbox. Все счастливы.

Понятно, что для жизни пример малопригоден. Но создаваемая инфраструктура позволит довольно просто нарастить на него «мускулы», если это, конечно, кому-то потребуется.

Создание solution

После установки VSTO в студии при создании нового solution станут доступны Office проекты:

Будьте аккуратны с названием — оно одновременно будет являться названием root namespace и поменять его можно будет только ручками через выгрузку проекта.

На выходе получается солюшен с двумя проектами:

WordCAB — это, собственно, add-in. Второй — не менее важный, это deployment проект для модуля расширения. Почему он важен?

Дело в том, что установка модуля расширения на рабочие станции пользователей — весьма трудоемкая затея. Она заключается не только в том, чтобы просто скопировать библиотеки в нужную папку. Для успешного фукнционирования модуля требуется прописать кучу ключей в реестр. Если копнуть глубже, получается, что модуль расширения подключается к Microsoft Office как COM-библиотека, со всеми вытекающими. Кому интересно, все нужные ключи реестра можно поглядеть в deployment-проекте (Нажать правой кнопкой, View->Registry).

В классе ThisAddIn находится точка доступа в модуль:

public partial class ThisAddIn
{
  private void ThisAddIn_Startup(object sender, System.EventArgs e)
  {
   new AddInApplication().Run();
  }private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
  {
  }#region VSTO generated code/// <summary>
  /// Required method for Designer support - do not modify
  /// the contents of this method with the code editor.
  /// </summary>
  private void InternalStartup()
  {
    this.Startup += new System.EventHandler(ThisAddIn_Startup);
    this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
  }#endregion
}
* This source code was highlighted with Source Code Highlighter.

В ThisAddIn_Startup будем писать код. Если быть точнее, будем запускать CAB-приложение.

Что такое CAB-приложение?

Это класс такой. В нем есть точка запуска — метод Run(), и некоторая инфраструктура каркаса — в частности, имеются доступ к главному прецеденту «Приложение» (т.е., к основному, рутовому WorkItem) и возможности переопределить системные сервисы, добавляемые в приложения по-умолчанию. Этот класс уже реализован в базовом виде, а программисту требуется параметризировать его — указать тип рутового прецедента и тип главной формы:

internal sealed class AddInApplication : WindowsFormsApplication<AddInWorkItem, object>
{
  protected override void Start()
  {
  }
}
* This source code was highlighted with Source Code Highlighter.

В качестве типа главной формы (шелловой формы), указан object. Почему? Потому что приложение запускается в Microsoft Word, поэтому необходимости создавать главную форму нет. Форма Microsoft Word и есть главная форма, правда, без традиционных для нее возможностей.

По ходу пьесы мне пришло в голову немного прояснить модель приложения для модуля расширения в Microsoft Word.

Модель приложения Word Add-in

Итак, пользователь работает с документами. Более того, в единицу времени он может работать только с одним документом. Поэтому модуль расширения имеет четкий контекст — текущий открытый документ. (Кстати, в модели Microsoft Word VSTO этот документ обозначен как Globals.ThisAddIn.Application.ActiveDocument). Отсюда (я сделал) вывод (или упрощение) — показывать пользовательские бизнес-элементы имеет смысл в модальном режиме, поскольку в ином случае нарушается контекст. Элементы управления, открытые для одного документа, должны существовать только во время активности этого документа.

Пример — пользователь открыл документ и открыл его карточку (не в модальном режиме). Переключился на другой документ, а карточка осталась для предыдущего — нарушение контекста и целостности восприятия. Разумеется, подобное поведение контролировать можно (и даже нужно, в некоторых случаях), но модальность элементов управления и жесткая их привязка к текущему активному документу делает жизнь проще.

Modal Workspace

Пользовательские элементы управления в каркасе CAB показываются в специальных так называемых рабочих зонах (Workspace). Согласно описанному выше поведению, нам требуется показывать элементы управления в модальном режиме. Поскольку родной CAB-овский WindowWorkspace оказался дубоват и выглядит некрасиво, я написал простенький свой — приводить код не буду, потому что его пара-тройка экранов. Насладиться творчеством можно скачав solution с кодом (ссылка на архив приведена в конце статьи).

Регистрация модальной рабочей зоны происходит в методе InitializeServices() главного прецедента — AddInWorkitem:

Workspaces.Add(( new SimpleModalWorkspace() ), WorkspaceNames.MainWorkspace);* This source code was highlighted with Source Code Highlighter.

Итого: под ключем-идентификатором WorkspaceNames.MainWorkspace зарегистрировали модальную рабочую зону. Доступиться до нее теперь можно откуда угодно, где есть ссылка на главный прецедент: запрос интерфейса IWorkspace из коллекции Workspaces по указанному выше ключу. Все просто, как апельсин! (с) х/ф «Терминатор 2»

Фабрика элементов управления Microsoft Word

Речь пойдет о тулбаре Word и кнопках на нем. В VSTO это обертки над COM-объектами — CommandBar и CommandBarButton из пространства имен Microsoft.Office.Core. Жутко глючные штуки, если честно, особенно их анимация. Понять все тонкости и детали удалось только после ударного троллинга на форумах VSTO.

В чем идея — идея состоит в том, чтобы избавить программиста и разработчиков бизнес-модулей от необходимости работать с COM-обертками. Для этого, мы (то есть, я) интегрируем модель Misrosoft Word в механизм так называемых мест расширения (UiExtensionSite) каркаса CAB.

Механизм мест расширения состоит из функциональных связок-добавлений. Проще говоря, в коде необходимо визуализировать отношения подчиненных элементов. В нашем случае, эти отношения такие:

  1. В массив тулбаров Microsoft Word вставляется тулбар
  2. В коллекцию кнопок на тулбаре вставляется кнопка
  3. В кнопку уже ничего не вставляется (не рассматриваем случае комбобоксов, хотя они присутствуют), поэтому это терминальный объект

Итого, у нас получились три подчиненные друг другу сущности:

  1. IWordCommandBarContainer — контейнер тулбаров Microsoft Word
  2. IWordCommandBar — контейнер кнопок на тулбаре
  3. IWordButton — кнопка

К слову сказать, помимо определения элементов управления Word конкретные типы интерфейсов IWordCommandBar и IWordButton являются адаптерами к упомянутым выше CommandBar и CommandBarButton соответственно.

Для того, чтобы каркас понял, что, куда и, главное, каким образом вставлять, ему необходимо зарегистировать фабрику адаптеров для пользовательских элементов управления. В нашем случае, вставлять можно тулбары (в коллекцию тулбаров) и кнопки (в коллекцию кнопкок на тулбаре). Поэтому, регистрировать фабрику адаптеров нужно для IWordCommandBarContainer и для IWordCommandBar. Потом, когда пользователь будет получать место расширения, каркас будет искать инстансы классов добавления подчиненных элементов для этого места расширения, после чего использовать их по назначению — добавлять элементы. Ну а чтобы не заморачиваться, эти инстансы порождаются фабрикой:

public class CommandBarUIAdapterFactory : IUIElementAdapterFactory
{
  public IUIElementAdapter GetAdapter(object uiElement)
  {
    if ( uiElement is IWordCommandBarContainer ) //тулбары
      return new CommandBarUIAdapter(( IWordCommandBarContainer )uiElement);
    if ( uiElement is IWordCommandBar ) //кнопки в тулбарах
      return new CommandBarButtonUIAdapter(( IWordCommandBar )uiElement);throw new ArgumentException("uiElement");
  }public bool Supports(object uiElement)
  {
    return ( uiElement is IWordCommandBarContainer ) || ( uiElement is IWordCommandBar );
  }
}
* This source code was highlighted with Source Code Highlighter.

А вот реализация коллекции тулбаров (с возможностью добавить новый!)

internal class BarCollection : IWordCommandBarContainer
{
  #region IWordCommandBarContainer Memberspublic void AddBar(IWordCommandBar bar)
  {
    CommandBar commandBar = null;
    //todo: сделать так, чтобы второй раз создать нельзя было
    try
    {
      commandBar = Globals.ThisAddIn.Application.CommandBars[bar.Id];
    }
    catch ( ArgumentException ) { }if ( commandBar == null )
      commandBar = Globals.ThisAddIn.Application.CommandBars.Add(bar.Id, ( object )MsoBarPosition.msoBarTop, _null, true);

    commandBar.Visible =

true;
  }private object _null = System.Reflection.Missing.Value;#endregion
}* This source code was highlighted with Source Code Highlighter.

С кнопками на тулбарах аналогично. Гляньте код на досуге.

Пришлось, к слову, создавать фабрику самих кнопкок и тулбаров (см. IWordUIElementFactory). Дело в том, что модули CAB работают с интерфейсами IWordCommandBar и IWordButton. Конкретные типы этих интерфейсов находятся в сборке VSTO Word-AddIn и завязаны на Microsoft.Office.Core. Поэтому, чтобы в модулях (где отсутствует ссылка на Office) была возможность получать инстансы указанных элементов, создается фабрика. Она регистрируется в главном WorkItem.

Регистрация фабрик и места расширения под тулбары:

Services.AddNew<WordUIElementFactory, IWordUIElementFactory>();
IUIElementAdapterFactoryCatalog factoryService = base.Services.Get<IUIElementAdapterFactoryCatalog>();
factoryService.RegisterFactory(new CommandBarUIAdapterFactory());

UIExtensionSites.RegisterSite(UIExtensionSiteNames.WordBarsSite,

new BarCollection());* This source code was highlighted with Source Code Highlighter.

Если вы запутались, извините:) Я сам понимаю, что тут без поллитры не разберешься. (Если подебажиться, то многое становится понятным.)

Модуль CAB

Это обычная сборка. В ней должен быть класс ModuleInit. В этом классе есть ссылка на главный прецедент AddInWorkitem и, как следствие, есть доступ ко всему добру, про которое я писал выше.

Задача модуля CAB такова — подгрузиться к главному преценденту, вставить тулбар, вставить кнопку на него, описать обработчик кнопки:

Voila:

UIExtensionSite site = _rootWorkItem.UIExtensionSites[UIExtensionSiteNames.WordBarsSite];
IWordCommandBar mainBar = site.Add<IWordCommandBar>(_factory.CreateBar("AddInToolbar"));

IWordButton btn = _factory.CreateButton(mainBar, CommandNames.OpenForm, ToolStripItemDisplayStyle.ImageAndText,

"Открыть окно",
  "Открыть форму просмотра Custom Control", Resources.OpenForm, false);

mainBar.AddButton(btn);
btn.Click +=

new EventHandler<WordButtonClickArgs>(ButtonClick);* This source code was highlighted with Source Code Highlighter.

Обработчик кнопки будет создавать пользовательское окно с кнопкой «получить текст» и текстовым полем, после чего показывать его (применив специальный модификатор показа WindowSmartPartInfo) в ранее упомянутой рабочей зоне (которую, как мы помним, я зарегистрировал в прецеденте под ключем WorkspaceNames.MainWorkspace):

private void ButtonClick(object sender, WordButtonClickArgs e)
{
  object smartPart = _rootWorkItem.SmartParts.AddNew<SampleSmartPart>();
  WindowSmartPartInfo info = new WindowSmartPartInfo();
  info.FormStartPosition = FormStartPosition.CenterScreen;
  info.MaximizeBox = false;
  info.MinimizeBox = false;
  info.Resizable = false;
  info.Title = "Custom Control";
  info.ShowInTaskbar = false;
  _rootWorkItem.Workspaces[WorkspaceNames.MainWorkspace].Show(smartPart, info);
}
* This source code was highlighted with Source Code Highlighter.

Подгрузка модуля

Здесь я сделал финт коленом. Обычно модули подгружаются в каркас через специальный декларативный формат подгрузки — так называемый ProfileCatalog. Обычно, это хороший способ подключить все, что нужно. Но, учитывая суровые советские реалии, имеется ненулевая вероятность того, что программисту потребуется недекларативная логика подключения модуля. Для этого мы будем переопределять специальный сервис перечисления подгружаемых модулей — IModuleEnumerator. Я сделал его очень простым — он глядит в папку исполняемой сборки и ищет в ней модуль под названием CustomModule.dll. Находит, и подгружает. Ну, или не подгружает, если не находит:

public class CustomModuleEnumerator : IModuleEnumerator
{
  #region Constants
  private const string ModuleName = "CustomModule.dll";
  #endregion#region IModuleEnumerator Memberspublic IModuleInfo[] EnumerateModules()
  {
    List<IModuleInfo> result = new List<IModuleInfo>();string path = GetModulePath(ModuleName);
    if ( File.Exists(path) )
      result.Add(new ModuleInfo(ModuleName));
    return result.ToArray();
  }#endregion#region Private Methods
  private string GetModulePath(string assemblyFile)
  {
    if ( !Path.IsPathRooted(assemblyFile) )
      assemblyFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, assemblyFile);return assemblyFile;
  }
  #endregion
}
* This source code was highlighted with Source Code Highlighter.

Сейчас эта служба очень простая, но при желании, разумеется, в нее можно нагородить хоть черта лысого. Например, подгружать модули из базы данных:)

Перегрузить эту службу нужно в классе приложения CAB:

protected override void AddServices()
{
  base.AddServices();

  RootWorkItem.Services.Remove<IModuleEnumerator>();
  RootWorkItem.Services.AddOnDemand<CustomModuleEnumerator, IModuleEnumerator>();
}

* This source code was highlighted with Source Code Highlighter.

Доктор, я устал. Что получилось?

Получилось то, что задумывалось в изначальном псевдопримере:

В тулбарах Microsoft находится наш тулбар, на нем висит кнопка с иконкой, по событию нажатия кнопки вылезает пользовательский элемент управления, в текстовое поле которого с помощью кнопки можно загнать выделенный в документе текст:) Тривиально, но обратите внимание, как ничтожна связность между объектами! Хочется подметить, что у каркаса, при должном обращении, идеальная code maintainability.

Внимание! Подводные камни!

Security

Когда скомпилируете и запустите пример, ничего не запустится. Более того, вывалится сообщение, которое скажет вам — дескать, не хватает прав на запуск стороннего модуля (в нашем случае, CustomModule.dll). Проблема в том, что по умолчанию приложение VSTO (в режиме разработки!) дает Full Trust права только на исполняемую сборку и на все сборки, от которых она зависит — т.е. на WordCAB.dll. Для того, чтобы разрешить использование кода сторонних библиотек, выполните следующее действие:
C:WindowsMicrosoft.NETFrameworkv2.0.50727>caspol -u -ag All_Code -url "D:ProjectsWordCABbinDebug*" FullTrust
Microsoft (R) .NET Framework CasPol 2.0.50727.3053
Copyright (c) Microsoft Corporation. All rights reserved.

The operation you are performing will alter security policy.
Are you sure you want to perform this operation? (yes/no)
yes
Added union code group with "-url" membership condition to the User level.
Success

Вместо "D:ProjectsWordCABbinDebug*" нужно указать папку, из которой производится запуск Add-in. Не забудьте про звездочку.

Add-in перестал загружаться!

Иногда, когда в модуле возникает необработанный Exception, Word блокирует исполнение этого модуля при следующей загрузке. Зайдите Help->About->Disabled Items и посмотрите, нет ли в списке вашего расширения. Если есть, уберите его оттуда. При следующем Run-Debug он появится.

Как убрать

Удаление расширения не такое тривиальное. Вытащите кнопку COM-AddIns на тулбар Microsoft Word. Для этого надо зайти в Tools->Customize->Tools->(Drag’n’Drop)COM-AddIns. Кликните на нее и снимите галку с вашего расширения. Он выгрузится. Чтобы заново подгружать, наоборот, выставите галку обратно. Другой способ — зайти в панель управления и удалить из программ.

А что с Word 2007?

Add-in замечательно подгружается в Ribbon на последнюю вкладку.

Там еще кучка мелких подводных камней. Но я тут и так уже настолько много написал, что не уверен, что кто-нибудь до конца дочитает:)

Где взять код

github.com/head-thrash/VSTO-CAB

Выводы

В общем, можно взять VSTO, Microsoft Office 2003, .NET Framework 2.0 и сделать решение. Базовое решение, на которое можно нарастить функционал, я привел в этой статье. Кто хочет — пользуйтесь на здоровье. Буду рад ответить на вопросы и подправить неточности, если здесь такие будут. Спасибо огромное за внимание!

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.

Pieter van der Westhuizen

Posted on Monday, August 19th, 2013 at 7:22 am by .

In today’s article we’ll take a look at how you can combine information and text from various existing Microsoft Word documents into a single document. We’ll create a Word add-in that will allow the user to select and insert different paragraphs from one or more existing Word documents, into another document. To insert a paragraph into the current document, all the user needs to do is double-click on it in the list of existing paragraphs.

  • Creating the Word COM add-in project
  • Adding an advanced Word task pane
  • Adding code to add a new document
  • Getting a list all paragraphs in a Word document
  • Inserting an existing paragraph into a document
  • Testing the Word add-in

Creating the Word COM add-in project

Start by creating a new ADX COM Add-in in Visual Studio using Add-in Express for Office and .net.

Creating a new Word COM Add-in in Visual Studio

The “New Microsoft Office COM Add-in” project wizard will start. Select your preferred programming language and the minimum supported Office version your add-in needs to support. I will choose C# and Office 2010 for this add-in, though VB.NET and C++.NET are supported as well.

Selecting the programming language and the minimum supported Office version

Click ‘Next’ and select the supported application, which in this scenario will only be Microsoft Word.

Selecting Microsoft Word as the only supported application

Adding an advanced Word task pane

Next, add a new ADX Task pane to your Word add-in project, which is an advanced version-neutral Office task pane that supports all versions of Word, from 2000 to 2013.

Adding an advanced task pane to the Word add-in project

The custom task pane will form the main UI for our Word add-in and will contain a list view control to list all the selected Word documents, a button to add a new document as well as another list view control that will display all the paragraphs in the selected Word document.

The final design of the custom task pane, should resemble the following image:

The final design of the custom task pane

Adding code for adding a new Word document

With the task pane open in the Visual Studio designer, double-click the Add Document button to generate a Click event handler stub. Add the following code to the event handler:

private void btnAddDocument_Click(object sender, EventArgs e)
{
    if (diagOpen.ShowDialog() == DialogResult.OK)
    {
        ListViewItem itemDoc = new ListViewItem(diagOpen.SafeFileName, 0);
        itemDoc.Tag = diagOpen.FileName;
        lvDocs.Items.Add(itemDoc);
    }
}

The code listed above will open a file dialog, with which the user can select a MS Word document. The OpenFileDialog component is a standard Windows Form component I’ve added to the task pane control earlier.

Getting a list of all paragraphs in a document

Next, we need to add code to show a list of paragraphs when the user clicks on the document in the documents list view. To do this, double-click on the first list view we’ve added, to generate a method stub for its SelectedIndexChanged event:

private void lvDocs_SelectedIndexChanged(object sender, EventArgs e)
{
    Word.Application wordApp = null;
    Word.Documents documents = null;
    Word.Document doc = null;
    Word.Paragraphs paragraphs = null;
    Word.Paragraph paragraph = null;
    Word.Range paragraphRange = null;
    ListViewItem selectedItem = null;
    string documentPath = String.Empty;
    bool isVisible = false;
    object missingType = Type.Missing;
 
    try
    {
        if (lvDocs.SelectedItems.Count == 0) return;
        selectedItem = lvDocs.SelectedItems[0];
        documentPath = selectedItem.Tag.ToString();
 
        wordApp = (Word.Application)this.WordAppObj;
        wordApp.ScreenUpdating = false;
        try
        {
            documents = wordApp.Documents;
            doc = documents.Open(documentPath, missingType, missingType,
                missingType, missingType, missingType, missingType,
                missingType, missingType, missingType, missingType,
                isVisible);
 
            paragraphs = doc.Paragraphs;
            lvParagraphs.Items.Clear();
            for (int i = 1; i <= paragraphs.Count; i++)
            {
                paragraph = paragraphs[i];
                paragraphRange = paragraph.Range;
                ListViewItem lvItemParagraph = 
                    new ListViewItem(paragraphRange.Text, 1);
                lvItemParagraph.Tag = documentPath;
                lvParagraphs.Items.Add(lvItemParagraph);
            }
            doc.Close();
        }
        finally
        {
            wordApp.ScreenUpdating = true;
        }
    }
    finally
    {
        if (paragraphRange != null) Marshal.ReleaseComObject(paragraphRange);
        if (paragraph != null) Marshal.ReleaseComObject(paragraph);
        if (paragraphs != null) Marshal.ReleaseComObject(paragraphs);
        if (doc != null) Marshal.ReleaseComObject(doc);
        if (documents != null) Marshal.ReleaseComObject(documents);
    }
}

Inserting an existing paragraph into a document

Lastly, when the user double-clicks on one of the paragraphs listed in the list view, it should be added to the current document. Create an event handler for the paragraphs list view by selecting it and double-clicking next to its DoubleClick event in the property window.

Creating an event handler for the paragraphs list view

Add the following code to the DoubleClick event handler:

private void lvParagraphs_DoubleClick(object sender, EventArgs e)
{
    Word.Application wordApp = null;
    Word.Document wordDoc = null;
    Word.Selection selection = null;
    Word.Range selectionRange = null;
    Word.Range endOfParagraphRange = null;
    Word.Hyperlinks selectionHyperlinks = null;
    int endOfParagraphPosition = 0;
    ListViewItem selectedItem = null;
    string paragraphText = string.Empty;
 
    try
    {
        if (lvParagraphs.SelectedItems.Count == 0) return;
 
        selectedItem = lvParagraphs.SelectedItems[0];
        selectedItem.ImageIndex = 2;
        paragraphText = selectedItem.Text;
 
        wordApp = (Word.Application)this.WordAppObj;
        wordDoc = wordApp.ActiveDocument;
        selection = wordApp.Selection;
        selectionHyperlinks = selection.Hyperlinks;
        selectionRange = selection.Range;
        selectionRange.Text = paragraphText;
 
        endOfParagraphPosition = selectionRange.StoryLength - 2;
        endOfParagraphRange = wordDoc.Range(
            endOfParagraphPosition, endOfParagraphPosition);
        selectionHyperlinks.Add(
            endOfParagraphRange, 
            selectedItem.Tag.ToString(), 
            Type.Missing, 
            "Open Source Document", 
            char.ConvertFromUtf32(8594));
    }
    finally
    {
        if (wordDoc != null)
            Marshal.ReleaseComObject(wordDoc);
        if (endOfParagraphRange != null)
            Marshal.ReleaseComObject(endOfParagraphRange);
        if (selectionHyperlinks != null)
            Marshal.ReleaseComObject(selectionHyperlinks);
        if (selectionRange != null)
            Marshal.ReleaseComObject(selectionRange);
        if (selection != null)
            Marshal.ReleaseComObject(selection);
    }
}

The code above will not only insert the paragraph into the document, but also add a hyperlink to the end of the paragraph linking back to the source document. This is accomplished by using the Add method of the Document objects’ Hyperlinks collection.

Testing the Word add-in

Build, register and run your Word addin project. The final result should be similar to the following image:

The Document Builder add-in in Word 2013

Thank you for reading. Until next time, keep coding!

Available downloads:

This sample Word add-in was developed using Add-in Express for Office and .net:

C# Document Builder add-in for Word

Word add-in development in Visual Studio for beginners:

  • Part 1: Word add-in development – Application and base objects
  • Part 2: Customizing Word UI – What is and isn’t customizable
  • Part 3: Customizing Word main menu, context menus and Backstage view
  • Part 4: Creating custom Word ribbons and toolbars
  • Part 5: Building custom task panes for Word 2013 – 2003
  • Part 6: Working with Word document content objects
  • Part 7: Working with Word document designs, styles and printing
  • Part 9: Using custom XML parts in Word add-ins
  • Part 10: Working with Word document properties, bookmarks, content controls and quick parts
  • Part 11: Populating Word documents with data from external sources
  • Part 12: Working with Microsoft Word templates

Понравилась статья? Поделить с друзьями:
  • Word add in events
  • Word add in download
  • Word add in cards
  • Word add in 2010
  • Word add dictionary language