Divide by zero
 Thursday, June 19, 2008
VSTO solution for context sensitive dynamic help in Word 2007

A project I developed called for context sensitive dynamic help inside of Microsoft Word, based on the current cursor location. The idea was that if the cursor is on an image, then help information would be presented regarding the business rules pertaining to images. I.E. what size can the image be, policies regarding alternative text for web publishing etc. To achieve this there were two components. One was a Quality Assurance component to check for conformance to business rules. The other was the dynamic help system. I present a whittled down version of the help system here. The Quality Assurance component will be discussed in a future entry. There is everything necessary to implement this as a full fledged context sensitive dynamic help system. The help information is presented in a custom task pane, and uses HTML.


Figure 1 -Word 2007 showing the Context sensitive dynamic help system

Architecture

This solution uses three main components:

  • The VSTO addin
  • A MessageBroker which receives events from the VSTO addin
  • The Help UserControl which consumes events from the MesageBroker

This centralised messaging architecture is beneficial when other components are introduced such as the QualityAssurance component referred to earlier. The VSTO Addin wouldn't need to be changed, the MessageBroker, the QualityAssurance component would subscribe as listeners for the events raised by the MessageBroker. Another advantage of this approach is that security and logging would be easily implemented in the one location.

So, when the Addin loads, the WindowSelectionChanged event is subscribed to by the addin.

this.Application.WindowSelectionChange+= new Microsoft.Office.Interop.Word.ApplicationEvents4_WindowSelectionChangeEventHandler(
   Application_WindowSelectionChange);

It would be possible for the Help UserControl to subscribe directly, but by doing it this way we can define a few rules around what events get triggered, and also cache the current style so that the event is not consumed by every registered listener every time the cursor moves even though the style hasn't changed.


void Application_WindowSelectionChange(Microsoft.Office.Interop.Word.Selection Sel) {
     Word.Style style = (Word.Style)Sel.get_Style();
     if (! style.NameLocal.Equals(_currentStyle)) {
        _currentStyle = style.NameLocal;
        _messageBroker.Publish(typeof(MessageEventArgs), Sel.Paragraphs[1], style);
     }
}

Note in this code how it is important to retrieve an instance of the style object and cast it to type Word.Style. This is COM Interop in Word. It would be possible to write an extension method to get around this, but I don't think its necessary for this example.

The constructor for the Help UserControl accepts an instance of the MessageBroker. Note: I should probably make this class a Singleton.

The Help UserControl receives event notifications when the current style has changed. It loads a web browser control and loads an HTML page named after the style. if no page exists for that style, it will display a default help page. There is a tabbed interface, with an Infoand aHelp tab. The Help tab has not been implemented, the idea was to provide links to examples.

I have only included stripped down versions of HTML pages for three styles:

  • Heading 1
  • Heading 2
  • Heading 3

It is simply a matter of creating a page witht the style name, and an extension of .html and dropping it in the help folder. The code that loads the page looks like this:

private void LoadHelpIntoBrowser(string currentStyle) {
    String helpFilePath
=
CreateHelpFilePath(currentStyle);
    
if
(File.Exists(helpFilePath)) {
       webInfo.Navigate(helpFilePath);
    }
else
{
       LoadHelpHomePage();
    }
}

private void
LoadHelpHomePage() {
   string p =
Path.Combine(Path.GetDirectoryName(
   System.Reflection.Assembly.GetExecutingAssembly().CodeBase),
"..\\..\\help"
);
   String helpFilePath
= CreateHelpFilePath("WordJester"
);
   webInfo.Navigate(helpFilePath);
}

private
String CreateHelpFilePath(String style) {
   Uri baseUri
=new Uri(
      Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase));
   
return String.Format("{0}{1}..{1}..{1}help{1}{2}.html"
, baseUri.LocalPath,
         Path.DirectorySeparatorChar, style);
}

The beauty of this approach is that you can add in as many styles as required, even non standard styles, and as long as a help file exists, it will be displayed. The file names will look like this:


Figure 2 - file names for the HYML pages based upon the word style name

