Search code examples
c#ms-wordvstooffice-interopcompatibility

HeaderFooter enumeration fails in VSTO application when processing Compatibility Mode docs


I have created a C# MS Word VSTO add-in in Visual Studio 2022 that loops through the footers in documents and injects a text box and field code into the footers of different sections. This code works well with .docx documents. However, with .doc documents in Compatibility Mode, WdHeaderFooterIndex.wdHeaderFooterFirstPage is mistaken for Word.WdHeaderFooterIndex.wdHeaderFooterPrimary. So the program inserts the text box and field code into the primary footer when the code asks for the First Page Footer. This results in no content inserted into the First Page Footer and duplicates inserted into the Primary Footer.

This is the code:

  foreach (Word.Section section in activeDoc.Sections)
  {
      foreach (Word.HeaderFooter footer in section.Footers)
      {
          activeDoc.PageSetup.DifferentFirstPageHeaderFooter = -1;

          // Calculate the width of the page
          float pageWidth = activeDoc.PageSetup.PageWidth;

          // Calculate the top position for the text box
          float textBoxHeight = 24;
          float topPosition = activeDoc.PageSetup.PageHeight - textBoxHeight;

          // Insert the shape
          Word.Shape docIdTextBox = footer.Shapes.AddTextbox(
              Office.MsoTextOrientation.msoTextOrientationHorizontal,
              0, topPosition, pageWidth, textBoxHeight);
                    
          //additional code for formatting the text box/inserting the field code
      }
  }

I have tried the following without success:

  • Debugging to determine if the code is reading the HeaderFooter index incorrectly. It is not. When WdHeaderFooterIndex.wdHeaderFooterFirstPage is read, the index is identified correctly with the content inserted incorrectly into Word.WdHeaderFooterIndex.wdHeaderFooterPrimary
  • Identifying Compatibility Mode documents with if (activeDoc.CompatibilityMode == (int)Word.WdCompatibilityMode.wdWord2003) and inserting different shapes, text, etc. using different code statements.
  • Explicitly referring to activeDoc.Sections[1].Footers[Word.WdHeaderFooterIndex.wdHeaderFooterFirstPage].Range; without the loop
  • Updating the Visual Studio Tools for Office Runtime to the most recent version
  • Referencing a .NET Framework 4.0 class library containing the footer insertion code inside the VSTO Framework 4.6.2 solution, which then calls the lower Framework code (the thinking behind this was that the newer framework introduced a bug and by running the code in the older framework, I might be able to circumvent the issue)

Other Notes:

  • If I manually convert a Compatibility Mode .doc to .docx via Word Save As or via Microsoft 365 Word's File|Info|Convert Compatibility Mode command, my code runs fine (my firm will not allow me to convert documents programmatically)
  • The test compatibility documents I am using have no manually added sections. The are comprised of only one page (just a first page footer), or multiple pages (a first page footer and a primary footer for all pages after the first one)

All help greatly appreciated.


Solution

  • I was able to determine that it was the text box shape (and shapes in general) that was causing .doc Compatibility Mode documents to misidentify or skip the WdHeaderFooterIndex.wdHeaderFooterFirstPage and place content intended for that footer in the Word.WdHeaderFooterIndex.wdHeaderFooterPrimary. To work around this I manually created an MS Word building block that contains a shape (in my case a text box) and placed it in its own template file, which would be loaded via the STARTUP directory at Word launch. I then added, via code, the building block to the footers (including the first page footer) that required the content. Once the text box was in the Range for the correct footer, Word 365 allowed me to perform all programmatic functions on the shape, including looping through footers to find and manipulate shapes.

    Here is my code:

    using Word = Microsoft.Office.Interop.Word;
    
    Word.Document activeDoc = Globals.ThisAddIn.Application.ActiveDocument;
    Word.Template bblocksTemplate = Globals.ThisAddIn.Application.Templates[BBlocksTemplatePath];
    Word.BuildingBlock textBoxBuildingBlock = bblocksTemplate.BuildingBlockEntries.Item("NameOfBuildingBlock");
    textBoxBuildingBlock.Insert(footer.Range, true); //footer was passed into this method
    
    foreach (Word.Shape shape in footer.Range.ShapeRange)
    {
        if (shape.Type == Office.MsoShapeType.msoTextBox)
        {
            Word.Range fieldRange = shape.TextFrame.TextRange;
            //Add field code, text, formatting, etc.
        }
    }