Introduction
The attached example demonstrates the use of Cathal Coffey's "DocX" DLL which
can be found on CodePlex here:
http://docx.codeplex.com/. The project offers a DLL which can be
downloaded and added to a project and then used to programmatically create Word
documents on the fly and without any reliance upon the Microsoft Office DLLs. I
noticed that an update to the project broke the invoice project example that I
was using and I had to modify it to get it up and running again. You might find
the example useful if you need to generate Word documents programmatically and
if you also found the example failing following the update.
The Invoice example is a great introduction to the use of the DLL as it
demonstrates a number of common tasks:
- Programmatic creation of a template
document
- Programmatic population of the template
coupled with renaming and saving the document
- Adding graphics to the page layout (a
company icon in this case)
- Populating fields created as custom
properties in the document
- Creating and populating tables and
inserting them into the document
Much of the replacement invoice project does
essentially the same thing as the original version although I altered the
appearance of the document. This example does correct the issues I found with
the example that I originally downloaded from the CodePlex project site.
Figure 1 - Generated Invoice Document
Attached Project
The attached project contains a single example; it is an updated version of the
invoice example provided on the CodePlex project site. This updated version
corrects the failed parts of the example I encountered and I have updated the
comments to hopefully better describe what the code is doing at any given point
in the document generation process.
The project itself is a simple console mode application that generates a fake
invoice based upon a collection of canned data used to simulate acquiring the
data from a database. Whilst running, the code checks to see if a document
template exists (and this template is actually just a standard Word document and
not really a template file). If the template document is not found, the code
creates one programmatically and saves it.
Continuing on, the code next recovers data from the data source (in this case, a
collection of dummy data). That recovered data is then used to replace the
default property values stored in the template with live content. Once populated
with data, the code lastly saves the new template based document with a canned
file name. Given the design of the original project, one may look into the bin
folder to locate both the document template and, after running the code, the
populated document with an alternative file name so as to not overwrite the
template itself. Figure 1 in this document provides a screen shot of the
generated document.
The code is fully annotated and you can see what is going on by reading the
comments embedded within the code. You may find it more convenient to open the
project and review the code and comments from within Visual Studio 2012.
using
System;
using
System.Linq;
using
System.IO;
using
System.Data;
using
System.Drawing;
using
System.Reflection;
using
System.IO;
namespace
Novacode
{
///
<summary>
///
This class is an updated, alternate version of the
///
example project found on the project's website
///
at http://docx.codeplex.com/; that project was
///
written and maintained by Cathal Coffey.
///
///
I rewrote the example after noticing that the
///
invoice example did not work correctly following
///
an update of the DocX DLL. Other than some
///
minor changes, the example provided herein is the
///
work of the original author of the DLL, Cathal Coffey,
///
I take no credit for Mr. Coffey's work.
///
///
The DocX DLL offers a terrific way to generate
///
Word documents programatically and without making
///
use of the Microsoft Office DLLs. If one needed
///
to programmatically create Word documents and
///
attach those documents to an email message for
///
an automated distribution (such as weekly billing
///
invoices, or any sort of management rollup or report),
///
this DLL is a very good way to get the job done,
///
particularly if you are working with a constrained
///
budget and lack access to the traditional
///
enterprise products providing similar functionality.
///
</summary>
class
Program
{
// reference to the executing assembly - will
// be used to obtain embedded resources per
// the original example.
static
Assembly
gAssembly;
// reference to the working document.
static
DocX
gDocument;
static
void
Main(string[]
args)
{
// set a global reference to the executing assembly
gAssembly =
Assembly.GetExecutingAssembly();
// To begin with, we can look for a document template
// to see if it exists, if it does not, we will
// programmatically create the template, else we will use it.
try
{
if
(File.Exists(@"InvoiceTemplate.docx"))
{
// set a global reference to the template;
// note the template is just a document and
// not actually a template
gDocument =
DocX.Load(@"InvoiceTemplate.docx");
// once we have the document, we will populate it
// in code, this will create the document we want
// to send out or print.
gDocument =
CreateInvoiceFromTemplate(DocX.Load(@"InvoiceTemplate.docx"));
// Save the current working document so as not
// to overwrite the template document that we
// will want to reuse many times
gDocument.SaveAs("NewLoadedShipment.docx");
}
else
{
//
Create a store a global reference to the
// template 'InvoiceTemplate.docx'.
gDocument = CreateInvoiceTemplate();
// Save the template 'InvoiceTemplate.docx'.
gDocument.Save();
// I will be lazy and just call the
// same code again now that we have
// a working template saved and so
// the user does not have to restart
//
the appication is the template was
// originally missing
// set a global reference to the template
// just created
gDocument =
DocX.Load(@"InvoiceTemplate.docx");
//
populate the document with data so we
// can print it or email it off to a
// recipient
gDocument =
CreateInvoiceFromTemplate(DocX.Load(@"InvoiceTemplate.docx"));
// Save the current working document so as not
// to overwrite the template document that we
// will want to reuse many times
gDocument.SaveAs("NewLoadedShipment.docx");
}
}
catch
(Exception
ex)
{
Console.WriteLine("An
Error has occurred");
Console.WriteLine("Message:
"
+ ex.Message);
Console.WriteLine(ex.StackTrace);
Console.WriteLine("Press
any key to continue");
Console.Read();
}
}
///
<summary>
///
Take the document template and populate it with
///
variable data to ready it for one specific
///
billing target
///
///
This addresses putting custom data into various
///
regions of the document, demonstrating how to
///
fully populate the template. Examine each
///
defined region to see how to put in text, images,
///
and tabular data.
///
</summary>
///
<param name="template"></param>
///
<returns></returns>
private
static
DocX
CreateInvoiceFromTemplate(DocX
template)
{
#region
Logo
// A glance at the template shows us that
// the logo exists in a table at row 0, cell 1.
// That image is a placeholder image that has
// to be replaced with the actual logo.
Paragraph
logo_paragraph =
template.Tables[0].Rows[0].Cells[1].Paragraph;
// Prepare to replace the temporary logo in
// the template with the actual logo by first
// removing that temporary image from the
// template
logo_paragraph.Pictures[0].Remove();
// Add the actual logo to this document
// which is contained in an embedded
// resource
Novacode.Image
logo =
template.AddImage(
gAssembly.GetManifestResourceStream("Novacode.Resources.FakeLogo.png"));
// Insert the extracted logo into the paragraph
Picture
logo_picture = logo_paragraph.InsertPicture(logo.Id);
#endregion
#region
Set Custom Property values
// Set the value of the custom property 'company_name'.
template.AddCustomProperty(
new
CustomProperty("company_name",
"Bart's Parts"));
// Set the value of the custom property 'company_slogan'.
template.AddCustomProperty(
new
CustomProperty("company_slogan",
"The King of OEM distributions"));
// Set the value of the custom properties
'hired_company_address_line_one',
//'hired_company_address_line_two' and
'hired_company_address_line_three'.
template.AddCustomProperty(
new
CustomProperty("hired_company_username",
"Joe Bagadonuts"));
template.AddCustomProperty(
new
CustomProperty("hired_company_address_line_one",
"1100 Main Street"));
template.AddCustomProperty(
new
CustomProperty("hired_company_address_line_two",
"Atlanta, GA"));
template.AddCustomProperty(
new
CustomProperty("hired_company_address_line_three",
"30303"));
// Set the value of the custom property 'invoice_date'.
template.AddCustomProperty(
new
CustomProperty("invoice_date",
DateTime.Today.Date.ToShortDateString()));
// Set the value of the custom property 'invoice_time'.
template.AddCustomProperty(
new
CustomProperty("invoice_time",
DateTime.Today.Date.ToShortTimeString()));
// Set the value of the custom property
// 'hired_company_details_line_one' and 'hired_company_details_line_two'.
template.AddCustomProperty(
new
CustomProperty("hired_company_details_line_one",
"1100 North Main Street, Fremont, CA 12345"));
template.AddCustomProperty(
new
CustomProperty("hired_company_details_line_two",
"Phone: 012-345-6789, Fax: 012-345-6789, e-mail: [email protected]"));
#endregion
#region
Replace Placeholder Table
// Capture the blank table in the template
Table
t = template.Tables[1];
// replace the blank table with a table containing
// the invoice data
Table
invoice_table =
CreateAndInsertInvoiceTableAfter(t,
ref
template);
// remove the blank table from the document
t.Remove();
//
Return the template now that it has been
// modified to hold all of our custom data.
return
template;
#endregion
}
///
<summary>
///
Create a template
///
///
This method is called whenever the template does not
///
exist. The purpose of the method is to create the
///
template so that it might be used as the basis for
///
creating data specific versions of the invoice
///
document
///
///
In general, the template defines locations and
///
custom properties that will be used by the process
///
used to populate the document with actual data.
///
///
In that process, the code will find each specific
///
location and property and replace the default
///
text assigned here with actual information
///
</summary>
///
<returns></returns>
private
static
DocX
CreateInvoiceTemplate()
{
// Create a new document with the canned
// document title. Note this is really just
// a document and not an actual template
DocX
document =
DocX.Create(@"InvoiceTemplate.docx");
// Create a table for layout purposes
// (This table will be invisible).
// Document content will be placed into various cells
// within this table
Table
layout_table = document.InsertTable(2, 2);
layout_table.Design =
TableDesign.TableNormal;
layout_table.AutoFit =
AutoFit.Window;
#region
Create document style
// create formatting styles that will be used
// to define the appearance of the document
// once populated with actual data
// Large Dark formatting - for titles
Formatting
large_dark_formatting =
new
Formatting();
large_dark_formatting.Bold =
true;
large_dark_formatting.Size = 16;
large_dark_formatting.FontColor =
Color.Black;
// Dark formatting
Formatting
dark_formatting =
new
Formatting();
dark_formatting.Bold =
true;
dark_formatting.Size = 12;
dark_formatting.FontColor =
Color.Black;
// Light formatting
Formatting
light_formatting =
new
Formatting();
light_formatting.Italic =
true;
light_formatting.Size = 11;
light_formatting.FontColor =
Color.Black;
#endregion
#region
Company Name
// Define a custom property for the company name, this property
// will be populated with the actual company name when the
// document is populated with actual data
// Capture the upper left Paragraph location in the layout_table;
this
// is the location the company name will be placed into when the
// document is populated with data.
Paragraph
upper_left_paragraph =
layout_table.Rows[0].Cells[0].Paragraph;
// Create a custom property called company_name and set it to
// display a generic company name in the template
CustomProperty
company_name =
new
CustomProperty("company_name",
"Company Name");
// Put the document property into the table at the
// correct location and apply the display style
layout_table.Rows[0].Cells[0].Paragraph.InsertDocProperty(
company_name, large_dark_formatting);
// Force the next text insert to be on a new line.
upper_left_paragraph.InsertText("\n",
false);
#endregion
#region
Company Slogan
// use the same approach used with the company name to
// insert a new property under the company name; this
// property will display the company slogan using a
// smaller font and in italics
// Create a custom property called company_slogan
CustomProperty
company_slogan =
new
CustomProperty("company_slogan",
"Company slogan goes here.");
// Insert a field of type doc property
// (This will display the custom property 'company_slogan')
upper_left_paragraph.InsertDocProperty(
company_slogan, light_formatting);
#endregion
#region
Company Logo
// Get the upper right Paragraph in the layout_table.
Paragraph
upper_right_paragraph = layout_table.Rows[0].Cells[1].Paragraph;
// Add a template logo image to this document.
Novacode.Image
logo = document.AddImage(gAssembly.GetManifestResourceStream("Novacode.Resources.logo_template.png"));
// Insert this template logo into the upper right Paragraph.
Picture
picture_logo = upper_right_paragraph.InsertPicture(logo.Id,
"",
"");
upper_right_paragraph.Alignment =
Alignment.right;
#endregion
#region
Hired Company Address
// Create a custom property called
// company_address_line_one
CustomProperty
hired_company_username =
new
CustomProperty("hired_company_username",
"User Name:");
// Create a custom property called
// company_address_line_one
CustomProperty
hired_company_address_line_one =
new
CustomProperty("hired_company_address_line_one",
"Street Address,");
// Get the lower left Paragraph in the layout_table.
Paragraph
lower_left_paragraph =
layout_table.Rows[1].Cells[0].Paragraph;
lower_left_paragraph.InsertText("TO:\n",
false,
dark_formatting);
// Insert a field of type doc property
// (This will display the custom property
// 'hired_company_username')
lower_left_paragraph.InsertDocProperty(
hired_company_username, light_formatting);
// Force the next text insert to be on a new line.
lower_left_paragraph.InsertText("\n",
false);
// Insert a field of type doc property
//
(This will display the custom property
// 'hired_company_address_line_one')
lower_left_paragraph.InsertDocProperty(
hired_company_address_line_one, light_formatting);
// Force the next text insert to be on a new line.
lower_left_paragraph.InsertText("\n",
false);
// Create a custom property called
// company_address_line_two
CustomProperty
hired_company_address_line_two =
new
CustomProperty("hired_company_address_line_two",
"City,");
// Insert a field of type doc property
// (This will display the custom property
// 'hired_company_address_line_two')
lower_left_paragraph.InsertDocProperty(
hired_company_address_line_two,
light_formatting);
// Force the next text insert to be on a new line.
lower_left_paragraph.InsertText("\n",
false);
// Create a custom property called company_address_line_two
CustomProperty
hired_company_address_line_three =
new
CustomProperty("hired_company_address_line_three",
"Zip Code");
// Insert a field of type doc property
// (This will display the custom property
// 'hired_company_address_line_three')
lower_left_paragraph.InsertDocProperty(
hired_company_address_line_three,
light_formatting);
#endregion
#region
Date & Invoice number
// Get the lower right Paragraph from the layout table.
Paragraph
lower_right_paragraph =
layout_table.Rows[1].Cells[1].Paragraph;
CustomProperty
invoice_date =
new
CustomProperty("invoice_date",
DateTime.Today.Date.ToString("d"));
lower_right_paragraph.InsertText("Date:
",
false,
dark_formatting);
lower_right_paragraph.InsertDocProperty(invoice_date,
light_formatting);
CustomProperty
invoice_time =
new
CustomProperty("invoice_time",
DateTime.Today.Date.ToShortTimeString());
lower_right_paragraph.InsertText("\nTime:
",
false,
dark_formatting);
lower_right_paragraph.InsertText("",
false,
light_formatting);
lower_right_paragraph.InsertDocProperty(invoice_time,
light_formatting);
// set the paragraph to align against the right side
// of the invoice
lower_right_paragraph.Alignment =
Alignment.right;
#endregion
#region
Statement of thanks
// Insert an empty Paragraph between two Tables,
// so that they do not touch.
document.InsertParagraph(string.Empty,
false);
// This table will hold all of the invoice data.
// set the table style to a canned format
Table
invoice_table = document.InsertTable(7, 4);
invoice_table.Design =
TableDesign.LightShadingAccent1;
invoice_table.Alignment =
Alignment.center;
// A nice thank you Paragraph.
Paragraph
thankyou =
document.InsertParagraph("\nThank
you for your business, "
+
"see us again for all of your OEM parts needs.",
false,
dark_formatting);
thankyou.Alignment =
Alignment.center;
#endregion
#region
Hired company details
CustomProperty
hired_company_details_line_one =
new
CustomProperty("hired_company_details_line_one",
"Street Address, City, ZIP Code");
CustomProperty
hired_company_details_line_two =
new
CustomProperty("hired_company_details_line_two",
"Phone: 000-000-0000, Fax: 000-000-0000, "
+
"e-mail: [email protected]");
Paragraph
companyDetails =
document.InsertParagraph(string.Empty,
false);
companyDetails.InsertDocProperty(hired_company_details_line_one,
light_formatting);
companyDetails.InsertText("\n",
false);
companyDetails.InsertDocProperty(hired_company_details_line_two,
light_formatting);
companyDetails.Alignment =
Alignment.center;
#endregion
//
Return the template document now that it has been created.
return
document;
}
///
<summary>
///
This method will capture the data required to
///
populate the invoice's table, and it will then
///
use that data to populate a new table, will
///
insert the new table into the document, and
///
it will then remove the old blank place holder
///
table from the document
///
</summary>
///
<param name="t"></param>
///
<param name="document"></param>
///
<returns></returns>
private
static
Table
CreateAndInsertInvoiceTableAfter(
Table
t,
ref
DocX
document)
{
// Grab data from somewhere (Most likely a database); this
// example just creates a dummy list of values
DataTable
data = GetDataFromDatabase();
/*
* The trick to replacing one Table with another,
* is to insert the new Table after the old one,
* and then remove the old one.
*/
Table
invoice_table =
t.InsertTableAfterSelf(data.Rows.Count + 1,
data.Columns.Count);
invoice_table.Design =
TableDesign.DarkListAccent1;
#region
Table title and column headers
Formatting
table_title =
new
Formatting();
table_title.Bold =
true;
invoice_table.Rows[0].Cells[0].Paragraph.InsertText(
"Invoice ID",
false,
table_title);
invoice_table.Rows[0].Cells[0].Paragraph.Alignment =
Alignment.left;
invoice_table.Rows[0].Cells[1].Paragraph.InsertText(
"Part Number",
false,
table_title);
invoice_table.Rows[0].Cells[1].Paragraph.Alignment =
Alignment.left;
invoice_table.Rows[0].Cells[2].Paragraph.InsertText(
"Description",
false,
table_title);
invoice_table.Rows[0].Cells[2].Paragraph.Alignment =
Alignment.left;
invoice_table.Rows[0].Cells[3].Paragraph.InsertText(
"Unit Price",
false,
table_title);
invoice_table.Rows[0].Cells[3].Paragraph.Alignment =
Alignment.left;
invoice_table.Rows[0].Cells[4].Paragraph.InsertText(
"Number Ordered",
false,
table_title);
invoice_table.Rows[0].Cells[4].Paragraph.Alignment =
Alignment.left;
invoice_table.Rows[0].Cells[5].Paragraph.InsertText(
"Total",
false,
table_title);
invoice_table.Rows[0].Cells[5].Paragraph.Alignment =
Alignment.left;
#endregion
// Loop through the rows in the Table and insert
// data from the data source.
for
(int
row = 1; row < invoice_table.RowCount; row++)
{
for
(int
cell = 0; cell < invoice_table.Rows[row].Cells.Count; cell++)
{
Paragraph
cell_paragraph =
invoice_table.Rows[row].Cells[cell].Paragraph;
cell_paragraph.InsertText(
data.Rows[row -
1].ItemArray[cell].ToString(),
false);
}
}
// Let the tables coloumns expand to fit its contents.
invoice_table.AutoFit =
AutoFit.Contents;
// Center the Table
invoice_table.Alignment =
Alignment.center;
// Return the invloce table now that it has been created.
return
invoice_table;
}
///
<summary>
///
Get the source data and use it to populate the invoice
///
table displayed in the document
///
///
You will likely be getting the data from a database and
///
you will have to adjust the column headers and structure
///
to match the content returned from any required query
///
against the actual data source.
///
</summary>
///
<returns></returns>
private
static
DataTable
GetDataFromDatabase()
{
DataTable
table =
new
DataTable();
table.Columns.AddRange(new
DataColumn[]
{
new
DataColumn("InvoiceId"),
new
DataColumn("PartNo"),
new
DataColumn("Description"),
new
DataColumn("UnitPrice"),
new
DataColumn("UnitsOrderd"),
new
DataColumn("RowTotal")});
table.Rows.Add
(
"78123",
"801-ST344",
"Steering
Column",
"$287.65",
"1",
"$287.65"
);
table.Rows.Add
(
"78124",
"71-AC9488",
"Compressor, AC",
"$614.82",
"1",
"$614.82"
);
table.Rows.Add
(
"78125",
"783342",
"Air filter, Fram",
"$9.12",
"1",
"$9.12"
);
table.Rows.Add
(
"78126",
"AC49034",
"Spark Plug, Platinum",
"$5.12",
"8",
"$40.96"
);
table.Rows.Add
(
"78127",
"FMC-66-1243",
"Bumper, Front",
"$212.45",
"1",
"$212.45"
);
table.Rows.Add
(
"",
"",
"Tax",
"",
"",
"93.20"
);
table.Rows.Add
(
"",
"",
"Total",
"",
"",
"$1258.20"
);
return
table;
}
}
}