Tallan's Blog

Tallan’s Experts Share Their Knowledge on Technology, Trends and Solutions to Business Challenges

Developing Your First VSTO Add-In

Eric Mattson

Anyone planning to develop a VSTO Add-In may come to realize that documentation and examples are scarce or nonexistent. Recently I have found myself in this exact predicament as I have been developing a Microsoft Word VSTO Add-In to be used in the legislative drafting process. Throughout development, my team has created a handful of solutions for both simple and complex tasks. Many of these solutions could not be found online, and as a result, required significant research and testing. Here are a few examples that I wish I had when I started to develop a VSTO add-in that will hopefully jump-start your VSTO development.

Overriding Default Save Behavior

One of the core features that was required for our Word Add-In was version control. In order to support this functionality, we would need to override the default behavior of Word’s save functionality. To create a fluid and seamless experience for the end user, the server would create and deliver a document to the user, which would open in Word. From there we needed a solution to save those changes back to the server that would be equally seamless. To support that ease of use, we needed the save functionality to act as an API call which would save the document back to the server.

Take a look at the code below which lays out how to hook into, and modify, Word’s save event. The ThisAddIn_Startup function should be found within the ThisAddin.cs file:

private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
    Application.DocumentBeforeSave += Application_DocumentBeforeSave;
}

private void Application_DocumentBeforeSave(Word.Document Doc, ref bool Cancel)
{
    bool addInEnabled = true;
    if (addInEnabled)
    {
        if (Doc.Saved)
        {
            Cancel = true;
            return;
        }
        if (SaveDocument(Doc, out Cancel))
        {
            Doc.Saved = true;
        }
    }
}

public bool SaveDocument(Word.Document Doc, out bool cancel)
{
    var success = HostConnector.Save(Doc).Result;
    cancel = success;
    return success;
}

The first step is to include and define the Application_DocumentBeforeSave event. First, we want to make sure we are modifying a document intended for our VSTO Add-In. For the purpose of this snippet, I have reduced this to a simple Boolean. One thing to keep in mind while looking at this code: if Cancel returns true, Word will abort its default save behavior. This means that if there is some sort of failure with our HostConnector Save, Word will then allow the user to save locally.

The way the code snippet currently works, every document saved on a machine with this Add-In installed will use this modified save behavior. To make sure that only intended documents use this behavior, we can utilize custom XML parts.

Creating and Retrieving Custom Document Properties

As mentioned above, our documents are not brand-new documents created locally. The documents we want to interact with our VSTO Add-In are server generated and then delivered to the user. With this workflow, we can utilize the following server side and client side (VSTO) code:

First, we want to declare our custom document properties and incorporate them. This can be done through AddCustomXmlPart from the MainDocumentPart class to the documents generated XML.

var customProperties = new Dictionary<string, string>
{
    {"addInEnabled", true.ToString() },
    {"username", Username },
};

MainDocumentPart docPart = myDocument.MainDocumentPart;

foreach (var part in customProperties)
{
    var xmlPart = docPart.AddCustomXmlPart(CustomXmlPartType.CustomXml);

    using (var outputStream = xmlPart.GetStream())
    {
        using (var writer = new StreamWriter(outputStream))
        {
            writer.Write(part.Value);
        }
    }
}

Now that we have these values defined in the metadata of our delivered document, we can retrieve them with the following method:

public static T GetCustomProperty<T>(this Document doc, string propertyName)
{
    try
    {
        var properties = (DocumentProperties)doc.CustomDocumentProperties;

        string result = (from DocumentProperty property
            in properties
                         where property.Name == propertyName
                         select property.Value.ToString())
            .FirstOrDefault();

        if (result == null)
        {
            return default(T);
        }

        return (T)Convert.ChangeType(result, typeof(T));
    }
    catch (Exception)
    {
        return default(T);
    }
}

When the server-side code is integrated into your document generation method, this will become a clean way to pass variables from the server into the VSTO client. As shown above, we can pass in a Boolean to activate the overridden save function over the default Word behavior, along with a username for the client to correctly label saves and for other networking needs.

Tracking Document Contents: Content Controls vs. Bookmarks

As a final note, I would like to discuss methods for tracking content within the document. The two simplest options are content controls and bookmarks. Content controls are commonly used in forms or templates, but also can be used to wrap text, paragraphs, etc. As a result, content controls have superior tracking and functionality capabilities. Bookmarks are another simpler tracking tool. Bookmarks can wrap anything in OOXML and simply represent a bookmark start and bookmark end. As a result, they have significantly fewer capabilities for front facing functionality but are far more efficient in large-scale documents.

When work for our VSTO Add-In began, the initial instinct was to use content controls for their superior visibility and native right-click context functions. When implemented correctly, content controls fulfilled their purpose quite well. We were able to bind functions to right-click context menus based on selection or based on an entire parent content control. However, performance suffered when testing involved larger and larger documents. Eventually, the decision was made to rebuild the application for performance reasons and to make the overall product more robust. As a result, a lot of our design choices needed to be abandoned because the product was built from the ground up on content controls.

Once refactoring began, we decided to rebuild with bookmarks. Bookmarks were extremely fast compared to the content controls. While transitioning over to bookmarks, we had to rethink many of the ways bookmarks are tracked and relate them to their contextual metadata. To maximize performance, we offloaded as much processing to the server as possible as our tracking and processing methods become incrementally more complicated. As you become more accustom to Word’s functionality and limitations, it becomes clear that to maximize the system’s performance; you must minimize Word VSTO’s functionality to the smallest scope possible.

Hopefully you found this article helpful in getting started with your first VSTO Add-In Project.


With over 30 years of developing, designing and delivering custom software applications nationwide, learn how Tallan can help you with your software development needs today, Contact Us directly for more information or see us in-person at one of our many Events!

No comments

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

\\\