Overview
Reporting in TX Text Control .NET for Windows Forms joins a word processor and a merge engine in one form: users design a template in the TextControl with the Reporting ribbon tab, and the MailMerge component fills it with data, either in the visible editor or in the background. The concepts below apply to every template, whatever the data: how the layout grows with the data, how the data is shaped, where the merge runs and how many documents it produces.
The Mail Merge tutorials in Getting Started build a first invoice template and merge it, and the MailMerge page of the Overview introduces the component. The examples here use the same files: the template invoice.docx and the data invoice.json with the fields Company, Name and InvoiceNumber and the list Items.
The Reporting Model
A template is an ordinary document. There are no fixed report bands: users design it with the features of a word processor, such as tables, page columns, images and headers and footers, and the placeholders keep the formatting they are given.
- The text flows. When merged content makes a table or a paragraph longer, the lines and pages break again, and the table continues on the next page.
- Templates hold names, not connections. A template contains the names of merge fields and merge blocks. The data comes from your code or, for databases, from a separate data source configuration file, so one template works with any data of the same structure.
- Merge fields keep their formatting. The font, size and color of a field in the template are those of the merged value.
The following picture shows an invoice merged from such a template in the TX Words Reporting sample: the table of items has grown by one row per product.

Pre-Shaped Data
MailMerge doesn't query a database while it merges data that your code passes. You prepare the data in the hierarchy the document needs and pass it to a merge method; Text Control calls this pre-shaped data. The data can come from any number of sources, as long as your code brings it into this shape first. The one exception is a data source configuration file: with it, Merge() connects to the database the file names and reads the master table and its related tables itself.
Internally, the hierarchy becomes tables:
| In the data | In the template |
|---|---|
| A property of the root object, a column of the master table | A merge field of the same name, such as Company |
| A nested object, a related table with one row | A merge field with dot notation, such as Customer.City |
| An array, a collection or a related table | A merge block of the same name, such as Items, repeated for each record |
The records you pass form the master table. The template is merged once for each record, so an array of 100 invoices creates 100 invoices. Data Sources lists the kinds of data MailMerge accepts.
Where the Merge Runs
MailMerge holds no document. Its TextComponent property connects it to the control that holds the template and, after the merge, the result:
| Connected to | The template is | The result |
|---|---|---|
The TextControl of the form | The document in the editor | Replaces the template in the editor, where the user sees it |
A ServerTextControl | Loaded by your code, not visible | Stays in the ServerTextControl until your code saves or shows it |
Merging in the editor is the natural choice for a preview or for a user who edits the merged letter. Merging in a ServerTextControl leaves the template in the editor untouched and suits batches of documents.
In the Windows Forms designer, MailMerge is a component of the TX Text Control 34.0 toolbox tab. Dropped on a form, it appears in the component tray below the form:

Select the component and choose the TextControl in the drop-down of its TextComponent property in the Properties window:

