How to: Create a Master-Detail Report

  • 8 minutes to read

This article describes how the Mail Merge feature enhanced with DOCVARIABLE specifics allows users to create Master-Detail reports.

In this example, the report is generated using four templates: Main, Master, Detail, and DetailDetail containing DOCVARIABLE fields. A RichEditDocumentServer instance is created on each data level to hold the corresponding template and calculate in the RichEditDocumentServer.CalculateDocumentVariable event handler. As a result, all intermediate documents are combined into a single report, which is passed to the parent document to substitute the initial DOCVARIABLE field.

The following image displays the result.

OfficeFileAPI_MasterDetail_Result

Main Template

The main template is the document without MERGEFIELD fields. It may contain a logo, standard header and footer, and all the parts required for a corporative design and layout. The template’s DOCVARIABLE field indicates where the Master template is inserted. Use the FieldCollection.Create method to insert a field into the desired position in the document.

Initiate the mail merge process with empty data. Assign the control to a mock data source and call the Document.MailMerge method with the resulting control’s document or a file set as the destination (in this example, the result is passed to the resultRichEdit).

// Start the process by merging main template into the document contained within the resultRichEdit server.
public void Start()
{
    // Since the main template contains no merge fields requiring no merge data, provide a fake data source.
    // Otherwise mail merge will not start.
    mainRichEdit.Options.MailMerge.DataSource = fakeDataSource;
    // Trigger the multistage process. After the first mail merge the CalculateDocumentVariable event
    //for the resultRichEdit server fires.
    mainRichEdit.MailMerge(resultRichEdit.Document);
    resultRichEdit.SaveDocument("result.docx", DocumentFormat.OpenXml);
}

Master Template

The destination control receives the Main document and fires the RichEditDocumentServer.CalculateDocumentVariable event. Handle this event the following way to create a master part and insert it into the document:

  1. Check the CalculateDocumentVariableEventArgs.VariableName value to make sure that the event is fired for the required field.
  2. Create a new RichEditDocumentServer instance and use the CalculateDocumentVariableEventArgs.Value property to pass it to the event sender.
  3. Subscribe the created RichEditDocumentServer to the RichEditControl.CalculateDocumentVariable event to process the detail part.
  4. Call the Document.MailMerge method to merge the Master template and obtain the source document with the corresponding data.
  5. Pass the document to insert to the e.Value property.
  6. Set the Handled property to true. Otherwise, the previous step is ignored.
// Second stage. For each Supplier ID create a detailed document that will be inserted in place of the DOCVARIABLE field.
void resultRichEdit_CalculateDocumentVariable(object sender, CalculateDocumentVariableEventArgs e)
{
    if (e.VariableName == "Supplier") {
        // Create a text engine to process a document after the mail merge.
        RichEditDocumentServer richServerMaster = new RichEditDocumentServer();
        // Provide a procedure for further processing
        richServerMaster.CalculateDocumentVariable += richServerMaster_CalculateDocumentVariable;
        // Create a merged document using the Supplier template. The document will contain DOCVARIABLE fields with ProductID arguments. 
        // The CalculateDocumentVariable event for the richServerMaster fires.
        supplierRichEdit.MailMerge(richServerMaster);
        richServerMaster.CalculateDocumentVariable -= richServerMaster_CalculateDocumentVariable;
        // Return the document to insert.
        e.Value = richServerMaster;
        // Required to use e.Value. Otherwise it will be ignored.
        e.Handled = true;
    }
}

Detail Template

After the mail merge, the document created from the Master template is loaded into the RichEditDocumentServer instance, and the RichEditDocumentServer.CalculateDocumentVariable event fires. Perform the following steps in the event handler:

  1. Check the CalculateDocumentVariableEventArgs.VariableName value to make sure that the event is fired for the required field;
  2. Create new RichEditDocumentServer instance.
  3. This template contains a master-detail table header. The resulting table is composed of separate tables. Use the MergeMode.JoinTables option to make the tables continuous.

    Skip this step if your template does not contain tables. Otherwise, the result might have an incorrect layout.

  4. Subscribe to the Document.CalculateDocumentVariable event of the current RichEditDocumentServer instance. This step is required if the Detail template contains any DOCVARIABLE fields. In this example, the Document.CalculateDocumentVariable event is used to insert and format the UnitPrice field.
  5. Call the Document.MailMerge method to merge the Detail template.
  6. Pass the document to insert to the e.Value property.
  7. Set the Handled property to true. Otherwise, the previous step is ignored.
