Howto: Mail Merge

This chapter shows how to use the DocumentServer.MailMerge class in WPF projects to merge TXTextControl.ApplicationFields in template documents with data from various data sources. The MailMerge class encapsulates powerful mail merge capabilities in a ready-to-use component. The DocumentServer.MailMerge class is part of the TXTextControl.DocumentServer namespace.

Using the DocumentServer.MailMerge component, template documents containing MS Word merge fields can be easily loaded, merged with a DataSet or DataTable of the Microsoft .NET System.Data namespace and exported to the supported file formats of TX Text Control like Adobe PDF or DOCX.

It is possible to pass a DataTable containing different columns that covers the merge field names. The data rows of the table equals the number of created documents. Optionally, it is possible to create separate documents or to append the documents to one resulting document.

Single merge fields can be inserted that will be filled with database content. Additionally, repeating blocks can be defined in the template. These blocks will be repeated according to the number of passed data rows.

The DocumentServer.MailMerge component supports the most important standard merge fields of MS Word. Supported fields are: MERGEFIELD, INCLUDETEXT, IF and DATE.

In the first step, this article explains how to connect the MailMerge component with an instance of TextControl. The source code for the examples is contained in the subfolders MailMerge_Simple, MailMerge_Blocks and MailMerge_Nested_Blocks of the following directories:

  • %USERPROFILE%\Documents\TX Text Control 32.0.NET for WPF\CSharp\Howto\MailMerge
  • %USERPROFILE%\Documents\TX Text Control 32.0.NET for WPF\VB.NET\Howto\MailMerge

Used TX Text Control controls:

  • TXTextControl.WPF.TextControl
  • TXTextControl.WPF.ButtonBar
  • TXTextControl.WPF.RulerBar
  • TXTextControl.WPF.StatusBar
  • DocumentServer.MailMerge

Relevant API links:

Sample: Simple Mail Merge

Having loaded the sample program from the MailMerge_Simple directory, start the program and use the Add method from the Application Fields main menu to insert some fields. An XML file is used as a data source which is also used to insert the menu items dynamically.

Image

The following code shows how to insert the fields into TX Text Control:

private void InsertMergeField(string name) {
        // Create a new TXTextControl.DocumentServer.Fields.MergeField
        // and add it to TextControl.
        TXTextControl.DocumentServer.Fields.MergeField mfMergeField = new TXTextControl.DocumentServer.Fields.MergeField();
        mfMergeField.Name = name;
        mfMergeField.Text = "{ " + name + " }";
        mfMergeField.ApplicationField.HighlightMode = TXTextControl.HighlightMode.Activated;
        mfMergeField.ApplicationField.DoubledInputPosition = true;

        m_txTextControl.ApplicationFields.Add(mfMergeField.ApplicationField);
}
Private Sub InsertMergeField(ByVal name As String)
        'Create a new TXTextControl.DocumentServer.Fields.MergeField
        'and add it to TextControl.
        Dim mfMergeField As TXTextControl.DocumentServer.Fields.MergeField = New TXTextControl.DocumentServer.Fields.MergeField()
        mfMergeField.Name = name
        mfMergeField.Text = "{ " & name & " }"
        mfMergeField.ApplicationField.HighlightMode = TXTextControl.HighlightMode.Activated
        mfMergeField.ApplicationField.DoubledInputPosition = True

        Me.m_txTextControl.ApplicationFields.Add(mfMergeField.ApplicationField)
End Sub

After you created your mail merge template, choose Merge from the Mail Merge main menu. The MailMerge component is used to merge the data into the ApplicationFields.

private void Merge_Click(object sender, RoutedEventArgs e) {
        m_mmMailMerge.Merge(m_dsAddresses.Tables[0], true);
}
Private Sub Merge_Click(sender As Object, e As RoutedEventArgs)
        m_mmMailMerge.Merge(m_dsAddresses.Tables(0), True)
End Sub

Image

