Working with Microsoft Word templates: C# sample
This is a continuation of the work we did in my previous article – Populating Word documents with data from external sources. The difference is, we will use a pre-built template for inserting data. Instead of inserting data “willy-nilly”, we need to find where to insert it within the document’s pre-defined structure.
Creating a Word template
Before we write code, we need a document template. After we build the template, we can write code around it because we know its structure. We might, if we are living right, have an idea about how the document will be utilized by users.
For this sample, you need to do the following:
- Open Microsoft Word.
- Create a blank document.
- Perform a Save As command and save the file as .dotx file. You can save to the location of your choice. Write down its location because you will need when you test your code. Name the file DataTemplate.dotx because that’s what I named mine.
- Design the first page according the image below:
- Insert a page break to add another page to the Word document.
- Design the page according to the image below:
- Be sure to insert the bookmarks as directed. You can find the bookmark command in Word’s Insert tab. After you add both bookmarks, the bookmark dialog box will look like this:
- Save the document.
We now have a template document with two pages. Both have two tables but the second page’s tables reside within Word bookmarks. We are ready to write some code.
Write the code
The code for today centers on how to find the tables in the document. The tables act as placeholders for the data we want to insert.
The findTable function
First up, we need a function that finds the desired table. Word doesn’t provide a way for users to name the tables. We can do it in code via the ID property but this is useless to a user building templates.
Therefore, the template builder creates tables and insert a name for it the first cell. We can then loop through all the tables in the document and search for our table. This is what the findTable function does.
private Word.Table findTable(Word.Document doc, string searchText) { if (doc.Tables.Count > 0) { int iCount = 1; Word.Table tbl; do { tbl = (Word.Table)doc.Tables[iCount]; //Select first cell and check it's value string header = tbl.Cell(1, 1).Range.Text; if (header.Contains(searchText)) { return tbl; } iCount++; Marshal.ReleaseComObject(tbl); } while (iCount < doc.Tables.Count + 1); } return null; }
The function accepts a Word document and a string as parameters. It then loops through the document’s tables and finds the table that contains the search string. I decided to use Contains versus == as it is more forgiving.
The getTableByBookmarkName function
Using bookmarks simplifies the code. All we need to do is call the bookmark by name and retrieve the first table within the bookmark’s range. The getTableByBookmarkName cleanly handles this need.
private Word.Table getTableByBookmarkName(Word.Document doc, string bookmarkName) { Word.Table tbl = doc.Bookmarks[bookmarkName].Range.Tables[1]; if (tbl != null) return tbl; else return null; }
For our purposes, I assume the bookmark contains only table or, if multiple tables exist, that the first table is the one we want. It’s easy enough to change this logic should business requirements dictate otherwise.
Required code changes to the previous article
To incorporate the functions above into our data insertion methods, we need to make a couple of changes. For the purposes of this article, I’ll focus on importing from Access and Outlook. Yes, the previous article imported from SharePoint as well but I think we only need to samples to drive the point.
Import data from Access
The ImportFromAccess method now has a parameter called demoType. This is a string and allow us to specify what method we want to use for finding a table. Do you we want to use the bookmark strategy or the table looping strategy? That’s why demoType exists.
We check demoType‘s value and split accordingly. If the value is “table” we call findTable and loop through the document’s tables. Otherwise, we use the bookmark method by calling getTableByBookmarkName.
private void InsertFromAccess(string demoType) { Word.Table tbl; if (demoType == "table") { tbl = findTable(this.WordApp.ActiveDocument, "Company List"); } else { tbl = getTableByBookmarkName(this.WordApp.ActiveDocument, "CompanyList"); } if (tbl == null) return; //We need a counter... int i = 1; //Call the database. OleDbConnection cnn = new OleDbConnection(); cnn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + "C:\\Users\\Tyrant\\Desktop\\PopulateWordWithData\\Northwind2007.accdb;" + "Persist Security Info=False;"; string sql = "SELECT Top 11 Company FROM Customers "; OleDbCommand command = new OleDbCommand(sql, cnn); cnn.Open(); OleDbDataReader reader = command.ExecuteReader(); if (reader.HasRows) { while (reader.Read()) { i = i + i; tbl.Rows.Add(); tbl.Cell(i, 1).Range.Text = reader[0].ToString(); } } Marshal.ReleaseComObject(tbl); }
With the table in-hand, we query access and insert the query results into the table.
Import from Outlook
InsertFromOutlook works the same as InserFromAccess. We use the demoType parameter to specify how to retrieve the table. After retrieving the table, we open Outlook and insert contact data into the table.
private void InsertFromOutlook(string demoType) { Word.Table tbl; if (demoType == "table") { tbl = findTable(this.WordApp.ActiveDocument, "Company List"); } else { tbl = getTableByBookmarkName(this.WordApp.ActiveDocument, "CompanyList"); } if (tbl == null) return; //Add two columns if (tbl.Columns.Count == 1) { //1 Column tbl.Columns.Add(); //2 Columns tbl.Columns.Add(); //pretty basic logic...what happens if the table template had 2 columns? //with my code...nothing...which would be a problem. //it's a sample, so make of it what you wish. } //We need a counter... int i = 1; //And a table header tbl.Cell(i, 1).Range.Text = "Contact Name"; tbl.Cell(i, 2).Range.Text = "Phone Number"; tbl.Cell(i, 3).Range.Text = "Email Address"; //start outlook Outlook.Application appOL = new Outlook.Application(); Outlook._NameSpace ns = appOL.Session; Outlook.MAPIFolder fldr = ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts); Outlook.Items items = fldr.Items; if (items.Count > 0) { Outlook.ContactItem buddy; int iFldrCount = items.Count; int iContacts = 1; do { tbl.Rows.Add(); i = i + 1; buddy = items[iContacts]; tbl.Cell(i, 1).Range.Text = buddy.FullName; tbl.Cell(i, 2).Range.Text = buddy.MobileTelephoneNumber; tbl.Cell(i, 3).Range.Text = buddy.Email1Address; iContacts++; Marshal.ReleaseComObject(buddy); } while (iContacts < iFldrCount + 1); } tbl.AutoFitBehavior(Word.WdAutoFitBehavior.wdAutoFitContent); Marshal.ReleaseComObject(items); Marshal.ReleaseComObject(ns); Marshal.ReleaseComObject(fldr); appOL.Quit(); Marshal.ReleaseComObject(appOL); Marshal.ReleaseComObject(tbl); }
A key different with this method is we add two columns to the table and set values for the header row.
For production code, the demoType parameter doesn’t make any sense. It sure works to highlight the two strategies however.
***
When it comes to inserting data using a Word document template, the first step is to build the template structure. The next step is to be creative with your code and build a solution around the template’s structure.
Available downloads:
This sample Word add-in was developed using Add-in Express for Office and .net:
Working with Word Templates add-in (C#)
Word add-in development in Visual Studio for beginners:
- Part 1: Word add-in development – Application and base objects
- Part 2: Customizing Word UI – What is and isn’t customizable
- Part 3: Customizing Word main menu, context menus and Backstage view
- Part 4: Creating custom Word ribbons and toolbars
- Part 5: Building custom task panes for Word 2013 – 2003
- Part 6: Working with Word document content objects
- Part 7: Working with Word document designs, styles and printing
- Part 8: Working with multiple Microsoft Word documents
- Part 9: Using custom XML parts in Word add-ins
- Part 10: Working with Word document properties, bookmarks, content controls and quick parts
- Part 11: Populating Word documents with data from external sources
6 Comments
It shows some error in my machine. Default(Page Name) doesn’t contain defintion for WordApp and No extension method ‘WordApp’ accepting a first aargument of the type Default could be found…
What to do?
Hello Umair,
The code above is used in an Add-in Express add-in; it doesn’t look like you create an Add-in Express add-in. Not sure if the above makes sense in a different context. You take all risks upon yourself. WordApp is an Add-in Express property; it returns a Word.Application object. Try to use this fact in your code; say every object in the Word object model provides the Application property.
After run in my project, it shows error not finding the Word application object, it works find in my programming computer, however not work in another server…
Hello,
Make sure Office/Word is installed on that machine.
Excellent tutorial. I wish if you had also added code snippet for data import from an Excel file.
Hello Syed,
Importing from Excel is a generic topic, not Add-in Express specific. For instance, you may find this Google search useful: https://www.google.com/search?q=import+from+Excel+C%23+OR+VB.NET+site:stackoverflow.com&newwindow=1.