Run the solution, and try creating some heading levels 1, 2 and 3, and move the cursor between them and check out the help in the custom task pane change. Let me know what you think of this application.

WordJester.zip (224.21 KB)


Thursday, June 19, 2008 10:37:37 PM (AUS Eastern Standard Time, UTC+10:00)   #    Comments [0]  Downloads | VSTO | Word 2007
link to del.icio.us link to reddit link to StumbleUpon link to Facebook Bookmark to Google
 Sunday, May 11, 2008
Wikipediaise - a c# VSTO Word addin

Wikipediaise - What is it?

Wikipediaise is a Visual Tools for Office addin (VSTO) developed in Microsoft Visual Studio 2008 as an addin for Microsoft Word. It is written in C#.  It  was designed to hyperlink acronyms and jargon  to Wikipedia.

I do a lot of technical documentation for my work, and the IT industry being what it is, the documents end up with a ridiculous number of acronyms. To make life easier, we usually put an abbreviation section at the top of the document, but this is a time consuming process to go thru every time, so I automated it. Additionally I added another method which will seek out the first occurrence of an abbreviation or acronym, and hyperlink it. First I will describe how this works, then how to use and customize the functionality.

Initially Wikipedia was used as the reference point, as it is an excellent reference point for technical information. After a while it became clear that many acronyms were better documented elsewhere, or in internal company documents, so I added the ability to use alternative reference sources.

Note that although I refer to acronyms, the addin is good for jargon and technical terms as well.

The following images show a before and after shot of a simple document, additionally it shows the document with an acronym table inserted at the top.


Figure 1 - Before shot of a Word document with acronyms and jargon to be hyperlinked


Figure 2 - The same document after it has been hyperlinked


Figure 3 - The same document, hyperlinked, and with an acronym table inserted at the beginning

How it works

The application comes with an embedded XML file with a set of pre-defined acronyms. This serves as an example only. The application will look in the %mydocuments% folder for a file called wikipediaise.dic. If this file exists it will override the embedded file, so the application can be customized for most requirements.

Format of the XML file

There are two elements available in the wikipediaise.dic file shown below.

Table 1 - Elements available in wikipediaise.dic

Element

Description

Comment

excludeStyle

Lists a Word style to be excluded from the process

This could be a built in style or a user defined style. If a word is in this style it will not be hyperlinked.

entry

Contains a mandatory key term that will be searched for. Optional attributes will be described later.

This text will be searched for in a case sensitive manner. If the term is found in the middle of a word, it will still be matched. For this reason, position longer superset acronyms earlier e.g. place https before http

The excludeStyle element has no attributes, so just looks like this


Figure 4 - excludeStyle element example

The entry element has attributes, these are described below.

Attribute name

Mandatory

Description

Comment

key

Yes

The term that will be searched for and hyperlinked.

Case sensitive. position longer superset acronyms earlier e.g. place https before http

wikipediaEntry

No

This attribute is only used for entries in Wikipedia where the page name is not the same as the attribute.  E.g. the entry in Wikipedia for Apache has a page name of Apache_HTTP_Server

 

description

No

This will be used as a tooltip when a hyperlink is created in Word. It will also be used in the acronym table if that feature is used,

 

url

No

This is an alternative URL if Wikipedia is not to be the source of reference.

 

Table 2 - entry element attributes

Focas.NET.wikipediaise.zip (21.4 KB)
Sunday, May 11, 2008 5:29:29 PM (AUS Eastern Standard Time, UTC+10:00)   #    Comments [3]  Downloads | VSTO | Word 2007
link to del.icio.us link to reddit link to StumbleUpon link to Facebook Bookmark to Google
 Wednesday, December 05, 2007
Adding buttons to the Word 2007 ribbon at runtime

The ribbon in Word 2007 is a great feature, and it can be customized fairly easily using Visual Studio or other tools. As far as I am aware though, it is impossible to add buttons at run time. This would be a great feature, one that is missed from the earlier versions of Word.

