Category Archives: Word

Word Headers/Footers and the LinkToPrevious

0
Filed under Office, Troubleshooting, Word

imageIf you’ve had to work much with Word’s Object Model, either from VBA macro scripts or C#/VB.net projects, you’ve probably dealt with Headers and Footers at one point or another. And Headers and Footers can be particularly convoluted things in Word.

I’m not going to try explaining the whole “three objects, but some exist and some don’t, and you have to jump through hoops to see them” issues of Headers and footers, but rather focus on one small element: LinkToPrevious.

As the name suggests, the LinkToPrevious property indicates whether the header or footer for a particular section is “Linked to” the same header/footer of the previous section. When linked, changing the header/footer of either section automatically changes the other, which makes sense.

Often, when scanning through the sections of a document, you’ll need to adjust headers and footers, to a common pattern in code is to check the LinkToPrevious property and if true, skip that header/footer. The reason is because, since it’s linked to the immediately previous section, and you’re scanning through all sections starting at the top, you’ve already processed it. Makes sense.

Except, that the LinkToPrevious property should NEVER be true for the header/footer of the FIRST section.

But it can be!

To prove it, create a document with two sections, set the header for the second section to “LinkToPrevious” true. Then delete the first section. If you do it right. You’ll end up with a single section document whose header is set LinkToPrevious True. Technically, this shouldn’t be possible and it certainly isn’t correct.

The end result is that if you’re scanning headers/footers like this, you have to check LinkToPrevious on all sections except the first.

Integrating with the New Office Backstage from a VSTO 3 Addin

2
Filed under .NET, Code Garage, Office, VSTO, Word

If you’re like me and want to drop straight to the code, you can download the sample project here.

When it comes to writing addins for Office applications, you really only have 2 choices.

  • Old School – Implementing the IExtensibility2 interface
  • VSTO

The problem is that VSTO addins tend to be one trick ponies. If you want to write a single addin that works in multiple Office applications, or if you want a single addin to work across multiple Office Application versions, you’ll likely run into walls with VSTO. Sure, you could create separate DLL’s for each Office app, and for each target version, but good lord, who wants to do that?

Still, VSTO makes dealing with Ribbons and TaskPanes much easier and can be handy when you need to target a single Office app, say, Word, and when you’re specifically concerned with a single version of the app (or maybe the latest few).

This was the case recently with an addin I was working on.

The Situation

The target application was Word, specifically Word 2010. However, virtually all of the addin was finished by the time VSTO 4 and VS2010 was released. Since we didn’t really want to run the risks of retooling the addin for VSTO 4 (seeing as it’s a brand new platform and we were close to the end of the dev cycle), the decision was made to stick with VSTO 3.

No big deal. VSTO 3 addins run perfectly fine under Word 2010.

The Snag

During the ramp up on Word 2010, however, we discovered the new Backstage view:

image

It’s a really nice extension to the traditional Office File menu. One of the few new elements of Office 2010 that really makes upgrading worthwhile, in my opinion, but that’s another story.

At any rate, it turns out that the Backstage view is extensible via XML, very much like the Ribbon.

Unfortunately, VSTO 3 has no support for the Backstage view.

Customizing the BackStage in the First Place

For me, though, the first question was exactly how do you customize the BackStage? Turns out, it’s fairly simple. If you’ve manually customized the Ribbon before, you’ll recognize the process instantly. John Durant wrote up a short and sweet article on the process here.

Unfortunately, he describes modifying the BackStage by altering the Custom XML package in a document. This makes the BackStage modification document specific. Not the way you want to do it for an addin.

I also found this great article, but it describes modifying the BackStage by implementing an addin using the older style IExtensibility2 model, not VSTO.

More articles, same shortcomings. This was starting to look as hopeless as the Cowboy’s season this year.

I knew that modifying the ribbon via an IExtensibility2 addin was as simple as creating a public function called GetCustomUI, and returning the XML for whatever custom buttons you want added to the Word Ribbon. From the articles mentioned above, it was obvious that the exact same process was used to alter the BackStage. The problem was, there wasn’t any obvious way to get at  the GetCustomUI function in a VSTO addin. All of that XML munging is automagically handled for you by VSTO.

Further, I wanted, if at all possible, to continue to leverage the VSTO Ribbon support. The Visual Studio Ribbon Designer is just too handy to have to go back to manually crafting up the XML for it.

However, I knew that no matter what happened, I was still going to have to manually build the XML for my BackStage customizations. That was ok, though.

The Wrong Way – Wrap RibbonManager

I initially thought I could just create a new object that wrapped RibbonManager.

The main VSTO Connect class (that itself inherits from the VSTO Addin class), exposes the overridable function CreateRibbonExtensibilityObject  that expects a IRibbonExtensibility object as a return value.

