Ty Anderson

Using custom XML parts in Word add-ins

Microsoft Word contains enough tools to make any writer productive. But writers are peculiar people and they need and require more features. Writers that are also developers have the power to enable their peculiarities and thrust them upon others.

Productive writers

A good document complies with certain qualities or standards. You can check them off and enforce your standards if you know how to employ the right technology. For what I want to accomplish today, we need to employ custom XML parts.

What are custom XML parts?

Custom XML parts are chunks of XML that reside within a Word document. They are not part of the document, per se, because they are not visible to the user. Starting with Office 2007, the Office file formats are XML-based and are comprised of XML parts.

So, just think of custom XML parts like you do custom document properties. They aren’t built-in and exist for your purposes.

What can you do with XML parts?

Unlike document properties, you can store lots of data in a single part. This is due to the fact that you store a chunk of XML in a custom XML part. So, fill that sucker up with any and all data that fits your needs. Personally, I think they are perfect for storing process data related to the document. For example, a document to do list.

Hmmm… sounds like the basis for a solid code sample.

When should you use XML parts?

The short answer is whenever you want if you are using Word 2007 or later. The longer answer is you should use them when your requirements are more complex than storing data in Word document properties.

What do I need to start developing with an XML part?

Well, besides the obvious tools of Visual Studio, Office, and (of course) Add-in Express, you need the following:

1. An idea
2. Gumption
3. Time

And the skills. No doubt you can’t make it happen if you don’t know how to do it. And lucky of you, providence has smiled upon us all. I have a solid code sample to show just how to make effective use of custom XML parts in Microsoft Word.

A sample Word add-in that uses custom XML parts

In this sample Word addin, I’ll show you how to build a custom Word task pane that contains a document-specific to-do list. This to-do list’s state will reside in its corresponding Word document as a custom XML part. When the user saves the document, we will insert the to-do list’s state. When the user opens the document, we will retrieve the custom XML part and display it in the custom task pane.

How do we retrieve it you ask? I can’t tell you now. You must read on to learn the secret.

Before we start, you need to create a new Add-in Express based COM Add-in project in Visual Studio. I’ll use Visual Studio 2012 with Add-in Express for Office and .NET.

Creating the Word COM add-in project

Go ahead and create the project (you can find the detailed step-by-step instructions in this article Building Office COM add-ins) and open the AddinModule in design view. After you do that, the fun can begin. By the way, I named my sample project UsingXMLParts. You can do the same but it isn’t a requirement.

Add the necessary Add-in Express components

We need to add three components to the project… we’ll take them one-at-a-time.

Advanced Word task pane

In the Visual Studio solution explorer window, right-click the project name and click Add. In the Add New Item dialog…

  1. Go to the Add-in Express Items, select Word, and select ADX Word Task Pane.
  2. Name the pane DocToDoPane and click OK.

The DocToDoPane will look like the image below. I’ve added the controls and their properties. Go ahead and add them.

The design of the To-Do pane

Note. I apologize for the use of the Telerik RADTreeview control… but not really. For code samples, I really try to use only native WinForm controls. But, in this case, the Telerik control simplifies the scenario and allowed me to focus solely on custom XML parts. If I were to use the WinForm treeview, we would need to write a lot of “not-germane-to-the-topic” code. I didn’t want to write that code so, Telerik it is.

Word Task Panes Manager

After completing the task pane’s design, we need to open the AddInModule design view and add an ADXWordTaskPanesManager. Just right click the AddInModule and select it from the context menu.

Adding Word Task Panes Manager

After adding the task panes manager, select it and open its Items collection. In the ADXWordTaskPanesCollection Editor dialog, add a new collection item and configure its properties to match this image:

Configuring the properties of the newly added WordTaskPanesCollection's item

Word Events component

This component allows us to write code that responds to Microsoft Word events. In the AddinModule design view, ADXWordEvents icon in the AddinModule toolbar. You don’t need to do anything with this component. The code we write next will take care of it..

Write code that works with custom XML parts

The code resides in two locations:

  1. DocToDoPane: the code for saving and retrieving XML parts resides here.
  2. AddinModule: the code for responding to Word events resides here.

There’s more to it than this but this fulfills Pareto’s law.

Writing code for the DocToDoPane

To me, the logical place for creating, saving, and retrieving custom XML parts is in the task pane. I think this because the XML parts display in the task pane via the treeview control. You might think differently.

SaveToDoList function

A user will work with a word document and edit the task pane’s to-do list. This To-Do list resides in a tree view control. We need to save the state of this treeview in a custom XML Part. This what the SaveToDoList function does.

Friend Function SaveToDoList() As Boolean
    'Wrapper method for a call from AddinModule methods.
    'Calls the AddCustomXmlPartToDocument function
    Try
        Dim wApp As Word.Application = TryCast(Me.WordAppObj, Word.Application)
 
        AddCustomXmlPartToDocument(wApp.ActiveDocument, RadTreeView1.TreeViewXml)
 
        Return True
    Catch ex As Exception
        Return False
    End Try
