The Open XML SDK and Fluent UI Extensibility

Fluent UI, or the Ribbon, was introduced as part of Office 2007 as a replacement of the previous system of toolbars and menus. The Fluent UI technology, like the Open XML formats, is based on xml, which allows for a much richer extensibility story for developers. In Erika Ehril's blog post she described several tools and resources related to Fluent UI extensibility. I wanted to take this opportunity to extend her post by showing you how the Open XML SDK can be used to extend or actually control custom UI within documents.

As is the case with other features in Open XML files, the Open XML SDK 2.0 for Microsoft Office supports Fluent UI through strongly typed access to the custom UI xml part as well strongly typed access to the underlying xml contained within the custom UI xml part. In other words, you can easily add, remove or modify custom UI for a particular document or set of documents using the SDK. In today's post, I am going to show you how to add custom UI to a set of documents within a directory.

If you want to jump straight into the code, feel free to download this solution here.

Scenario

Imagine a scenario where I would like to programmatically add custom UI to a set of documents based on custom UI within a specific template. These documents can exist within a directory or a SharePoint library. In this scenario, my template is going to contain the same custom UI as designed by Frank Rice in his Office developer resources ribbon UI addin blog post. For the sake of this example, let's say I am starting with the following Word document as my template:

This custom UI provides a tab called Office Developer Resources, which contains commands that take you to specific web sites. The code behind these commands is actually contained within macros within the file. In order to add this custom UI to other documents we need to copy over the custom UI as well as the macros that power those commands.

The Solution

To add custom UI to a set of documents we can take the following actions:

  1. Go through all files within a specific directory
  2. If a file is a macro-free document then convert the document to a macro-enabled document
  3. Open the template file using the Open XML SDK
  4. Grab the ribbon extensibility part (contains the custom UI)
  5. Grab the VBA project part (contains the macros)
  6. Open the converted file (or the original file if it was a macro-enabled file) and import both the ribbon and macro parts
    • The file can only contain one instance of each part so be sure to remove preexisting ribbon and macro parts from the file

Note that the steps outlined above are just one method to accomplish this scenario. Another more practical use of this solution is to go through a list of files contained within a SharePoint library instead of a directory on disk.

The Code

The first step as outlined in the solution section above requires us to go through a directory of files. Here is the code snippet to accomplish this task:

string newFileName = null;   string[] files = Directory.GetFiles(@"D:\Open XML SDK demos\Word\DeployCustomUI\DeployCustomUI\bin\Debug\Files");   foreach (string filename in files) { if (filename.EndsWith(".docx")) { newFileName = ChangeDocumentType(filename); } else if (filename.EndsWith(".docm")) newFileName = filename;   ImportCustomUI(templateFile, newFileName); }

If a file in the directory turns out to be a macro-free file we need to change the document type to be a macro-enabled file. Here is the code snippet used to accomplish this task:

static string ChangeDocumentType(string filename) { using (WordprocessingDocument myDoc = WordprocessingDocument.Open(filename, true)) { myDoc.ChangeDocumentType(WordprocessingDocumentType.MacroEnabledDocument); }   string newFileName = Path.GetDirectoryName(filename) + @"\" + Path.GetFileNameWithoutExtension(filename) + "(was docx).docm";   File.Move(filename, newFileName); File.Delete(filename);   return newFileName; }

Notice how the Open XML SDK provides functionality to switch document types. At this point we are ready to import our custom UI from our template into our files within the directory. Below is the code snippet necessary to grab the custom UI and vba project parts from the template document:

static void ImportCustomUI(string templateFile, string outputFile) { using (WordprocessingDocument myDoc = WordprocessingDocument.Open(templateFile, true)) { MainDocumentPart mainPart = myDoc.MainDocumentPart;   RibbonExtensibilityPart customRibbonPart = myDoc.GetPartsOfType<RibbonExtensibilityPart>().First(); ExtendedPart vbaPart = null;   foreach (IdPartPair partPair in mainPart.Parts) { if (partPair.OpenXmlPart.RelationshipType == "https://schemas.microsoft.com/office/2006/relationships/vbaProject") { vbaPart = (ExtendedPart)partPair.OpenXmlPart; break; } }   AddCustomUIParts(outputFile, customRibbonPart, vbaPart); } }

Now that we have the two parts from our template file we are ready to import them into our output file. Below is the code snippet necessary to accomplish this task:

static void AddCustomUIParts(string filename, RibbonExtensibilityPart customRibbonPart, ExtendedPart vbaPart) { using (WordprocessingDocument myDoc = WordprocessingDocument.Open(filename, true)) { MainDocumentPart mainPart = myDoc.MainDocumentPart;   if (myDoc.GetPartsCountOfType<RibbonExtensibilityPart>() > 0) myDoc.DeletePart(myDoc.GetPartsOfType<RibbonExtensibilityPart>().First());   myDoc.AddPart<RibbonExtensibilityPart>(customRibbonPart);   ExtendedPart extendedPart = null;   foreach (IdPartPair partPair in mainPart.Parts) { if (partPair.OpenXmlPart.RelationshipType == "https://schemas.microsoft.com/office/2006/relationships/vbaProject") { extendedPart = (ExtendedPart)partPair.OpenXmlPart; break; } }   if (extendedPart != null) mainPart.DeletePart(extendedPart);   if (vbaPart != null) mainPart.AddPart<ExtendedPart>(vbaPart); } }

Notice in the code above that I am removing any previous custom UI or vba part within the output document. Instead of deleting these parts I could have chosen to do some kind of merge, although that approach would have been more difficult.

End Result

Running this code I should end up with a directory full of macro-enabled files that all have custom UI enabled. Here is a screenshot of one of my documents, which has the imported custom UI:

Zeyad Rajabi