So, create a new object, RibbonManagerInterceptor, that implements IRibbonExtensibility, and just wraps the internally created RibbonManager object, and return that, like so:

Private _RibbonExt As Microsoft.Office.Core.IRibbonExtensibility
Protected Overrides Function CreateRibbonExtensibilityObject() As Microsoft.Office.Core.IRibbonExtensibility
    Dim RibbonManager = MyBase.CreateRibbonExtensibilityObject()
    _RibbonExt = New RibbonManagerInterceptor(RibbonManager)
    Return _RibbonExt
End Function

Unfortunately, it won’t work. The VSTO authors make the assumption that the object implementing IRibbonExtensibility is of base type RibbonManager. For instance:

Private ReadOnly Property RibbonExtensibility As IRibbonExtensibility
    Get
        If (Me.ribbonExtensibility Is Nothing) Then
            Me.ribbonExtensibility = Me.CreateRibbonExtensibilityObject
            Dim ribbonExtensibility As RibbonManager = TryCast(Me.ribbonExtensibility,RibbonManager)
            If (Not ribbonExtensibility Is Nothing) Then
                ribbonExtensibility.ServiceProvider = MyBase.HostContext
            End If
        End If
        Return Me.ribbonExtensibility
    End Get
End Property

This is from the Addin class in Microsoft.Office.Tools.Common.v9.0. You’ll notice that the property accepts an IRibbonExtensibility object, but then proceeds to cast it as RibbonManager and if that fails, the addin won’t register the service provider.

What’s worse is that RibbonManager has been marked NotInheritable, so you can’t create a subclass from it to pass this test. Full stop.

Intercepting GetCustomUI

Boiling the basic requirements of an Office addin down, I knew I had to implement IRibbonExtensibility and the GetCustomUI method.

    Public Function GetCustomUI(ByVal RibbonID As String) As String Implements IRibbonExtensibility.GetCustomUI
        Dim xml = _RibbonManager.GetCustomUI(RibbonID)

        If _Connect.Core.WordInstance.Version = "14.0" Then
            '---- only add in backstage support for version 14 (Office 2010)
            Dim bs = <backstage>
                         <tab id="bsSample" label="BackStageSample" insertAfterMso="TabInfo">
                             <firstColumn>
                                 <group id="bsSampleGroup" label="BackStage Sample Group">
                                     <topItems>
                                         <button id="BackStageSample"
                                             label="BackStage Sample Button"
                                             onAction="bsSampleClicked"/>
                                     </topItems>
                                 </group>
                             </firstColumn>
                         </tab>
                     </backstage>
            xml = xml.Replace("</customUI>", bs.ToString & "</customUI>")
            xml = xml.Replace("http://schemas.microsoft.com/office/2006/01/customui", "http://schemas.microsoft.com/office/2009/07/customui")
        End If
        Return xml
    End Function

The first step is to use the underlying RibbonManager object to generate its version of the CustomUI XML. That allows me to continue to use the Ribbon Designer in Visual Studio and the RibbonManager for all the heavy lifting where the Ribbon is concerned.

Next, if I see the addin is hosted by Word v14 (2010), I use the handy inline XML feature of VB.net to create the BackStage custom XML and inject it into the Ribbon XML previously already generated.

And finally, I have to patch the schema used, so that Word knows I’m defining BackStage customizations in the XML as well as Ribbon customizations.

A Better Way

I knew that VSTO was somehow intercepting  all callbacks from Word as defined in the CustomUI XML, and then generating events for the various Ribbon controls, or interrogating control properties. Maybe there was a way to hook into that process.

So, I loaded up Reflector and started spelunking.

It didn’t take long to realize what was going on.

When you set up a custom UI for Word, you define callback functions in the XML that Word will then call when necessary. Those functions must be public functions on the object that implements IRibbonExtensibility and that is exposed as a COM object. Further, those functions are ALWAYS called via IDispatch. For instance, take the following custom UI:

<backstage>
    <tab id="bsSample" label="BackStageSample" insertAfterMso="TabInfo">
        <firstColumn>
            <group id="bsSampleGroup" label="BackStage Sample Group">
                <topItems>
                    <button id="BackStageSample"
                            label="BackStage Sample Button"
                            onAction="bsSampleClicked"/>
                </topItems>
            </group>
        </firstColumn>
    </tab>
</backstage>

The onAction element above defines a function called bsSampleClicked, that Word will call on the IRibbonExtensibility object whenever that button is clicked.

Interestingly, it doesn’t matter that this is for a BackStage object (a button) and not a Ribbon. Word vectors everything through that IRibbonExtensibility object.

So, how does that process work then?

Well, as it turns out, it’s not terribly complicated, but it does require a little work. With .NET, you implement a latebound IDispatch type call by implementing the IReflect interface. That interface contains a number of members that need to be implemented, though most can be stubbed out.

