Following on from my previous article [http://community.dynamics.com/product/ax/axtechnical/b/dynamicsax_wpfandnetinnovations/archive/2013/01/26/the-problems-with-ssrs.aspx] … I would like to talk more on how we can go about creating “data-merged print-publication” documents in Dynamics. I’ve already covered the trials and tribulations of using SSRS or MSOffice when trying to achieve this goal. Now let’s see what Windows Presentation Foundation can bring to the table…
MSDN quotes in the following article [http://msdn.microsoft.com/en-us/library/ms748388.aspx]
Windows Presentation Foundation (WPF) offers a wide range of document features that enable the creation of high-fidelity content that is designed to be more easily accessed and read than in previous generations of Windows. In addition to enhanced capabilities and quality, WPF also provides integrated services for document display, packaging, and security. |
Sounds impressive, let’s put it to the test to see if we can get some of the “Desktop-Publishing” features that we need to satisfy our requirements.
· By now, you should already be familiar with the concept that a WPF Control can be housed inside Dynamics (and interact seamlessly with it). This article will cover the creation of a WPF UserControl that will display data merged documents inside Dynamics.
An SSRS generated report is nothing more than an A4 sized “windows-form” that contains labels, data and graphics (or whatever your paper-size). This is no different to the concept of generating a form within WPF.
WPF allows the developer to create the page by positioning data and graphic elements onto a form surface (called a “canvas”). This is all done via an Xml markup language called XAML. There are a few “visual-designers” in the marketplace that allow you to “mock-up” your canvas; however, nothing currently surpasses the native “Metro-style” designer available inside Visual Studio 2012. Unfortunately, this version of Visual Studio is not currently compatible with Dynamics 2012 (despite them both having “2012” in their titles!)
There are two types of documents that you can create using Wpf:
· FixedDocument: Document page layouts are static (i.e. print regions are fixed).
· FlowDocument: Document page layouts are dynamic (i.e. print regions can grow).
I’m going to concentrate on “FixedDocuments” as this will address all the issues raised thus far. “FlowDocuments” will only give us the same dynamic resizing problems that we face in SSRS. Fixed documents can be rendered and written directly to disk, but during the design phase you need to see what you are working on so a Windows “DocumentViewer” control is required. This control is defined and initialised as follows:
Xaml |
<DocumentViewer Name="_viewer" Width="Auto" Height="Auto"/>
|
Code |
// create a document (collection of pages) and set the default page dimensions to match the printer FixedDocument document = newFixedDocument(); document.DocumentPaginator.PageSize = newSize(UnitToWpf(210, "mm"), UnitToWpf(297, "mm"));
// create page and set the default page dimensions to match the printer FixedPage page = newFixedPage(); page.Width = document.DocumentPaginator.PageSize.Width; page.Height = document.DocumentPaginator.PageSize.Height;
...code to populate document
// preview the document _viewer.Document = document;
|
· As it stands nothing will be displayed yet in the DocumentViewer control.
The next thing to discuss is duplex printing. We need to be able to print our report so that there is a “reverse-page” printing option for things like “terms and conditions”. This is done via something known as “Page-Sequencing” of “Page-Masters”.
Let’s take a very simple example of an Invoice that requires some terms and conditions on the back. First you need to define two page-masters. This is done by adding two “user-controls” to your Wpf project that will house the front-page and the reverse-page:
InvoicePage.xaml |
<UserControl x:Class="WpfInvoice.InvoicePage" 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" d:DesignHeight="1120" d:DesignWidth="800"> <Canvas> <TextBlock Text="Invoice page data" /> </Canvas> </UserControl>
|
InvoicePageDuplex.xaml |
<UserControl x:Class="WpfInvoice.InvoicePageDuplex" 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" d:DesignHeight="1120" d:DesignWidth="800"> <Canvas> <TextBlock Text="Terms and conditions" /> </Canvas> </UserControl>
|
· Note, the DesignHeight and DesignWidth are measured in pixels. The values above equate to an A4-portrait page (210 x 297) mm.
If you are emulating printed stationary then you will need to translate ruler-measurements to wpf-measurements using the following helper functions:
Helper functions for WPF measurement translation |
#region Helper functions // convert measurement to Wpf pixels publicdouble UnitToWpf(double _sourceValue, string _sourceUnit) { switch (_sourceUnit.ToLower()) { case"inch": return (_sourceValue * 96); case"mm": return (_sourceValue * 3.779527); case"point": return (_sourceValue * 1.333333); default: return 0; } }
// convert measurement from Wpf pixels publicdouble UnitFromWpf(double _sourceValue, string _sourceUnit) { switch (_sourceUnit.ToLower()) { case"inch": return (_sourceValue * 0.01041666); case"mm": return (_sourceValue * 0.26458333); case"point": return (_sourceValue * 0.75); default: return 0; } } #endregion
|
Once you have the pages defined then you can simply use them as "Types" and add them to blank pages thus:
Page-sequencing sample code |
// create a document (collection of pages) and set the default page dimensions to match the printer FixedDocument document = newFixedDocument(); document.DocumentPaginator.PageSize = newSize(UnitToWpf(210, "mm"), UnitToWpf(297, "mm"));
// create page and set the default page dimensions to match the printer FixedPage page = newFixedPage(); page.Width = document.DocumentPaginator.PageSize.Width; page.Height = document.DocumentPaginator.PageSize.Height;
{ // invoice page template001.InvoicePage uc = new template001.InvoicePage(); page.Children.Add(uc);
// populate invoice-page // ...
// add the page to the document PageContent pageContent = newPageContent(); ((IAddChild)pageContent).AddChild(page); document.Pages.Add(pageContent); } { // invoice duplex page template001.InvoicePageDuplex uc = new template001.InvoicePageDuplex(); page.Children.Add(uc);
// populate invoice-duplex-page // ...
// add the page to the document PageContent pageContent = newPageContent(); ((IAddChild)pageContent).AddChild(page); document.Pages.Add(pageContent); }
// preview the document _viewer.Document = document;
|
If you compile and run your project, then you will be able to see 2 pages within the DocumentViewer control. You can zoom-in, resize and arrange the pages as you would in any standard document viewer. Try and print this document using the printer icon button:
· If you have a duplex enabled printer then you will get double-sided printing.
This is not very glamourous (or useful) yet, but it has demonstrated the fundamental principle of page-sequencing. We now have the ability to decide which “page-master” will print and in what order. In the next article I will be discussing how we can go about populating these pages with some meaningful data and how to handle page-overflow.
REGARDS