Printing a XAML ItemsControl from a Windows 8 Store app

This article describes how to print the contents of an ItemsControl in a XAML and MVVM based Windows 8 Store app. The code is an evolution of the printing framework that I published a couple of months ago. In a nutshell, this framework wraps the pages to be printed inside a RichTextBlock control. That is the only WinRT control that properly supports pagination, at least when you connect it to a TextBlockOverflow. Pagination is the only real problem to overcome if you want to print dynamic pages. A RichTextBlock is limited in the sense that it can only contain Paragraphs. So if you want to print an ItemsControl, then you have to make it the content of a Paragraph. That means you have to wrap it in an InlineUIContainer first. Confused? Well, here's how the body of the print page looks like in the attached sample app:

<RichTextBlock>
    <Paragraph>
        <InlineUIContainer>
            <Image Source="ms-appx:///Assets/droids.png"
                    Height="400" />
        </InlineUIContainer>
    </Paragraph>
    <Paragraph>
        <InlineUIContainer>
            <ItemsControl ItemsSource="{Binding Droids}"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Stretch"
                            Margin="0">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Grid Margin="0 20">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="auto" />
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="auto" />
                                <RowDefinition Height="auto" />
                            </Grid.RowDefinitions>

                            <Image Source="{Binding Image}"
                                    Height="60"
                                    Width="60"
                                    Margin="0 20 20 20"
                                    Opacity="1" />
                            <TextBlock Grid.Column="1"
                                        Text="{Binding Name}"
                                        Foreground="Black"
                                        FontSize="36"
                                        Margin="0 20" />
                            <TextBlock Grid.Row="1"
                                        Grid.ColumnSpan="2"
                                        TextWrapping="Wrap"
                                        Text="{Binding Description}"
                                        Foreground="Black"
                                        FontSize="18"
                                        Margin="0 0 20 20" />
                        </Grid>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </InlineUIContainer>
    </Paragraph>
</RichTextBlock>

The first version of my printing framework rendered the paragraphs one by one to see if they fitted the print page. A paragraph that could not be fully added to the page, automatically overflowed entirely to the next print page Here’s the original code:

// Move content from print page to print template - paragraph by paragraph.
var printPageRtb = printPage.Content as RichTextBlock;
while (printPageRtb.Blocks.Count > 0)
{
    var paragraph = printPageRtb.Blocks.First() as Paragraph;
    printPageRtb.Blocks.Remove(paragraph);

    var container = paragraph.Inlines[0] as InlineUIContainer;
    if (container != null)
    {
        // Place the paragraph in a new textblock, and measure it.
        var measureRtb = new RichTextBlock();
        measureRtb.Blocks.Add(paragraph);
        PrintingRoot.Children.Clear();
        PrintingRoot.Children.Add(measureRtb);
        PrintingRoot.InvalidateMeasure();
        PrintingRoot.UpdateLayout();
        measureRtb.Blocks.Clear(); // .Remove(paragraph);

        // Apply line height to trigger overflow.
        paragraph.LineHeight = measureRtb.ActualHeight;
    }

    firstPage.AddContent(paragraph);
};


Unfortunately that approach didn't allow an ItemsControl to span more than one print page, so the framework was useless if you wanted to print long lists. Recently, I had to deliver an app that needed to print out a list of records, possibly a long list. So I created a sample app to isolate the problem. Here's a screenshot of it. The list on the left doesn’t fit on the screen, but also not on a single print page:

Here are some vain attempts to solve the issue. I first tried to create an ItemsControl with a RichTextBlock as ItemsPanel and a Paragraph as Item Template root. The idea was to render this control directly (as part of the current screen, that's what the printing framework does with all controls) and then move the paragraph children to the print page. But the construction of such an ItemsControl is not allowed: RichTextBlock is not a Panel *and* Paragraph cannot be used as Item. Making an ItemsControl behave like a Paragraph is not possible, so I tried to do the opposite: creating a RepeatingParagraph control, a Paragraph with an ItemsSource property. Unfortunately the classes in the Windows.UI.Xaml.Documents namespace are sealed (whose idea is that?), and with attached properties I couldn’t get deep enough into the binding mechanisms. In the meantime, the deadline of the app was approaching. So I decided to change the core framework logic instead of the controls.

I refactored the Paragraph rendering to a separate method:

// Renders a Paragraph
private double Render(Paragraph paragraph)
{
    var measureRtb = new RichTextBlock();
    measureRtb.Blocks.Add(paragraph);
    PrintingRoot.Children.Clear();
    PrintingRoot.Children.Add(measureRtb);
    PrintingRoot.InvalidateMeasure();
    PrintingRoot.UpdateLayout();
    measureRtb.Blocks.Clear();

    return measureRtb.ActualHeight;
}


When rendering the print page, I verified for each Paragraph whether or not it was embedding an ItemsControl. These paragraphs got the new treatment. For all others the original code was called:

// Move content from print page to print template - paragraph by paragraph.
var printPageRtb = printPage.Content as RichTextBlock;
while (printPageRtb.Blocks.Count > 0)
{
    var paragraph = printPageRtb.Blocks.First() as Paragraph;
    printPageRtb.Blocks.Remove(paragraph);

    var container = paragraph.Inlines[0] as InlineUIContainer;
    if (container != null)
    {
        var itemsControl = container.Child as ItemsControl;
        if (itemsControl != null)
        {
            // New code to render an ItemsControl.
            // ...
        }
        else
        {
            // Original code to render a complex Paragraph
            // ...
        }
    }
    else
    {
        // Original code to render a simple Paragraph
        // ...
    }
};


After a few iterations, I came up with this algorithm to render all items in an ItemsControl as a separate Paragraph, allowing individual items to overflow to the next print page. First we have to make sure that the control reads its ItemsSource. That is done by … rendering it. Then we walk through the rendered children via the ItemContainerGenerator. We wrap each item in a new InlineUIContainer and in a new Paragraph. We render that Paragraph again,  to measure it. Then we move that Paragraph to the print page. Here's the code, the whole code, and nothing but the code:

// Render the paragraph, to read the ItemsSource
this.Render(paragraph);

// Render children individually
foreach (var item in itemsControl.Items)
{
    var x = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as ContentPresenter;
    Paragraph p = new Paragraph();
    InlineUIContainer c = new InlineUIContainer();
    var o = x.ContentTemplate.LoadContent() as UIElement;
    (o as FrameworkElement).DataContext = item;
    c.Child = o;
    p.Inlines.Add(c);
    p.LineHeight = this.Render(p);
    firstPage.AddContent(p);
}


Here’s the result in the sample app. The list nicely spans two pages:

Here’s the code for the sample app, including the whole printing framework: U2UConsult.Win8.PrintItemsControl.zip (3.18 mb)

Enjoy!

Diederik