End Function

The To-Do list has a property called WordAppObj. This is the Word Application object. We use this object to gain access to the ActiveDocument and pass it to the AddCustomXMLPartToDocument method. We also pass the RadTreeView‘s XML value.

AddCustomXMLPartToDocument method

The name says it all. This method adds a custom XML part to a Word document.

Private Sub AddCustomXmlPartToDocument(ByVal document As Word.Document, xml As String)
    '1-Creates an XML Part and saves into the doc's CustomXMLParts collection
    '2-Creates a custom document property to store the CustomXMLPart's ID.
    Dim xmlString As String = xml
    Dim treeviewXMLPart As Office.CustomXMLPart = document.CustomXMLParts.Add(xmlString)
 
    'Add document property
    Try
        document.CustomDocumentProperties.Add( _
            "treeViewToDO", False, _
            Office.MsoDocProperties.msoPropertyTypeString, treeviewXMLPart.Id)
 
    Catch ex As Exception
        document.CustomDocumentProperties("treeViewToDO") = treeviewXMLPart.Id
    End Try
 
    Marshal.ReleaseComObject(treeviewXMLPart)
End Sub

The method takes the passed Word document and XML string and adds a new CustomXMLPart to the document’s CustomXMLParts collection.

But this isn’t all! The method also creates a custom document property. This property stores the custom XML part’s ID. This is the secret sauce that allows us to retrieve the part later.

LoadToDoList function

This method starts the custom XML part retrieval process. It mimics aspect of SaveToDoList because it grasps the Word Application object to retrieve the ActiveDocument. It then calls the RetrieveCustomXMLPart method to retrieve the custom XML part.

Private Const tempXMLFilePath As String = "C:\_projects\TVTOC.xml"
 
Friend Function LoadToDoList(partID As String) As Boolean
    'Populates the TreeView control by retrieving the CustomXML from the document and… 
    'saving to the file system (because it fails if I don't do this and try to
    'load straight from memory… no idea why) and… 
    'calling the treeview's LoadXML method.
    Try
        Dim wApp As Word.Application = TryCast(Me.WordAppObj, Word.Application)
 
        Dim result As String = RetrieveCustomXMLPart(wApp.ActiveDocument, partID)
        Dim writer As New StreamWriter(tempXMLFilePath, False)
        writer.Write(result)
        writer.Close()
        If result <> "" Then
            RadTreeView1.LoadXML(tempXMLFilePath)
        End If
 
        Return True
    Catch ex As Exception
        Return False
    End Try
End Function

With the XML in-hand, the method writes its content to a file (tempXMLFilePath) and the loads it into the RadTreeView. This seems silly to me but… when I try to load the XML directly from memory, the RadTreeView control would fail. So, I went with a solution that worked. And… it works well.

LoadFreshList list method

This method exists to clear the tree view control so that no nodes have checks in their check boxes.

Friend Function LoadFreshList()
    'Friendly helper method to clear the treeview for a new document.
    RadTreeView1.ClearSelection()
End Function

This situation is relevant anytime a user creates a new Word document.

RetrieveCustomXMLPart method

This method retrieves the custom XML part that matched the passed partID string.

Private Function RetrieveCustomXMLPart(doc As Word.Document, partID As String) As String
    'Retrieves the CustomXMLPart using the passed partID.
 
    Dim treeviewXMLPart As Office.CustomXMLPart = Nothing
    Try
 
        treeviewXMLPart = doc.CustomXMLParts.SelectByID(partID)
        Return treeviewXMLPart.XML.ToString
    Catch ex As Exception
        Return ""
    End Try
    Marshal.ReleaseComObject(treeviewXMLPart)
End Function

This method really makes the use of the document property to store the ID look really smart. I love it when a plan comes together.

Writing code for the Addin Module

The code in the AddinModule exists to respond to relevant document events. In addition, it is the place for helper methods that call the “friendly” methods residing in the DocToDoPane. I didn’t mention it before but maybe you noticed. Some of the methods in the DocToDoPane use the Friend modifier. This is done so that we can call them form the events code residing in the AddInModule.

IsXMLSupported function

Not all documents support custom XML parts. As I mentioned before, only the XML-based Office documents support them. This means existing “.docx” document or new document created with Word 2007, Word 2010 or 2013.

Private Function IsXMLSupported(doc As Word.Document) As Boolean
    'Quick and Dirty rule for determining if we can inject a 
    'CustomXML Part into the document.
    If Right(doc.FullName, 4) = "docx" Then
        Return True
    ElseIf Right(doc.FullName, 3) = "doc" Then
        Return False
    ElseIf doc.Application.Version >= 12 Then
        Return True
    Else
        Return False
    End If
End Function

This method runs through a series of tests to determine if the document supports XML. We need this test to save some cycles in our code… as you will see.

RefreshToDoPane method

This is the first method to call one the DocToDoPane’s “friendly” methods. It starts by creating a reference to the current instance of the Word task pane.

