Merge Events
MailMerge reports its progress through events: before a merge block is merged, after each field, each block row and each record, and when images, barcodes, charts, form fields and subtemplates are merged. Handlers of these events check the data, change the merged content, collect results or stop the merge. All events of MailMerge are listed below with the order in which they occur, how a Windows Forms handler is created in the designer and how a handler cancels a merge.
Other pages show what the events are good for: Formatting and Styling colors rows and cells, Merge Fields changes field text, and Overview saves one document per record. The examples use the invoice of the Getting Started tutorials, invoice.docx and invoice.json with the Items block.
The Events of MailMerge
| Event | Occurs | Its arguments provide |
|---|---|---|
| FieldMerged | For each field, whether it received data or not | The field, Merged, the record, the block, the table cell and the merged field as a byte array; Cancel |
| BlockMerging | Before a merge block is merged | The block name, its column names and its rows; Cancel |
| BlockRowMerged | After each row of a merge block | The block name, the row number, the row count and the merged row as a byte array |
| DataRowMerged | After each record of the master table | The record, its number and its merged document as a byte array; Cancel |
| IncludeTextMerging | When an INCLUDETEXT field is merged | The file name and the field; your code can supply the document to include |
| ImageMerged | After an image placeholder has been merged | The image, its name and its descriptive text, which can be replaced |
| ImageFieldMerged | After a merge field with the prefix image: has been merged | The field name and the merged image, which can be replaced |
| BarcodeMerged, ChartMerged | After a barcode or a chart has been merged | The merged BarcodeFrame or ChartFrame and the record |
| FormFieldMerged | After a form field has been merged | The form field, Merged, the record and the table cell |
Most arguments tell where in the template an element is: MergeBlockName is the name of its merge block, or an empty string outside blocks, and NestingDepth the level of the block. Since version 33.0, SubTextPartID and SubTextPartNumber identify the subtext part of the block. Images and Barcodes and Subtemplates describe the events of their elements.
The Order of the Events
For each record of the master table, the events of a merge block and the record occur in this order:
Record of the master table
|- BlockMerging before a merge block is merged
| |- FieldMerged for each field of a block row
| '- BlockRowMerged after each row of the block
'- DataRowMerged after the record has been merged
Fields outside blocks raise FieldMerged as well. To watch the order with your own template, log the events. The following handler of a button named logMergeButton merges the invoice in a ServerTextControl and writes each event into a ListBox named listBox1:
using System;
using System.IO;
using System.Windows.Forms;
using TXTextControl;
using TXTextControl.DocumentServer;
using TXTextControl.DocumentServer.Fields;
private void logMergeButton_Click(object sender, EventArgs e)
{
listBox1.Items.Clear();
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 })
{
mailMerge.BlockMerging += (s, args) =>
listBox1.Items.Add($"BlockMerging: {args.BlockName}, {args.BlockData.Length} rows");
mailMerge.FieldMerged += (s, args) =>
{
if (args.MailMergeFieldAdapter is MergeField mergeField)
{
string block = args.MergeBlockName == "" ? "" : $" in {args.MergeBlockName}";
listBox1.Items.Add($"FieldMerged: {mergeField.Name}{block}, merged: {args.Merged}");
}
};
mailMerge.BlockRowMerged += (s, args) =>
listBox1.Items.Add($"BlockRowMerged: {args.MergeBlockName} row {args.DataRowNumber} of {args.DataRowCount}");
mailMerge.DataRowMerged += (s, args) =>
listBox1.Items.Add($"DataRowMerged: record {args.DataRowNumber}");
mailMerge.MergeJsonData(File.ReadAllText("invoice.json"));
}
}
}
The code works as follows:
- The handlers are attached before MergeJsonData starts the merge.
- BlockName and BlockData of
BlockMergingname the block and hold its rows before they are merged. - MailMergeFieldAdapter of
FieldMergedis the merged field, aMergeFieldfor merge fields, and MergeBlockName is empty outside blocks. - DataRowNumber and DataRowCount of
BlockRowMergedcount the rows of the block, and the DataRowNumber ofDataRowMergedthe records.
The list shows the block Items with its two rows, the fields of each row and one record.
Create Handlers in the Designer
A MailMerge component dropped on the form, mailMerge1 in the component tray, has its events in the Properties window. Select the component, click the Events button with the lightning icon at the top of the window and double-click an event, such as FieldMerged. Visual Studio creates the handler in Form1.cs and connects it in Form1.Designer.cs:
private void mailMerge1_FieldMerged(object sender, MailMerge.FieldMergedEventArgs e)
{
if (!e.Merged && e.MailMergeFieldAdapter is MergeField mergeField)
{
listBox1.Items.Add($"No data for {mergeField.Name}");
}
}
With its TextComponent set to textControl1, mailMerge1.MergeJsonData(File.ReadAllText("invoice.json")) merges the document in the editor, and the handler runs for each of its fields. Overview shows how to set up the component.
Cancel a Merge
The arguments of FieldMerged and DataRowMerged have a Cancel property that cancels the current merge. The following handler merges the invoice only when every field outside the blocks receives data, and saves no PDF otherwise:
private void checkedMergeButton_Click(object sender, EventArgs e)
{
string missingField = null;
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 })
{
mailMerge.FieldMerged += (s, args) =>
{
// Stop at the first field outside a block without data
if (!args.Merged && args.MergeBlockName == "" &&
args.MailMergeFieldAdapter is MergeField mergeField)
{
missingField = mergeField.Name;
args.Cancel = true;
}
};
mailMerge.MergeJsonData(File.ReadAllText("invoice.json"));
}
if (missingField != null)
{
MessageBox.Show(this, $"The merge was canceled: no data for {missingField}.");
return;
}
tx.Save("invoice.pdf", StreamType.AdobePDF);
}
}
Cancel of FieldMergedEventArgs and Cancel of DataRowMergedEventArgs both cancel the merge; the second one fits checks that need the complete record. A merge canceled through DataRowMergedEventArgs.Cancel ends with a MergeCanceledException, which the code that calls the merge method catches. Merged is false for a field that received no data. Remove Company from invoice.json to see the message. BlockMergingEventArgs derives from CancelEventArgs and can cancel the merge of a single block.
Change Merged Content
Three arguments hand over merged content as a byte array in the internal TX Text Control format: MergedField of FieldMerged, MergedBlockRow of BlockRowMerged and MergedRow of DataRowMerged. Load the bytes into a second ServerTextControl, change the content and assign the saved bytes back; the merge continues with the changed content. Formatting and Styling colors block rows this way.
The blog article Using MailMerge Events to Remove Trailing Text in a Merge Block combines BlockMerging, FieldMerged and BlockRowMerged to drop the separator after the last row of a list, and MailMerge: Merging Hyperlinks using the FieldMerged Event turns merged values into hyperlinks.
Skip Records During the Merge
MailMerge merges every record it receives, so the natural place to leave records out is your code that prepares the data. When the decision can only be made during the merge, for example from input of the user, replace the document of a record with an empty one in DataRowMerged. The following handler of Form1.cs merges invoice.json and leaves out the record with the number 1:
using System.Collections.Generic;
using System.IO;
using TXTextControl;
using TXTextControl.DocumentServer;
private void mergeSkippingButton_Click(object sender, EventArgs e)
{
// The numbers of the records to leave out; the first record has the number 0
HashSet<int> recordsToSkip = new HashSet<int> { 1 };
// An empty document that replaces each skipped record
byte[] emptyDocument;
using (ServerTextControl empty = new ServerTextControl())
{
empty.Create();
empty.Save(out emptyDocument, BinaryStreamType.InternalUnicodeFormat);
}
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 })
{
mailMerge.DataRowMerged += (s, args) =>
{
if (recordsToSkip.Contains(args.DataRowNumber))
{
args.MergedRow = emptyDocument;
}
};
mailMerge.MergeJsonData(File.ReadAllText("invoice.json"));
}
tx.Save("invoices.pdf", StreamType.AdobePDF);
}
}
The code works as follows:
- The empty document is created once, before the merge, and saved in the internal TX Text Control format that
MergedRowexpects. - DataRowNumber counts the records from 0, so the number 1 is the second invoice. Assigning the empty document to MergedRow removes that invoice from the result.
- To decide by the content of a record instead of its number, read its values from DataRow, as Conditional Content does in
FieldMerged.
Add two more invoices to the array of invoice.json to see the effect: invoices.pdf then contains the first and the third invoice. The blog article Mail Merge: Skipping Records During the Merge Process in .NET C# uses the same technique with a list of record numbers.
Events of the DataSourceManager
The DataSourceManager of the Reporting tab merges with its own Merge method, for example for a preview. It raises FieldMerged and DataRowMerged with the same arguments as MailMerge; Template Preview uses this method.
Next Steps
- Formatting and Styling: format rows, cells and text in the events.
- Merge Fields: change the text of fields and handle fields without data.
- Merge Blocks: the blocks whose rows the events report.
- Template Design: test a template with
FieldMerged.