However, you’ll definitely need to implement the GetMethods and InvokeMember functions.

GetMethods returns to the caller an array of MethodInfo objects that describe the all latebound methods that our IRibbonExtensibility object will be supporting. Since we want to continue to allow the RibbonManager to service all of the methods that it needs to handle, you first need to retrieve the RibbonManager’s list of supported methods and then add to it:

    Private Function IReflect_GetMethods(ByVal bindingAttr As BindingFlags) As MethodInfo() Implements IReflect.GetMethods
        Dim ir = DirectCast(_RibbonManager, IReflect)
        Dim r = ir.GetMethods(bindingAttr)
        ReDim Preserve r(UBound(r) + 1)
        Dim mi = BackstageMethodInfo.CheckBoxActionMethod(DirectCast(_RibbonManager, RibbonManager), "bsSampleClicked", Nothing)
        r(UBound(r)) = mi
        Return r
    End Function

Finally, to vector the method call properly, define the InvokeMethod method like so:

    Private Function IReflect_InvokeMember(ByVal name As String, ByVal invokeAttr As BindingFlags, ByVal binder As Binder, ByVal target As Object, ByVal args As Object(), ByVal modifiers As ParameterModifier(), ByVal culture As CultureInfo, ByVal namedParameters As String()) As Object Implements IReflect.InvokeMember
        Dim r As Object
        If name.StartsWith("bs") Then
            '---- it's a Backstage control, just intercept and pass through
            '     to the connect object
            Select Case name.ToLower
                Case "bssampleclicked"
                    _Connect.bsSampleClicked()
                Case Else
            End Select
            Return Nothing
        Else
            Try
                Dim ir = DirectCast(_RibbonManager, IReflect)
                r = ir.InvokeMember(name, invokeAttr, binder, _RibbonManager, args, modifiers, culture, namedParameters)
            Catch ex As Exception
                Throw New TargetInvocationException(ex)
            End Try
            Return r
        End If
    End Function

Here, if the callback function Word is trying to call starts with “bs”, I assume it’s one of my “backstage” callbacks and I drop into the first Select Case.

If not, I forward the call on through to underlying RibbonManager object, so VSTO can continue to work it’s magic.

The actual function that handles the callback, I defined in my VSTO Connect object:

    '---- Testing function
    Friend Sub bsSampleClicked()
        MsgBox("BackStage Sample Button was Clicked")
    End Sub

A Few Side Notes

I put together a small sample addin for Word that illustrates this approach. Download it here.

If you download and unzip the sample project, one of the first things you’re likely to notice is the References folder. For addins like this, I like to copy any referenced dll locally to the project and reference it from there, rather than scattering referenced DLLs all over my system. It makes moving the project to other machines much easier, among other benefits.

You’ll also notice that the DLLs are for Office 2007, as that’s part of the point of this sample; to show that a VSTO 3 addin  can target both 2007 and 2010 and support Ribbons and the Backstage.

One downside to this approach is that VSTO 3 insists  that Office 2007 be installed, even if you’re actually only targeting 2010. You’ll get errors and very specific warnings to that effect if you try to run the project on a machine without Office 2007.

Just make sure you’ve installed Office 2007, then Office 2010, to be able to run the addin against both versions.

COM Visibility

Since addins, even VSTO addin’s, are, at their core, COM dlls, you’ll likely find yourself having to deal with COM interop to some degree. To make that easier, I usually turn OFF COM visibility for the WHOLE PROJECT in the Assembly.vb file:

' Setting ComVisible to false makes the types in this assembly not visible
' to COM components.  If you need to access a type in this assembly from
' COM, set the ComVisible attribute to true on that type.
<Assembly: ComVisible(False)> 

And then turn ON COM visibility for each class that actually needs to be visible via COM.

''' <summary>
''' Core object for this addin
''' </summary>
''' <remarks></remarks>
<ComVisible(True)> _
<Guid("8618E3FB-D57B-4875-ABE4-D204E1C1046A")> _
Public Class Core
...

Debugging

To make the project easy to debug, I always set the start action to “Start External Program”, and point it at my WinWord.exe in the Office installation.

image

You may need to change the path to WinWord as applicable to your system.

Sample Project Requirements

To run the sample project, you’ll need the following:

  • Visual Studio 2008
  • Office 2007
  • To test the backstage support, you’ll also need Office 2010 installed (They can be installed side by side except for Outlook).
  • Visual Studio Tools for Office 3.0 (VSTO). I found the installer already on my system at

    c:\Program Files (x86)\Microsoft SDKs\Windows\v6.0A\Bootstrapper\Packages\VSTOR30\vstor30.exe
  • You’ll likely also want the service pack for VSTO. It was located here:

    c:\Program Files (x86)\Microsoft SDKs\Windows\v6.0A\Bootstrapper\Packages\VSTOR30\vstor30sp1-KB949258-x86.exe