// Third stage. For each Product ID create a detailed document that will be inserted in place of the DOCVARIABLE field.
void richServerMaster_CalculateDocumentVariable(object sender, CalculateDocumentVariableEventArgs e)
{
    int currentSupplierID = GetID(e.Arguments[0].Value);
    if (currentSupplierID == -1)
        return;

    if (supplierID != currentSupplierID) {
        // Get data source that contains products for the specified supplier.
        dataDetailedForProducts = GetProductsDataFilteredbySupplier(currentSupplierID);
        supplierID = currentSupplierID;
    }

    if (e.VariableName == "Product") {
        // Create a text engine to process a document after the mail merge.
        RichEditDocumentServer richServerDetail = new RichEditDocumentServer();
        // Specify data source for mail merge.
        MailMergeOptions options = productRichEdit.CreateMailMergeOptions();
        options.DataSource = dataDetailedForProducts;
        // Specify that the resulting table should be joined with the header table.
        // Do not specify this option if calculated fields are not within table cells.
        options.MergeMode = MergeMode.JoinTables;
        // Provide a procedure for further processing.
        richServerDetail.CalculateDocumentVariable += richServerDetail_CalculateDocumentVariable;
        // Create a merged document using the Product template. The document will contain DOCVARIABLE fields with OrderID arguments. 
        // The CalculateDocumentVariable event for the richServerDetail fires.
        productRichEdit.MailMerge(options, richServerDetail);
        richServerDetail.CalculateDocumentVariable -= richServerDetail_CalculateDocumentVariable;
        // Return the document to insert.
        e.Value = richServerDetail;
        // This setting is required for inserting e.Value into the source document. Otherwise it will be ignored.
        e.Handled = true;
    }
}

Detail-Detail Template

This is the final merging part. Handle the RichEditDocumentServer.CalculateDocumentVariable event to compose this part as follows:

  1. Make sure that the event is fired for the required field (by checking the CalculateDocumentVariableEventArgs.VariableName value).
  2. Create new RichEditDocumentServer and MailMergeOptions instances.
  3. This template also contains a master-detail table header. Set the MailMergeOptions.MergeMode property to MergeMode.JoinTables to make the tables continuous. When not using tables, assigning this value to the MergeMode property results in an incorrect layout.
  4. Provide the data source for merging. In this example, the data is filtered by supplier, then retrieved and passed as the RichEditMailMergeOptions.DataSource property value.
  5. Call the Document.MailMerge method with given mail merge options to merge the Detail-Detail template.
  6. Pass the document to insert to the e.Value property.
  7. Set the Handled property to true. Otherwise, the previous step is ignored.
// Fourth stage. For each Order ID create a detailed document that will be inserted in place of the DOCVARIABLE field.
// This is the final stage and the Product.Orders template does not contain DOCVARIABLE fields. So, further processing is not required.
void richServerDetail_CalculateDocumentVariable(object sender, CalculateDocumentVariableEventArgs e)
{
    int currentProductID = GetID(e.Arguments[0].Value);
    if (currentProductID == -1)
        return;

    if (productID != currentProductID) {
        // Get data source that contains orders for the specified product. 
        // The data source is obtained from the data already filtered by supplier.
        dataDetailedForOrders = GetOrderDataFilteredbyProductAndSupplier(currentProductID);
        productID = currentProductID;
    }

    if (e.VariableName == "OrderDetails") {

        RichEditDocumentServer richServerDetailDetail = new RichEditDocumentServer();
        MailMergeOptions options = ordersRichEdit.CreateMailMergeOptions();
        options.DataSource = dataDetailedForOrders;
        options.MergeMode = MergeMode.JoinTables;
        ordersRichEdit.MailMerge(options, richServerDetailDetail);
        e.Value = richServerDetailDetail;
        e.Handled = true;
    }
}
See Also