The page size and margins of the TextControl or ServerTextControl connected through TextComponent are used for the merged document. Set UseTemplateFormat to true to keep those of the template instead; the default is false.
What a Merge Processes
A merge fills more than merge fields:
| Element | What happens | Described in |
|---|---|---|
MERGEFIELD fields | Replaced with the value of the same name | Merge Fields |
DATE fields | Filled with the current date in the field's format | Merge Fields |
IF fields | Insert one of two texts, depending on a comparison with the data | Conditional Content |
NEXT and NEXTIF fields | Continue with the next record in the same document, always or on a condition, for example on a sheet of labels | NEXT and NEXTIF Fields, the blog article Sneak Peek Reporting X10: Creating Labels with NEXT Fields |
INCLUDETEXT fields | Replaced with another document, a subtemplate | Subtemplates |
| Merge blocks | Repeated for each record of a list, also nested | Merge Blocks, Nested Blocks and Master-Detail Reports |
| Image and barcode placeholders | Filled with an image or encode a value | Images and Barcodes |
| Form fields | Left alone, preselected or replaced, depending on FormFieldMergeType | Merge Fields |
Fields in headers and footers are merged as well.
Create One Document or One per Record
MergeJsonData, MergeObjects and Merge have a second parameter, append. With true, the default, all records are merged into one document. With false, each record becomes a document of its own, and the connected control holds only the last one when the merge ends.
To keep every document, handle the DataRowMerged event: its MergedRow property holds the document of the record in the internal TX Text Control format. The following Click handler of a button named splitButton saves one PDF per invoice of invoice.json:
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using TXTextControl;
using TXTextControl.DocumentServer;
private void splitButton_Click(object sender, EventArgs e)
{
List<byte[]> documents = new List<byte[]>();
using (ServerTextControl tx = new ServerTextControl())
{
tx.Create();
LoadSettings loadSettings = new LoadSettings
{
ApplicationFieldFormat = ApplicationFieldFormat.MSWord,
LoadSubTextParts = true
};
tx.Load("invoice.docx", StreamType.WordprocessingML, loadSettings);
using (MailMerge mailMerge = new MailMerge { TextComponent = tx })
{
// Collect the document of each record
mailMerge.DataRowMerged += (s, args) => documents.Add(args.MergedRow);
// false: a separate document for each record
mailMerge.MergeJsonData(File.ReadAllText("invoice.json"), false);
}
}
for (int i = 0; i < documents.Count; i++)
{
using (ServerTextControl document = new ServerTextControl())
{
document.Create();
document.Load(documents[i], BinaryStreamType.InternalUnicodeFormat);
document.Save($"invoice-{i + 1}.pdf", StreamType.AdobePDF);
}
}
MessageBox.Show(this, $"{documents.Count} invoices created");
}
The code works as follows:
- The
LoadSettingsimport the MS Word fields and the merge blocks of the DOCX template, as in Merge Data into Fields. DataRowMergedoccurs once per record, and the handler keeps each byte array.- Each byte array is loaded into a new
ServerTextControland saved as invoice-1.pdf, invoice-2.pdf and so on.
invoice.json holds one invoice, so the handler creates one PDF. Add a second object with the same properties to its array to see a second file.
Load, Save and Print With MailMerge
MailMerge can also load the template and save or print the result itself. The following handler merges invoice.docx in a ServerTextControl, saves the result as PDF and prints it on the printer the user chooses:
using System;
using System.Drawing.Printing;
using System.IO;
using System.Windows.Forms;
using TXTextControl;
using TXTextControl.DocumentServer;
private void printButton_Click(object sender, EventArgs e)
{
using (ServerTextControl tx = new ServerTextControl())
{
tx.Create();
using (MailMerge mailMerge = new MailMerge { TextComponent = tx })
{
if (!mailMerge.LoadTemplate("invoice.docx", FileFormat.WordprocessingML))
{
MessageBox.Show(this, "The template could not be loaded.");
return;
}
mailMerge.MergeJsonData(File.ReadAllText("invoice.json"));
mailMerge.SaveDocument("invoice.pdf", StreamType.AdobePDF, new SaveSettings());
// Let the user choose the printer
using (PrintDialog printDialog = new PrintDialog { Document = new PrintDocument() })
{
if (printDialog.ShowDialog(this) == DialogResult.OK)
{
mailMerge.Print(printDialog.Document);
}
}
}
}
}
- LoadTemplate returns
falsewhen the template can't be loaded. Its format is a value of FileFormat:MSWord,RichTextFormat,WordprocessingMLorInternalUnicodeFormat. LoadTemplateFromMemory loads a template from memory, and the TemplateFile property, which you can also set in the Properties window, names a file whose format is deduced from its extension. - SaveDocument saves the merged document to a file, and SaveDocumentToMemory to a byte array or a string.
- Print prints the merged document with the settings of a
PrintDocument, such as the printer, the page range and the number of copies. A second overload takes the document name, the printer name, the number of copies and whether to collate.
LoadTemplate takes no LoadSettings. To decide how the fields and blocks of a DOCX template are imported, load it with Load and LoadSettings, as in the previous example.
Follow the Merge With Events
MailMerge raises events while it merges, for example FieldMerged for each field, BlockRowMerged for each row of a merge block and DataRowMerged for each record. Your handlers can check the data, change the merged content or collect the results. Merge Events describes them all.
Next Steps
- Data Sources: JSON, business objects, DataSets and configuration files.
- Merge Fields: formats, date fields, fields without data and form fields.
- Merge Blocks: repeat parts of a template for each record of a list.
- Reporting Ribbon and Dialog Boxes: the Reporting tab your users design templates with.
- Template Preview: show users the merged result while they design.