Sample: Mail Merge with Repeating Blocks

The DocumentServer.MailMerge component can not only merge single fields in a template. Additionally, blocks can be defined that are repeated based on the number of specified data rows.

A MergeBlock is a region in a document that will be repeated in the merge process. This block can be easily inserted using the shipped TX Text Control Words. The document element SubTextPart range to define the beginning and the end of a repeating block. SubTextParts have a special naming, so that TX Text Control's MailMerge class can find the blocks and connected data. The name of the SubTextPart begins with txmb_ + the name of the merge block.

Load the sample program from the MailMerge_Blocks directory and start it. Automatically, a template is loaded that consists of simple merge fields and a repeating block which is basically the second row of the table.

Image

Choose Merge from the Mail Merge main menu to start the merge process. With a very few lines of code, the template is loaded and the data is merged using the DocumentServer.MailMerge component.

private void TextControl_Loaded(object sender, RoutedEventArgs e) {
        // Create a new data set and load the XML file
        m_dsData = new DataSet();
        m_dsData.ReadXml(@"Files\Data.xml");

        TXTextControl.LoadSettings lsLoadSettings = new TXTextControl.LoadSettings {
                ApplicationFieldFormat = TXTextControl.ApplicationFieldFormat.MSWord,
                LoadSubTextParts = true
        };

        // Load the 'Template.docx' template
        m_txTextControl.Load(@"Files\Template.docx", TXTextControl.StreamType.WordprocessingML, lsLoadSettings);

        // Initialize a MailMerge instance.
        m_mmMailMerge = new TXTextControl.DocumentServer.MailMerge();
        m_mmMailMerge.TextComponent = m_txTextControl;

        // Set focus to the TextControl.
        m_txTextControl.Focus();
}

private void Merge_Click(object sender, RoutedEventArgs e) {
        m_mmMailMerge.Merge(m_dsData.Tables["orders"], true);
}
Private Sub TextControl_Loaded(sender As Object, e As RoutedEventArgs)
        ' Create a new data set and load the XML file
        m_dsData = New DataSet()
        m_dsData.ReadXml("Files\Data.xml")

        Dim lsLoadSettings As TXTextControl.LoadSettings = New TXTextControl.LoadSettings With {
                .ApplicationFieldFormat = TXTextControl.ApplicationFieldFormat.MSWord,
                .LoadSubTextParts = True
        }

        ' Load the 'Template.docx' template
        Me.m_txTextControl.Load("Files\Template.docx", TXTextControl.StreamType.WordprocessingML, lsLoadSettings)

        ' Initialize a MailMerge instance.
        m_mmMailMerge = New TXTextControl.DocumentServer.MailMerge()
        m_mmMailMerge.TextComponent = Me.m_txTextControl

        ' Set focus to the TextControl.
        Me.m_txTextControl.Focus()
End Sub

Private Sub Merge_Click(sender As Object, e As RoutedEventArgs)
        m_mmMailMerge.Merge(m_dsData.Tables("orders"), True)
End Sub

Image

Sample: Mail Merge with Nested Repeating Blocks

The DocumentServer.MailMerge component supports the merging of nested blocks. The component recursively descents into the tree structure and merges the contained fields with the corresponding relational data.

In this sample, a report should list the "sick leave hours" and "vacation days" of all employees of a purely fictional company. The template contains static content, dynamic content for each employee and nested dynamic content for each specific employee record.

Static content

  • Heading: "Accruals Report"
  • Page number: "Page n"

Dynamic content

  • Company name: "Software Consultants, LLC"
  • Date

Dynamic content for each employee

  • Employee number
  • Employee name

Nested dynamic content for each employee

  • Date
  • Type
  • Action
  • Note
  • Hours
  • Balance

Image

A repeating block can be any contiguous block of text in a template that is marked with a SubTextPart. The following screenshot shows such a repeating block in the TX Text Control WPF Words Ribbon sample project.

Image