The Wrapup

So there you have it. Support for the latest Backstage View in a VSTO 3 based addin. It required a fair bit of poking around under the covers, something that would simply not be possible without Lutz Roeder’s excellent Reflector tool. If you don’t have that in your toolbox, you’re missing out on a fantastic debugging, diagnostic, research, and discovery tool!

In the end, if you need your addin to support multiple Office applications or a wide variety of versions, your best bet will be to go with IExtensibility2 (or possible AddinExpress, though I’ve never used it, and have no idea whether it truly makes things easier for multi targeting or not).

And finally, the standard disclaimer. IWOMM (It works on my machine). If you find bugs or problems, please let me know. Heck, if you know a better way (short of converting to VSTO 4!), I’d love to hear about it! I’m fairly certain that this isn’t the only way to skin this particular cat.

Word’s Compatibility Options

0
Filed under .NET, Code Garage, Office, Word

One element you’ll eventually run up against when dealing with Word documents is "compatibility”.

image

You can see one small indicator of compatibility in the above screenshot. When you open any old format DOC file in Word 2007 or 2010, it’ll open in compatibility mode.

But what does that mean, really?

Compatibility in a Nutshell

Word’s Compatibility mode actually encompasses a fairly significant number of tweaks to how Word renders a document. You can see those options at the bottom of the Advanced tab on Word’s Options dialog.

image

a small sampling of compatibility options available

Word sets those options in a number of different combinations depending on the source of the original document. You can select the source document manually via the below dropdown, but most of the time, Word will choose an appropriate selection automatically when it opens the original  document.

image

The problem is, many of those options can cause Word to render a document in very strange, unpredictable ways. In fact, most Word experts advise to manually turn OFF all compatibility options (and so force Word to layout the document using it’s most recent rules, either those set for Word 2007 or for Word 2010).

Controlling Compatibility Programmatically

Manipulating those settings manually via the Options dialog is fine, but if you’ve got thousands of documents to deal with, that may not be your best approach.

Why not automate it with a bit of .net code?

    Private Sub ForceCompatibility(ByVal Doc As Word.Document)
        Doc.Convert()

        Doc.Application.Options.DisableFeaturesbyDefault = False
        For Each e As Word.WdCompatibility In [Enum].GetValues(GetType(Word.WdCompatibility))
            Dim res As Boolean = False
            Select Case e
                Case Word.WdCompatibility.wdDontULTrailSpace
                    res = True
                Case Word.WdCompatibility.wdDontAdjustLineHeightInTable
                    res = True
                Case Word.WdCompatibility.wdNoSpaceForUL
                    res = True
                Case Word.WdCompatibility.wdExpandShiftReturn
                    res = False
                Case Word.WdCompatibility.wdLeaveBackslashAlone
                    res = True
                Case Word.WdCompatibility.wdDontBalanceSingleByteDoubleByteWidth
                    res = True
            End Select
            Doc.Compatibility(e) = res
        Next
    End Sub

So, what’s going on here?

First, I pass in a Document variable to the function. This variable contains a reference to the Document object you need to fix compatibility on. You can easily obtain teh Active document object with the Application.ActiveDocument property.

The function first calls the Doc.Convert method. This converts the document to the latest format available to the version of Word you’re running and enables all new features. This conversion happens in memory. Nothing is saved to disk at this point. Also, you’d think that this would be enough to turn off all compatibility tweaks, but alas, Word leaves many of them still turned of after this conversion.

Next, it uses the DisableFeaturesbyDefault property to enable all available features.

Then, it enumerates all the wdCompability options to clear or set them as necessary. This handy bit of trickery can come in handy in a number of situations. Essentially, it uses a little reflection here:

[Enum].GetValues(GetType(Word.WdCompatibility))

to retrieve an array of all the possible values of the wdCompatibility enumeration.

Then, the For loop simply iterates through all the values in that array.

The SELECT CASE then matches up a few specific enumeration values that don’t work quite like you might expect, and handles them with special cases.

For instance, the wdNoSpaceForUL option is equivalent to the “Add Space for Underlines” option in the Word Options Dialog. However, the logic is reversed.

Leave it to a word processing program to make use of double negatives like this!

Odd Man Out

The wdExpandShiftReturn option is the one exception. When cleared, this option prevents Word from expanding spaces on lines that end with a soft Return (a Shift-Return). Many Word experts indicate that this is preferable to Word’s default behavior of expanding those spaces, so I’ve set it here accordingly. Your mileage may vary.

And finally, be sure the SAVE the document in xml format, using the Save method like this:

Doc.SaveAs FileName:="doc.xml", FileFormat:=wdFormatXML

So there you have it. A quick and easy way to force a document into the latest Word file format and turn off any errant compatibility options in the process.