In the second part of this series, I will be covering how to put data onto static page templates and how to handle overflow.
Page-Masters are setup as User-Controls and are sequenced using code logic. In order to position data onto a page-master you need to add controls. In the following example I’ve added an address and barcode to my invoice page-master and then exposed the data elements using class properties:
Xaml |
<UserControl x:Class="WpfInvoice.template001.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 x:Name="Address" Height="85" Canvas.Left="50" TextWrapping="Wrap" Canvas.Top="130" Width="320" FontFamily="Consolas" FontSize="12"><Run Language="en-gb" Text="Address"/></TextBlock> <TextBlock x:Name="BarCode" Height="20" Canvas.Left="630" TextWrapping="Wrap" Text="TextBlock" Canvas.Top="790" Width="315" FontFamily="BC C39 3 to 1 Wide" FontSize="20" RenderTransformOrigin="0.5,0.5" Foreground="#FF83837E"> <TextBlock.RenderTransform> <TransformGroup> <ScaleTransform/> <SkewTransform/> <RotateTransform Angle="-90"/> <TranslateTransform/> </TransformGroup> </TextBlock.RenderTransform> </TextBlock> <Border BorderBrush="Black" BorderThickness="1" Height="100" Canvas.Left="40" Canvas.Top="120" Width="340" CornerRadius="10,10,10,10" Background="#FFFBFFB1"/> </Canvas> </UserControl>
|
C# class properties |
namespace WpfInvoice.template001 { ///<summary> /// Interaction logic for InvoicePage.xaml ///</summary> publicpartialclassCoverPage : UserControl { publicstring _barCode { get { return BarCode.Text; } set { BarCode.Text = value; } }
publicstring _address { get { return Address.Text; } set { Address.Text = value; } }
public InvoicePage() { InitializeComponent();
} } }
|
The C# is straightforward, but the Xaml looks daunting to say the least!
Don’t worry; you don’t really need to be an expert in Xaml to understand this kind of thing, as most of it is auto-generated code. As you drag-n-drop onto the design surface and change properties within the UI designer, the markup adjusts automatically (you very rarely need to make change to the markup manually).
In the case of the address, I simply resized and dragged a textblock to the top left hand quadrant of the page and placed it within a rounded-corners-box. With the barcode I selected the barcode font I wanted and then rotated it 90 degrees anti-clockwise and placed it on the right-hand-lip of the page. As you can see, you have far more-control over the precise cosmetics of the design surface than you would have in SSRS.
· Things like rounded corners, text-rotation, shading, and gradients (etc) are very difficult effects to achieve in SSRS. The Xaml designer is feature rich and is only limited by the capabilities of the control-suite that you decide to use.
Populating the data is straightforward and is just a question of setting the user-control properties:
C# sample to add a new page and set its data properties |
// invoice page template001.InvoicePage uc = new template001.InvoicePage(); page.Children.Add(uc);
// populate invoice-page uc._barCode = "12345"; uc._invoiceTo = "Joe Bloggs\nTest address\nTest town";
// add the page to the document PageContent pageContent = newPageContent(); ((IAddChild)pageContent).AddChild(page); document.Pages.Add(pageContent);
|
· Typically, these properties would be “data-merged” from AOS data.
Now we come to the difficult bit… how to handle “dynamically-expanding-data” and “page-overflow”.
Dynamically expanding data is conventionally housed within a “grid” control (similar to the tablix in SSRS). Column and row definitions within the markup define the number and size of each respective element. In the case of a dynamically expanding grid, you only need to define 1 row (initially) to act as your guide. When the column dimensions of this guide row are correct, you can simply comment the first row it out as per the sample below:
Sample Xaml for dynamically expanding grid |
<Grid x:Name="GridGuide" Visibility="Visible" Canvas.Left="40" Canvas.Top="320" Width="720" ShowGridLines="True"> <Grid.RowDefinitions> <!--<RowDefinition Height="25"></RowDefinition> <RowDefinition Height="25"></RowDefinition>--> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="5"></ColumnDefinition> <ColumnDefinition Width="78*"/> <ColumnDefinition Width="2*"/> <ColumnDefinition Width="14*"/> <ColumnDefinition Width="2*"/> <ColumnDefinition Width="14*"/> <ColumnDefinition Width="2*"/> <ColumnDefinition Width="14*"/> <ColumnDefinition Width="2*"/> <ColumnDefinition Width="14*"/> <ColumnDefinition/> </Grid.ColumnDefinitions> </Grid>
|
For visual reference, the above Xaml relates to a grid that has been designed to populate the following template:
Additional rows are now added to the grid using the following method:
Sample C# to populate grid row and check for overflow |
// add a new row to the grid RowDefinition rowDef = newRowDefinition(); uc.GridGuide.RowDefinitions.Add(rowDef);
// populate 1st cell TextBlock description = newTextBlock(); description.Text = "*** Description ***"; description.TextWrapping = TextWrapping.Wrap; Grid.SetRow(description, gridRow); Grid.SetColumn(description, 1); uc.GridGuide.Children.Add(description);
// ...continue populating all cells
// tell the rendering engine to take new measurements for the updated layout uc.GridGuide.UpdateLayout(); uc.GridGuide.Measure(newSize(Double.PositiveInfinity, Double.PositiveInfinity)); uc.GridGuide.Arrange(newRect(uc.GridGuide.DesiredSize));
// handle overflow if (uc.GridGuide.ActualHeight > 634) { rowDef.Height = newGridLength(0); uc._continued = "Continued on next page"; break; }
|
· Wpf allows the run-time addition and measurement of UI controls.
The key things to note in the above code are “the-measuring-of-the-grid” and “checking-for-overflow”. After each addition of a new row to the grid-control we must check to see if we have exceeded the print boundaries. This is done by checking against the absolute height of the print region. In the example above this is hard-coded to 168mm (or 634 pixels – refer to previous article for more information on Wpf measurement conversions). Once we hit this threshold, overflow processing kicks in. In this case, I wanted to print a “continuation” message in a TextBox at the bottom of the page, then proceed with a duplex “Terms-and-Conditions” page and then start with the next page of the invoice data.
This requires some page-sequencing code in the overflow handler (refer to previous article).
So, in summary, we have now have a solution that handles “Page-Masters”, “Page-Sequencing”, “Data-merging”, “Duplex-printing” and “Overflow-handling”. In the next article, I will cover how to mix page orientations and disucss the overall performance of this type of solution.
REGARDS