Reporting: Sorting Merge Block Rows by Column Name
The concept of Text Control Reporting is to accept pre-shaped data. That means that Text Control's MailMerge component is merging the data rows 'as-is' and data shaping, sorting or queries are done in the application business layer. Sometimes, it is required to define the order of data rows in the template. This sample shows how to realize this concept based on additional parameters in the merge block name and LINQ to sort the data rows. Let's have a look at the XML data source. It consists…

The concept of Text Control Reporting is to accept pre-shaped data. That means that Text Control's MailMerge component is merging the data rows 'as-is' and data shaping, sorting or queries are done in the application business layer.
Sometimes, it is required to define the order of data rows in the template. This sample shows how to realize this concept based on additional parameters in the merge block name and LINQ to sort the data rows.
Let's have a look at the XML data source. It consists of 2 simple tables: company and product and a relation between those tables.

The template simply consists of a single merge block with the name product. Without any sorting, the results would look like this:

In order to enable sorting, we simply introduce a new parameter to the merge block name:
[BlockName];orderby,[ColumnName],ASC|DESC
For example:
product;orderby,name,ASC
The sample code shows how to detect and parse these merge blocks in the template and how to replace the rows of the relevant DataTables with sorted DataRows.
The class DataSelector is parsing the template in order to sort the given data source. The property Template returns the changed template which is loaded into a MailMerge instance using LoadTemplateFromMemory. Finally, the document is merged with the sorted DataSet:
private string sDataSource = "data.xml";
private string sTemplateFile = "template.tx";
private void mergeToolStripMenuItem_Click(object sender, EventArgs e)
{
// convert the XML file to a .NET DataSet
DataSet ds = new DataSet();
ds.ReadXml(sDataSource, XmlReadMode.Auto);
// create a new DataSelector instance
DataSelector selector = new DataSelector(ds,
File.ReadAllBytes(sTemplateFile));
// load the modified template
mailMerge1.LoadTemplateFromMemory(selector.Template,
TXTextControl.DocumentServer.FileFormat.InternalUnicodeFormat);
// merge the template with the new, sorted data source
mailMerge1.Merge(ds.Tables[0]);
// copy the merged document into a visible TextControl object
byte[] data = null;
mailMerge1.SaveDocumentToMemory(out data,
TXTextControl.BinaryStreamType.InternalUnicodeFormat, null);
textControl1.Load(data,
TXTextControl.BinaryStreamType.InternalUnicodeFormat);
}
The DataSelector class loops through all merge blocks in a given template to check for specific keywords. A temporary ServerTextControl instance is used to recognize blocks with the sorting keyword 'ORDERBY'. If such tagged merge blocks are found, the parameters are parsed, stored and the block name is reset to the original block name.
Finally, LINQ is used to sort the data rows by the given column name. The resulting data rows are replaced in the specific DataTable. The DataTable won't be replaced completely to keep existing data relations and constraints.
// DataSelector
// description: This class loops through all merge blocks in a given template to check
// for sort keywords. The given referenced DataSet will be sorted.
//
// Parameters: dataSet of type DataSet, template as a byte[] array in the InternalUnicodeFormat
public class DataSelector
{
public byte[] Template { get; set; }
public DataSelector(DataSet dataSet, byte[] template)
{
// use a temporary ServerTextControl instance to recognize blocks with
// the sorting keyword 'ORDERBY'
using (TXTextControl.ServerTextControl tx =
new TXTextControl.ServerTextControl())
{
tx.Create();
// load the template
tx.Load(template, TXTextControl.BinaryStreamType.InternalUnicodeFormat);
// create a list of merge blocks
List<MergeBlock> lMergeBlocks = MergeBlock.GetMergeBlocks(MergeBlock.GetBlockMarkersOrdered(tx), tx);
// loop through all merge blocks to check whether they
// have a sorting parameter
foreach (MergeBlock mergeBlock in lMergeBlocks)
{
string sBlockName = mergeBlock.StartMarker.TargetName;
// check for the unique sorting parameter
if (sBlockName.ToUpper().Contains("ORDERBY") == false)
continue;
// create a new SortedBlock object to store parameter
SortedBlock block = new SortedBlock(sBlockName);
// remove the sorting parameter from the block name
// so that MailMerge can find the matching data in the data source
mergeBlock.StartMarker.TargetName = block.Name;
mergeBlock.EndMarker.TargetName = "BlockEnd_" + mergeBlock.Name;
if (dataSet.Tables.Contains(mergeBlock.Name) == false)
continue;
// get all DataRows using LINQ
var query = from product in dataSet.Tables[mergeBlock.Name].AsEnumerable()
select product;
// create a new DataTable with the sorted DataRows
DataTable dtBoundTable = (block.SortType == SortType.ASC) ?
query.OrderBy(product => product.Field<String>(block.ColumnName)).CopyToDataTable() :
query.OrderByDescending(product => product.Field<String>(block.ColumnName)).CopyToDataTable();
// remove original rows and replace with sorted rows
dataSet.Tables[mergeBlock.Name].Rows.Clear();
dataSet.Tables[mergeBlock.Name].Merge(dtBoundTable);
dtBoundTable.Dispose();
}
// save the template
byte[] data = null;
tx.Save(out data, TXTextControl.BinaryStreamType.InternalUnicodeFormat);
this.Template = data;
}
}
}
Let's do two examples. The merge block name contains the following string:
product;orderby,name,ASC
The ascending sorted results are shown below:

Descending sorting results:

LINQ is used to sort the results to show the flexibility. You can customize this process with your own selectors. This shows the flexibility and the power of the MailMerge class. Give it a try to download the sample project directly from GitHub.
Download the sample from GitHub and test it on your own.
Download and Fork This Sample on GitHub
We proudly host our sample code on github.com/TextControl.
Please fork and contribute.
Requirements for this sample
- Visual Studio 2012 or better
- TX Text Control .NET for Windows Forms (trial sufficient)
Reporting
The Text Control Reporting Framework combines powerful reporting features with an easy-to-use, MS Word compatible word processor. Users can create documents and templates using ordinary Microsoft Word skills. The Reporting Framework is included in all .NET based TX Text Control products including ASP.NET, Windows Forms and WPF.
Related Posts
Windows Forms: Printing Multiple Pages Per Sheet
This sample project implements the class MultipagePrintDocument that inherits from System.Drawing.Printing.PrintDocument to print multiple pages of a document per sheet. The constructor of…
Inserting Watermark Images to All Pages Dynamically
This sample project shows how to create and insert a watermark image on all pages dynamically. Image objects have the Name property to store additional string information with an image. This…
MailMerge: Conditional INCLUDETEXT Fields
In documents and reports, often sentences or an appendix are added only under specific conditions. The Text Control reporting engine MailMerge provides the concept of INCLUDETEXT fields to include…
Windows FormsGetting StartedTutorial
Windows Forms Tutorial: Create Your First Windows Forms C# Application
This tutorial shows how to create your first Windows Forms application with C# using TX Text Control .NET for Windows Forms in Visual Studio 2022.
TX Text Control 32.0 Has Been Released
We are pleased to announce the immediate availability of TX Text Control 32.0 for all platforms including ASP.NET, Windows Forms, WPF and ActiveX.