Private Sub RefreshToDoPane(doc As Word.Document, display As Boolean)
    Dim toDoPane As DocToDoPane
    toDoPane = TryCast( _
        AdxWordTaskPanesCollectionItem1.CurrentTaskPaneInstance, DocToDoPane)
    If display Then
        Dim xmlPartID As String = HasToDoList(doc)
        toDoPane.LoadToDoList(xmlPartID)
    End If
    toDoPane.Visible = display
End Sub

With the task pane in-hand, the method checks for the existence of a To-Do list (by calling HasToDoList). If a To-Do list exists, the methods calls the task pane’s LoadToDoList method and passes the custom XML Part’s ID to it.

HasToDoList function

So just how do we determine whether or not a document has a To-Do list? Well, we check the custom document properties for the existence of the treeViewToDo property. If it exists, we assume the document as a To-Do list.

Friend Function HasToDoList(doc As Word.Document) As String
    'Check for the treeViewToDO custom property.
    'If it exists, we should have a customXML part to retrieve
    'and display in the DocToDoPane
    Try
        'if this works, we have a Todo list
        Dim prop As Office.DocumentProperty = _
            doc.CustomDocumentProperties.Item("treeViewToDO")
        If Not prop Is Nothing Then
            Dim propValue As String = prop.Value
            Marshal.ReleaseComObject(prop)
 
            Return propValue
        End If
    Catch ex As Exception
        Return ""
    End Try
End Function

It’s simple but elegant.

SaveToDoPane method

This is another method that calls a task pane “friendly” method… in this case, the SaveToDoList method.

Private Sub SaveToDoPane(doc As Word.Document)
    'Just a wrapper method to call the docToDoPane's Save method
    If IsXMLSupported(doc) Then
        Dim toDoPane As DocToDoPane
        toDoPane = TryCast( _
            AdxWordTaskPanesCollectionItem1.CurrentTaskPaneInstance, DocToDoPane)
        toDoPane.SaveToDoList()
    End If
End Sub

Before it makes this call, it needs to grab the instance of the custom task pane that belongs to the passed Word document.

The DocumentBeforeSave event

This event occurs before a document saves. It’s the ideal location for the code to trigger the saving of the To-Do list’s state.

Private Sub adxWordEvents_DocumentBeforeSave( _
    sender As Object, e As ADXHostBeforeSaveEventArgs) _
    Handles adxWordEvents.DocumentBeforeSave
 
    Try
        SaveToDoPane(TryCast(e.HostObject, Word.Document))
    Catch ex As Exception
    End Try
End Sub

The event code references the document being saved and calls the SaveToDoPane method.

The DocumentOpen event

When the user opens a Word document, we need to check if the document supports XML.

Private Sub adxWordEvents_DocumentOpen(sender As Object, hostObj As Object) _
    Handles adxWordEvents.DocumentOpen
 
    Dim doc As Word.Document = TryCast(hostObj, Word.Document)
    If IsXMLSupported(doc) Then
        RefreshToDoPane(doc, True)
    Else
        RefreshToDoPane(doc, False)
    End If
End Sub

If the XML is in-play, we need to refresh the custom task pane and display it. If not, we need to hide the task pane. The RefreshToDoPane method handles both scenarios.

The DocumentBeforeClose event

When a document closes, we need to call the RefreshToDoPane method and tell it to hide the task pane.

Private Sub adxWordEvents_DocumentBeforeClose(sender As Object, _
    e As ADXHostBeforeActionEventArgs) _
    Handles adxWordEvents.DocumentBeforeClose
 
    Dim doc As Word.Document = TryCast(e.HostObject, Word.Document)
    RefreshToDoPane(doc, False)
End Sub

This is especially relevant when Word remains open but does not have any documents open. In this situation, it does not make sense to display the task pane. The code above prevents that kind of silliness.

The NewDocument event

When the user creates a new document, we need to check for XML support and respond accordingly.

Private Sub adxWordEvents_NewDocument(sender As Object, hostObj As Object) _
    Handles adxWordEvents.NewDocument
 
    Dim doc As Word.Document = TryCast(hostObj, Word.Document)
    If IsXMLSupported(doc) Then
        Dim toDoPane As DocToDoPane
        toDoPane = TryCast( _
            AdxWordTaskPanesCollectionItem1.CurrentTaskPaneInstance, DocToDoPane)
        toDoPane.LoadFreshList()
    End If
End Sub

The even calls the IsXMLSupported function. If the document supports XML, then we grab the current task pane instance and create a fresh to-do list.

Awesome. This is how our custom task pane with a To-Do list looks like in Word 2013:

The custom task pane with a To-Do list in Word 2013

***

Custom XML Parts enable you to embed data into Word documents. As you see in this sample Word addin, custom XML parts provide considerable power to your solutions. They enable you to embed data that is relevant to the document. The document stores it and the add-in can read it and respond to it. This enables some powerful sharing capabilities.

Available downloads:

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

XML Parts Word add-in (VB.NET)

Word add-in development in Visual Studio for beginners:

6 Comments

Post a comment

Have any questions? Ask us right now!