Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Mail Merge Printing with WPF

0.00/5 (No votes)
18 Feb 2011 1  
This article explorers how to use WPF’s document viewing and printing classes to inspect, scale, and output mail merge letters produced from a XAML based template.

Introduction

A mail merge involves merging the records in a database with a document template to produce a set of similar documents where the body of the document is constant but the details, specific to each individual record, change. This article explorers how to use WPF’s document viewing and printing classes to inspect, scale, and output mail merge letters produced from a XAML based template.

Demo Application

In the demo, the template takes the form of a letter to be sent to clients of a Nature Reserve. There is a facility to preview the letters to check that they are formatted correctly and the ability to print in batches so that the whole print run is not lost when the printer decides to trash some paper. All the functionality required is provided by WPF’s document viewing and printing classes; the demo’s code is mainly concerned with interfacing the classes.

Mail Merge Letter Template

XAML, with its many layout items, is ideal for producing the template. The demo uses a UserControl with a DockPanel. The letter’s header and footer TextBlock items are docked to the top and bottom, respectively, with the letter body consisting of more TextBlocks stacked within a StackPanel. Databinding binds the relevant TextBlocks to properties in the ViewModel. The ViewModel, in turn, is populated by individual records from the data set.

<UserControl x:Class="printing.MailMergeLetter"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
       xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
       mc:Ignorable="d" 
       Height="1104" Width="792">
  <UserControl.Resources>
    <Style TargetType="TextBlock" x:Key="BodyText">
      <Setter Property="VerticalAlignment" Value="Center" />
      <Setter Property="FontFamily" Value="Times New Roman"/>
      <Setter Property="FontSize" Value="15"/>
      <Setter Property="TextWrapping" Value="Wrap"/>
    </Style>

  </UserControl.Resources>
   <DockPanel>
     <TextBlock 
         DockPanel.Dock="Top"
         HorizontalAlignment="Center"
         VerticalAlignment="Center"
         Height="30" 
         Margin="0,48,0,48"
         FontFamily="Times New Roman" FontSize="32" >
         <Bold>Hawk's Nature Reserve</Bold>
     </TextBlock>
     <TextBlock 
        Style="{StaticResource BodyText}"
        DockPanel.Dock="Bottom" 
        HorizontalAlignment="Center"
        Margin="0,0,0,60" 
        Name="footer" 
        >
        <Bold>23 Park Meadows Pembroke PB44 7BN Tel 016583946</Bold>
     </TextBlock>
     <TextBlock
        Style="{StaticResource BodyText}"
        DockPanel.Dock="Bottom"
        Margin="96,0,0,192" 
        Text="A. Sparrow-Hawk" 
        />
     <StackPanel>
      <TextBlock 
        Style="{StaticResource BodyText}"
        FontWeight="Bold" 
        Margin="96,20,96,45" 
        Text="{Binding Path=Address}" 
         />
      <TextBlock
        Style="{StaticResource BodyText}"
        Margin="96,25,96,45" 
        Text="{Binding Path=Date}" 
         />
      <TextBlock 
        Style="{StaticResource BodyText}"
        Margin="96,10,96,0" 
        Text="{Binding Path=Salutation}" 
        />
      <TextBlock 
        Style="{StaticResource BodyText}"
        Margin="96,10,96,0" 
        Text="{Binding Path=Body}" 
        />
      <TextBlock
        Style="{StaticResource BodyText}"
        Margin="96,10,96,0"
        Name="validation" 
        Text="Your feathered friend," 
        />
    </StackPanel>
  </DockPanel>
</UserControl>

Constructing the Document

Viewing and printing various types of documentation in WPF is centered around the Document class. By adding the mail merge letters to an object of type Document, all the functionality it provides becomes available to the mail merge. In the demo, the mail merge letters are added to a FixedDocument object and form the individual pages of the Document. A FixedDocument is used as it is designed to hold Visual (UI) elements. The FlowDocument, the other main document type, is not as suitable as it is geared more towards rendering blocks of text stored in XML format.

Adding the mail-merge letters to a Document is straightforward.

FixedDocument fixedDocument = new FixedDocument();
MailMergeLetter mailMergeLetter;
for (int i = 0; i < data.Length; i++)
{
  mailMergeLetter = documentBuilder.BuildPage(data, i);
  PageContent pageContent = new PageContent();
  FixedPage fixedPage = documentBuilder.CreatePage();
  fixedPage.Children.Add(mailMergeLetter);
  ((IAddChild)pageContent).AddChild(fixedPage);
  fixedDocument.Pages.Add(pageContent);
}

