Microsoft.Office.Interop.Word add tables to document

118 Views Asked by At

I am writing a C# utility library for .NET Framework 4.8 that automates the creation of MS Word documents. In particular, it mines a SQL Server database and creates a Word report containing tables of the data.

I cannot seem to add more than one table to my document without having them on separate pages (which is ugly because they are small). My various attempts are as follows:

using Word = Microsoft.Office.Interop.Word;
// ... [other code] ...

// Create an instance of the Word application.
Word.Application wordApp = new Word.Application
{
    ShowAnimation = false,
    Visible = false
};
// Create a new Word document.
Word.Document document = wordApp.Documents.Add();

First, try adding two tables to the range of the document itself (fails):

// *Attempt 1*
// Get document range.
Word.Range docRange = document.Range();
// Add table 1.
Word.Table table = document.Tables.Add(docRange, 2, 3);
// Try to add table 2.
// Exception: "The range cannot be deleted"
Word.Table table2 = document.Tables.Add(docRange, 4, 5); // does not work

Second, try adding two tables to the range of the first section of the document (fails):

// Get section range.
Word.Range secRange = document.Sections[document.Sections.Count].Range; // document.Sections.Count = 1
// Add table 1.
Word.Table table1 = document.Tables.Add(secRange, 2, 3);
// Try to add table 2.
Word.Table table2 = document.Tables.Add(secRange, 4, 5); // Exception: "The range cannot be deleted"

Third, try creating a new section with a page-break and then adding each table to its own section (works but the tables are on separate pages, which I don't want):

// Get section range.
Word.Range secRange = document.Sections[document.Sections.Count].Range; // document.Sections.Count = 1
// Add table 1.
Word.Table table1 = document.Tables.Add(secRange, 2, 3);
// Add a new section (with no arguments, a page-break will automatically be applied).
document.Sections.Add();
// Update section range.
secRange = document.Sections[document.Sections.Count].Range; // now document.Sections.Count = 2
// Add table 2 on a separate page.
Word.Table table2 = document.Tables.Add(secRange, 4, 5); // works

Lastly, try creating a new section without a page-break (i.e., that follows continuously on from the previous one) and then adding each table to its own section (fails):

// Get section range.
Word.Range secRange = document.Sections[document.Sections.Count].Range; // document.Sections.Count = 1
// Add table 1.
Word.Table table1 = document.Tables.Add(secRange, 2, 3);
// Add a new section without a page-break (have it come straight after the previous one).
object oRange = document.Sections[document.Sections.Count].Range; // document.Sections.Count = 1 still
object oStart = Word.WdSectionStart.wdSectionContinuous;
document.Sections.Add(oRange, oStart);
// Update section range.
secRange = document.Sections[document.Sections.Count].Range; // now document.Sections.Count = 2
// Try to add table 2.
Word.Table table2 = document.Tables.Add(secRange, 4, 5); // Exception: "The range cannot be deleted"

Is there anything else I can try? I can't believe it's not possible to have more than one table in a section! But then it's fair to say that I don't really know what I'm doing here, and haven't found any official documentation that explains how any of this works.

2

There are 2 best solutions below

1
jonsson On

The problem here is that docRange will be set to a range that "covers" the document, e.g. if the document is empty docRange.Start will be 0 and docRange.End will be 1. After you add "table", docRange.Start will still be 0, and docRange.End will be 9. So when you try to add table2, you are in effect asking Word to delete "table" and replace it, which isn't what you want anyway. And Word won't do it.

AFAIK there is no really simple way to create a document by adding sequences of text and/or objects successively at the end of a document, and range objects have enough strange behaviour that it's not always obvious what to do. A general approach I have seen for "appending multiple objects" is to

  • create a Range object
  • for each object,
    • "collapse" the Range object to the end of the document
    • add anything needed to separate one object from another
    • "collapse" the Range object to the end of the document
    • add the object

Using VBA (I leave you to translate to c#, a fix to your initial example might look like this:

Sub insert2tables1()
Dim docRange As Word.Range
Dim table1 As Word.Table
Dim table2 As Word.Table

Set docRange = ActiveDocument.Range
docRange.Collapse direction:=wdCollapseEnd
Set table1 = docRange.Tables.Add(docRange, 2, 3)

Set docRange = ActiveDocument.Range
docRange.Collapse direction:=wdCollapseEnd
docRange.InsertParagraph

Set docRange = ActiveDocument.Range
docRange.Collapse direction:=wdCollapseEnd
Set table2 = docRange.Tables.Add(docRange, 5, 5)

Set table2 = Nothing
Set table1 = Nothing
Set docRange = Nothing

End Sub

The above code could be shorter, but that's not really the point here.

Some other notes about Range objects while we're here:

  • When you start with a point range object (as the code above does) and Add a table to the range, the range ends up at the beginning of the table. If you then try to add another table to that range, you will create a table nested inside cell one of the first table.

  • Range objects have various ways to put text in them, e.g. Range.Text = "x", Range.InsertBefore "x" and Range.InsertAfter "x", and also various ways to insert paragraphs (Range.InsertParagraph, Range.InsertParagraphBefore, and Range.InsertParagraphAfter). My view is that these do not really behave as described and if you are going to be working with ranges and objects a lot, it will be worth doing a few tests, checking Range.Start and Range.End to see what actually happens to the Range.

  • Some object types have special "Insert" methods to insert them into Ranges. Others use "Add". Also probably worth seeing what happens to the Range in each case that you need to use.

Finally, it may be worth your while looking at OpenXml as suggested by @user246821 - not sure these have been referenced already but Eric White's pages on OpenXMl here may be useful, and if you can get your data from SQL in a simple XML format, his Document Assembler may be just what you need - see this page , but I think you may need to check GitHub for later versions of some of this material.

0
Ed Graham On

In the end I did it using a combination of sections and paragraphs as alluded to by this link posted by user246821:

// Create paragraph for the first table.
Word.Paragraph paraTable1 = document.Paragraphs.Add();
// Add first table to this paragraph.
Word.Table table1 = document.Tables.Add(paraTable1.Range, 2, 3);

// Add space-filling paragraph.
Word.Paragraph paraFiller = document.Paragraphs.Add();
paraFiller.Range.Text = $"{Environment.NewLine}{Environment.NewLine}";
paraFiller.Range.InsertParagraphAfter();

// Create paragraph for the second table.
Word.Paragraph paraTable2 = document.Paragraphs.Add();
// Add second table to this paragraph.
Word.Table table2 = document.Tables.Add(paraTable1.Range, 4, 5);

// Add a new section with a page-break.
document.Sections.Add();

// ... continue adding paragraphs and tables in the new section ...