Using the MailMerge component that comes with the TX Text Control, the merge process is very easy. You don't need to iterate through the text fields and you don't need to care about the repeating blocks. The whole process is done automatically - including the nested blocks. The following code shows the required calls of the MailMerge component:

private void CreateReport_Click(object sender, RoutedEventArgs e) {
        try {
                // Load the XML file.
                DataSet dsData = new DataSet();
                dsData.ReadXml(m_tbxLoadedDatabaseFile.Tag as string, XmlReadMode.Auto);

                // Add the relations for the main block and its child blocks.
                DataRelation relCompanyEmployee = new DataRelation("company_employee",
                         dsData.Tables["company"].Columns["company_number"],
                         dsData.Tables["employee"].Columns["company_number"]);

                DataRelation relEmployeeSick = new DataRelation("employee_sick",
                         dsData.Tables["employee"].Columns["employee_number"],
                         dsData.Tables["sick"].Columns["employee_number"]);

                DataRelation relEmployeeVacation = new DataRelation("employee_vacation",
                         dsData.Tables["employee"].Columns["employee_number"],
                         dsData.Tables["vacation"].Columns["employee_number"]);

                dsData.Relations.Add(relCompanyEmployee);
                dsData.Relations.Add(relEmployeeSick);
                dsData.Relations.Add(relEmployeeVacation);

                // Update the progress bar.
                m_pbProgress.Maximum = dsData.Tables["employee"].Rows.Count;

                // Merge.
                m_mmMailMerge.Merge(dsData.Tables["company"], true);

                // Reset the progress bar.
                m_pbProgress.Value = 0;
        } catch (Exception exc) {
                MessageBox.Show(this, exc.Message);
        }
}
Private Sub CreateReport_Click(sender As Object, e As RoutedEventArgs)
        Try
                ' Load the XML file.
                Dim dsData As DataSet = New DataSet()
                dsData.ReadXml(TryCast(Me.m_tbxLoadedDatabaseFile.Tag, String), XmlReadMode.Auto)

                ' Add the relations for the main block and its child blocks.
                Dim relCompanyEmployee As DataRelation = New DataRelation("company_employee", dsData.Tables("company").Columns("company_number"), dsData.Tables("employee").Columns("company_number"))

                Dim relEmployeeSick As DataRelation = New DataRelation("employee_sick", dsData.Tables("employee").Columns("employee_number"), dsData.Tables("sick").Columns("employee_number"))

                Dim relEmployeeVacation As DataRelation = New DataRelation("employee_vacation", dsData.Tables("employee").Columns("employee_number"), dsData.Tables("vacation").Columns("employee_number"))

                dsData.Relations.Add(relCompanyEmployee)
                dsData.Relations.Add(relEmployeeSick)
                dsData.Relations.Add(relEmployeeVacation)

                ' Update the progress bar.
                Me.m_pbProgress.Maximum = dsData.Tables("employee").Rows.Count

                ' Merge.
                m_mmMailMerge.Merge(dsData.Tables("company"), True)

                ' Reset the progress bar.
                Me.m_pbProgress.Value = 0
        Catch exc As Exception
                MessageBox.Show(Me, exc.Message)
        End Try
End Sub

Let's have a look at the XML file we are using as a data source. The DataSet contains 4 DataTables:

Image

The red highlighted columns indicate that these columns are connected through a DataRelation. The MailMerge class automatically descents into the tree structure and merges the contained fields with the corresponding relational data. This process is recursive, so that unlimited levels of nested blocks are feasible.

How to use this sample?

Assuming that you loaded this project into Visual Studio and started this project, the following steps are required to create a report:

1. Click the Load Sample Datasource drop down item from the Datasource button to browse the sample data.xml file that comes with the sample. To open an own sample datasource, click the Load XML... item.

2. Click the Create Report button to start the merge process.

The following screenshot shows the merged report document that can be printed or exported as an Adobe PDF file.

Image