this.Document = fixedDocument;

Viewing the Document

.jpg

Viewing the Document is just a matter of using the DocumentViewer control and binding its Document property to the mail merge Document. It is possible to print out the letters using default settings but, to determine how the letters are printed, it's necessary to get control of the DocumentViewer’s OnPrint command. There are two main ways of doing this. One is to replace the control’s template and the other is to subclass the DocumentViewer and override its OnPrint method. The latter approach is used in the demo.

protected override void OnPrintCommand()
{
  PrintDialog printDialog = new PrintDialog();

  // set up initial values for printDialog
  printDialog.UserPageRangeEnabled = true;
  printDialog.PrintTicket = printDialog.PrintQueue.UserPrintTicket;
  printDialog.PrintTicket.PageOrientation = PageOrientation.Portrait;
  printDialog.PrintQueue = LocalPrintServer.GetDefaultPrintQueue();
  if (printDialog.ShowDialog() == true)
   {
     this.PerformPrintRun(printDialog);
   }
}

The PrintDialog control allows the user to configure the dialog's PrintTicket class. The PrintTicket contains information about how the printing is to be carried out. The options available for the PrintTicket depend upon the capabilities of the selected printer. It's not necessary to interrogate the printer to determine its properties as this is neatly done by the PrintDialog.

Printing the Mail Merge

The FixedDocument has a DocumentPaginator object. One of the DocumentPaginator’s tasks is to present individual pages from the Document to the printer for printing. This is done using the paginator’s GetPage method. But, as it stands, the GetPage method does not have the ability to print a selected range of pages. So, to use the DocumentPaginator for printing a range of pages, it's necessary to subclass the DocumentPaginator and override the GetPage method.

public override DocumentPage GetPage(int pageNumber)
{
    PageContent pageContent = 
       this.fixedDocument.Pages[pageNumber + this.startIndex];
    return new DocumentPage(pageContent, this.paperSize, 
           new Rect(this.paperSize), new Rect(this.paperSize));
}

The number of calls to GetPage is determined by the Paginator's PageCount property. A range of pages can be printed by setting PageCount to the appropriate value and summing the pageNumber with the StartIndex obtained from the page range selection of the PrintDialog control. But there is a potential problem with this technique. Once a Visual becomes part of a Document's Pages collection, it becomes inextricably bound to the collection, and trying to associate it with another element can throw an exception along the lines of "Specified element is already the logical child of another element. Disconnect it first" - only you can't disconnect it as the Parent property is read only. This exception is somewhat idiosyncratic. Its appearance seems to depend upon the Operating Ssystem and the print driver.

Fortunately, there is an alternative method of printing that does not use a DocumentPaginator but creates a VisualsToXpsDocument to write the MailMerge Visuald directly to the PrintQueue in batch mode. This can be called from the DocumentViewer's OnPrint method, and is best used with a FixedDocument where the pages are of a predefined fixed size and the Paginator's function is limited.

XpsDocumentWriter writer = 
   PrintQueue.CreateXpsDocumentWriter(printDialog.PrintQueue);
VisualsToXpsDocument visualsToXpsDoc = 
        (VisualsToXpsDocument)writer.CreateVisualsCollator();
Size scaling = this.GetScalingFactor();
Size paperSize = this.GetPaperSize();
visualsToXpsDoc.BeginBatchWrite();
for (int i = startIndex; i <= endIndex; i++)
{
    PageContent pageContent = this.fixedDocument.Pages[i];
    pageContent.Child.LayoutTransform = 
         new ScaleTransform(scaling.Width, scaling.Height);
    pageContent.Child.Measure(paperSize);
    pageContent.Child.Arrange(new Rect(paperSize));
    visualsToXpsDoc.Write(pageContent);
}

visualsToXpsDoc.EndBatchWrite();

The PrintDialog control often allows the user to select a paper size, which means that it may be necessary to scale the MailMerge to fit the paper. To accommodate scaling, pageContent.Child.LayoutTransform is set to the appropriate scaling factor. Then the amount of space requested for each element is determined by calling Measure and, finally, the layout is updated by calling the Arrange method.

Conclusion

The methods described here are not the most efficient way to print documents. But they do allow scaling to different paper sizes and a print range selection to be made.

Acknowledgements

I’d like to express my gratitude to CodeProject’s regular contributors. They have taught me most of what I know. Any inadequacies in this article are entirely due to my own short comings and not theirs.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here