Merge Blocks
A merge block is the part of a template that MailMerge repeats for each record of a list: the line items of an invoice, the positions of a delivery note or the clauses of a contract. A block can also stand for one object and vanish when the data doesn't contain it. What follows goes beyond the first block of Getting Started: what a block may contain, how it is stored, the block tools of the Reporting tab, blocks created and inspected by your code, and what happens to blocks without data.
Add Repeating Merge Blocks in Getting Started inserts the Items block of the invoice template invoice.docx and merges it with invoice.json. The examples below use the same form, with textControl1 and the Reporting tab ribbonReportingTab1, which has invoice.json loaded. Blocks inside blocks are the subject of Nested Blocks and Master-Detail Reports.
What a Merge Block Contains
A block isn't limited to a table row. It can hold a complete table, paragraphs, images, text frames or a single word, and MailMerge copies the whole block, formatting included, once for each record:
- Breaks are copied too. A paragraph end in the block starts a new paragraph per record; a page break, section break or column break in the block is inserted per record.
- Each record on a new page. When the first paragraph of the block has
PageBreakBeforeset in its paragraph format, every copy of the block starts on a new page, as the blog article MailMerge: Starting Each Merge Block on a New Page shows. - Table header rows. A header row above a table row block is repeated at the top of each page. To give each copy of a block a header of its own, make the complete table the block, as Repeat Table Headers per Block explains.
- Fields, images and barcodes in the block receive the values of the current record.
How Blocks Are Stored
In the document, a merge block is a SubTextPart, a named range of text, whose name is txmb_ followed by the block name. MailMerge.MergeBlockNamePrefix returns this prefix. Subtext Parts describes the element itself.
- DOCX, DOC and RTF. Load templates in these formats with LoadSettings.LoadSubTextParts set to
true, as Merge Data into Fields does. Otherwise the blocks aren't imported. - Older templates mark a block with two document targets named
blockstart_andblockend_followed by the block name. This format is still supported. - Saving. ReportingMergeBlockFormat of
SaveSettingsdecides how blocks are written:Defaultkeeps them as they are, andDocumentTargetsconverts subtext part blocks into the older document targets, for applications that still expect them.
Sorting and filter instructions are stored with the block, as Sorting and Filtering explains.
Work With Blocks in the Reporting Tab
The Merge Blocks group of the Reporting tab holds everything a template designer needs for blocks. The Insert Merge Block drop-down lists the tables of the loaded data source:

- A table of the list opens the Insert Merge Block dialog box for that table.
- Insert Custom Merge Block makes the selected text a block with a name you type, for data the loaded data source doesn't contain.
- Highlight Merge Blocks marks all blocks of the template.
- Edit Merge Block, the second button of the group, renames or removes the blocks of the document.
In the dialog box, you check the columns that become merge fields of the block, including columns of related tables, and order them with Move up and Move down. Table Row creates the block as a table row, Plain paragraph as paragraphs, and Add table header row adds a row with the column names above a table row block. Color sets the color that highlights the block while the input position is in it, and Filter & Sort adds the instructions described in Sorting and Filtering:

Your code opens the same dialog boxes with InsertMergeBlockDialog, as Getting Started shows, and EditMergeBlocksDialog. Reporting Ribbon and Dialog Boxes describes the tab as a whole.
Insert a Paragraph Block in Code
DataSourceManager.InsertMergeBlock inserts a block at the input position. Its MergeBlockSettings decide what the block looks like:
| Property | Sets |
|---|---|
| BlockTemplateType | TableRow for a table row, PlainParagraph for paragraphs |
| CreateHeaderRow | Whether a table row block gets a header row |
| HighlightColor | The color of the block while the input position is in it; its alpha value is used |
| FieldDisplayMode | Whether the fields show their text or their field codes |
The following handler of a button named insertItemListButton inserts the invoice items as a list of paragraphs instead of a table. Add using System.Drawing;, using TXTextControl.DocumentServer.DataSources; and using System.Collections.Generic; to Form1.cs:
private void insertItemListButton_Click(object sender, EventArgs e)
{
// A paragraph for each item with two of its fields
MergeBlockInfo itemsBlock = new MergeBlockInfo("Items")
{
ColumnNames = new List<string> { "Description", "Quantity" }
};
MergeBlockSettings settings = new MergeBlockSettings(BlockTemplateType.PlainParagraph)
{
// Shown while the input position is in the block
HighlightColor = Color.FromArgb(64, Color.SteelBlue)
};
ribbonReportingTab1.DataSourceManager.InsertMergeBlock(textControl1, itemsBlock, settings);
if (settings.AddResult != SubTextPartCollection.AddResult.Successful)
{
MessageBox.Show(this, "The merge block was not inserted: " + settings.AddResult);
}
}
The code works as follows:
- MergeBlockInfo names the block after the
Itemsarray, and its ColumnNames become merge fields. - After the call, AddResult holds a value of SubTextPartCollection.AddResult, such as
Successful, or a reason likeOverlappingwhen the block would overlap another one. - For a table row block, CreatedTable returns the new table, for example to format it.
Find the Blocks of a Template
A template that users designed may contain other blocks than your data provides. Connected to textControl1, MailMerge lists the blocks of the document in the editor without merging it:
private void listBlocksButton_Click(object sender, EventArgs e)
{
StringBuilder report = new StringBuilder();
using (MailMerge mailMerge = new MailMerge { TextComponent = textControl1 })
{
foreach (string blockName in mailMerge.GetBlockNames())
{
string[] fieldNames = mailMerge.GetBlockFieldNames(blockName);
report.AppendLine(blockName + ": " + string.Join(", ", fieldNames));
}
}
MessageBox.Show(this, report.ToString(), "Merge blocks");
}
GetBlockNames returns the names of all blocks, and GetBlockFieldNames the merge fields of one block. The handler needs using System.Text; and using TXTextControl.DocumentServer;. For the invoice template, the message shows Items: Description, Quantity, Price.
Your application can also react to the block the user is working in, for example to enable a button. SubTextParts.GetItem without parameters returns the subtext part at the input position:
private void currentBlockButton_Click(object sender, EventArgs e)
{
// The innermost subtext part at the input position, or null
SubTextPart subTextPart = textControl1.SubTextParts.GetItem();
if (subTextPart == null || !DataSourceManager.IsMergeBlock(subTextPart))
{
MessageBox.Show(this, "The input position is not in a merge block.");
return;
}
MergeBlockInfo mergeBlockInfo = new MergeBlockInfo(subTextPart);
MessageBox.Show(this,
$"Block {mergeBlockInfo.TableName}, level {subTextPart.NestedLevel}, " +
$"{mergeBlockInfo.MergeFields.Length} merge fields");
}
- DataSourceManager.IsMergeBlock tells a merge block from other subtext parts.
- A
MergeBlockInfocreated from a subtext part describes an existing block. TableName is the name of its data, and MergeFields holds its fields without those of nested blocks. - NestedLevel is 1 for a block at the top level.
Rename Blocks and Fields in Code
Block names and prefixed field names must follow the data. When a table or an array of the data gets a new name, every template that uses it needs the new name too. Edit Merge Block renames the blocks of one template; for many templates, your code renames them. A block is a subtext part named txmb_ and the block name, and a field of a related table carries the table name before the dot, so both are renamed with string operations. The following method of Form1.cs renames a table in the template in textControl1:
using TXTextControl;
using TXTextControl.DocumentServer;
using TXTextControl.DocumentServer.Fields;
private void RenameDataTable(string oldName, string newName)
{
string oldBlockName = MailMerge.MergeBlockNamePrefix + oldName;
// The main text, text frames, headers and footers
foreach (IFormattedText textPart in textControl1.TextParts)
{
// Merge blocks are subtext parts named txmb_ and the block name
foreach (SubTextPart subTextPart in textPart.SubTextParts)
{
if (subTextPart.Name == oldBlockName)
{
subTextPart.Name = MailMerge.MergeBlockNamePrefix + newName;
}
}
// Merge fields that address the table with dot notation, such as Items.Price
foreach (ApplicationField applicationField in textPart.ApplicationFields)
{
if (applicationField.TypeName != "MERGEFIELD")
{
continue;
}
MergeField mergeField = new MergeField(applicationField);
if (mergeField.Name.StartsWith(oldName + "."))
{
mergeField.Name = newName + mergeField.Name.Substring(oldName.Length);
}
}
}
}
The code works as follows:
- TextParts holds the main text, the text frames and the headers and footers. Each part has its own SubTextParts and
ApplicationFields. - A subtext part whose Name is
txmb_Itemsis the blockItems. SettingNamerenames the block. - A
MergeFieldwrapped around each merge field changes Name in the document. Fields inside the block need no change, because their names don't contain the table name.
RenameDataTable("Items", "LineItems") adapts the invoice template to data whose array is named LineItems. Save the template afterward. The blog article Renaming Merge Blocks and Merge Fields Programmatically in C# wraps the same steps in a helper class.
Use a Block for a Single Object
A block doesn't have to repeat: named like a single nested object of the data, it is merged once if the object exists and removed when it doesn't. Include Optional Sections builds optional sections of a document this way.
Follow Blocks During the Merge
Two events accompany each block: BlockMerging occurs before a block is merged and hands over its data, and BlockRowMerged occurs after each merged row, which your code can change. Merge Events describes both, and Formatting and Styling uses them to format merged rows.
Merge Blocks From a DataSet
With a DataSet, each block is named like the DataTable that fills it, and a DataRelation connects the table to the master table. MailMerge.Merge with the master table fills the fields outside the blocks from the master table and each block from the related table of the same name; DataSets and DataTables shows the details.
MergeBlocks is an older method that merges only the blocks. Since version 20.0, Merge processes the relations itself; Deprecated APIs lists MergeBlocks with its replacement.
Render a Block as a Numbered List
To merge records as the items of a numbered list, put the merge fields into a numbered list item and add an empty list item below it. Select from the start of the fields to the empty item, so that the paragraph end of the first item is part of the selection, and make the selection a block. Each record then becomes an item of the list with its own number. The blog article Reporting: Merge Blocks Rendered As Numbered Lists shows such a template.
Next Steps
- Nested Blocks and Master-Detail Reports: blocks inside blocks for orders and their items.
- Sorting and Filtering: merge the rows of a block in order and only the rows you need.
- Conditional Content: include or leave out sections depending on the data.
- Template Preview: show users the merged blocks while they design.