There is a way around it, although it doesn’t provide the same functionality of adding buttons at will. When a Word 2007 Add-In loads, if it has a custom ribbon, then the ribbons GetCustomUI method will be called. This by default returns a string, which is the XML defined in the Visual Studio designer. By modifying this method, extra buttons can be added, but be aware that this method is only ever called once, when the Add-In loads.  Of course you could just define the buttons in the XML in the first place, but the method I outline here is good for a plug-in scenario, where you don’t know in advance what buttons might want to be added.

To cater for a plug-in scenario, you can define extra buttons in an external  configuration file that is read at start up. This can then be parsed and added to the ribbons XML text in the GetCustomUI method. This can be used to add in any of the button styles available to the ribbon. You still need to implement handlers, which I will discuss in a later post. In this example, I will add a simple button that just uses the Microsoft happy face image.

First, I have defined a helper method which just returns a Stream from the resource file which contains the XML representation of the ribbon. The GetCustomUi method returns a string, and by default just calls the standard GetResourceText method which is created when you add a ribbon.

Stream ribbonStream = GetResourceStream("WordAddIn1.Ribbon1.xml");

I haven’t included the code for GetReourceStream method, it is straightforward and easy to create. Now, the Stream is loaded into an XmlDocument. This is because we are going to add a node later, so we need a read/write object.

XmlDocument ribbonDocument = new XmlDocument();

XmlNamespaceManager nsmgr = new XmlNamespaceManager(ribbonDocument.NameTable);

nsmgr.AddNamespace("r", "http://schemas.microsoft.com/office/2006/01/customui");

ribbonDocument.Load(ribbonStream);

Note the use of an XmlNamespaceManager. This is not strictly necessary in this example, but if you are going to work with Office open XML then you should get used to using this object. In the ribbon designer you need to define somewhere to place your custom buttons. For this example I have defined a group that I will add buttons to. This is defined in the XML like this:

<group id="grpCustomButtons"

               label="Custom buttons" />

An XPath query will now be run nto get a reference to this XmlNode.

string xpath = "r:customUI/r:ribbon/r:tabs/r:tab[@idMso='TabAddIns']/r:group[@id='grpCustomButtons']";

XmlNode nodeCustomButtons = ribbonDocument.SelectSingleNode(xpath, nsmgr);

Now the custom buttons can be added to the nodeCustomButtons object. In this example I have just hard coded this, but ideally you would have helper methods that would look for a configuration file, load it and dynamically create the buttons from the information found there.

XmlNode nodeCustomButton = ribbonDocument.CreateElement("button",ribbonDocument.DocumentElement.NamespaceURI);

XmlAttribute att = ribbonDocument.CreateAttribute("id");

att.Value = "cb1";

nodeCustomButton.Attributes.Append(att);

               

att = ribbonDocument.CreateAttribute("label");

att.Value = "Custom button";

nodeCustomButton.Attributes.Append(att);

 

att = ribbonDocument.CreateAttribute("imageMso");

att.Value = "HappyFace";

nodeCustomButton.Attributes.Append(att);

 

att = ribbonDocument.CreateAttribute("size");

att.Value = "large";

nodeCustomButton.Attributes.Append(att);

This button will now be added to the nodeCustomButtons that was retrieved earlier.

nodeCustomButtons.AppendChild(nodeCustomButton);

And all that is left is to return the XML as a string as the method requires.

return ribbonDocument.OuterXml;

Figure 1 - The custom button displayed in thw Add-Ins tab

What comes next? This code only creates a custom button. It doesn’t create a handler for the button, so it is a useless button. What needs to be done next is to add a handler for this button. This code is based on a plug-in model, so this Add-In should also look in the configuration file for more information. Perhaps an assembly, and a class name and a method to call. The code above would be modified to add a handler, and the handler code created to allow for dynamic execution of code.


Wednesday, December 05, 2007 10:51:46 PM (AUS Eastern Standard Time, UTC+10:00)   #    Comments [1]  Word 2007
link to del.icio.us link to reddit link to StumbleUpon link to Facebook Bookmark to Google