As product manager I love it when a feature can be accomplished by combining several existing ComponentOne controls into one solution. That is the case with this print preview sample that combines the C1PdfViewer control, the C1Pdf class library, and several other UI controls to create a complete print preview window for the C1FlexGrid and C1DataGrid controls. In short, the datagrid control gets saved to a PDF file in memory that is then displayed in the PdfViewer. From there users can adjust page settings, save as PDF or print.

For further discussion I will only mention the FlexGrid implementation, but there is a separate version of the same sample for C1DataGrid as well.

Download Sample

It starts on the Silverlight page containing a FlexGrid control. We make a call to the new PrintPreview method.

_flex.PrintPreview();

This calls the PrintPreview extension method that is found in the C1.Silverlight.FlexGrid.PrintPreview library, which is a separate sample that you could simply add to your existing solution and use as-is with the one line of code above. But now let me walk you through how it works.

public static class C1FlexGridPreviewExtension
{
    public static void PrintPreview(this C1FlexGrid grid)
    {

        grid.PrintPreview(new PageSettings()
        {
            // create default settings
            Margins = new Thickness(72),
            ScaleMode = ScaleMode.PageWidth,
            IsLandscape = false,
            PaperKind = PaperKind.Letter,
            FooterLeftText = "[Date], [Time]",
            FooterRightText = "Page [Page] of [PageCount]",
            ShowHeaderSeparator = false,
            ShowFooterSeparator = true
        });
    }

    public static void PrintPreview(this C1FlexGrid grid, PageSettings pageSettings)
    {
        PrintPreviewWindow ppw = new PrintPreviewWindow(grid, pageSettings);
        ppw.WindowState = C1WindowState.Maximized;
        ppw.Show();
    }

}

This method takes a page settings parameter (which is optional with an override), then it instantiates a PrintPreviewWindow control.

The PageSettings class just holds the page related information that users want like margins, headers, footers, page orientation and even paper size. All of these settings will be taken into account when the pdf document is later generated. If no page settings are defined then hard-coded defaults will be used instead.

The PrintPreviewWindow class is based on a C1Window so it can be modal or modeless. It contains a C1PdfViewer control and handles the job of loading a PDF document to the control. It also contains a custom page setup button within the C1PdfViewer toolbar. When users click this button a separate PageSetupWindow is displayed.

The PageSetupWindow is another C1Window. It allows users to change the page settings on the fly. These settings are passed back to the PrintPreviewWindow and the document gets regenerated.

Generating the PDF

The solution also uses the ComponentOne PDF for Silverlight library to generate the documents on the client side. This is done through another extension method called ToPdf which generates a C1PdfDocument object given a FlexGrid and some page settings. The PDF library contains useful information too, like all common paper sizes and their dimensions, so we don’t have to program that ourselves.

The PdfExtension method, ToPdf, uses the GetPages method of the C1FlexGrid control to get each page from the control as they would appear if printed. GetPages needs to know only a couple things like scaling mode and page size, both of which are gathered from the PageSettings object. The method then returns the final C1PdfDocument back to the PrintPreviewWindow. Most of the code below is to handle the various page settings as the pdf is generated.

public static C1PdfDocument ToPdf(this C1FlexGrid grid, PageSettings pageSettings)
{
    // get root element to lay out the PDF pages
    Panel root = null;
    for (var parent = grid.Parent as FrameworkElement; parent != null; parent = parent.Parent as FrameworkElement)
    {
        if (parent is Panel)
        {
            root = parent as Panel;
        }
    }

    // create pdf document
    var pdf = new C1PdfDocument();

    // set landscape
    pdf.Landscape = pageSettings.IsLandscape;

    // set paper kind
    pdf.PaperKind = pageSettings.PaperKind;

    // set custom page size
    if (pageSettings.PaperKind == PaperKind.Custom)
    {
        pdf.PageSize = pageSettings.CustomPageSize;
    }

    // get page size
    var rc = pdf.PageRectangle;

    // create panel to hold elements while they render
    var pageTemplate = new PageTemplate();
    pageTemplate.Width = rc.Width;
    pageTemplate.Height = rc.Height;
    pageTemplate.SetPageMargin(pageSettings.Margins);
    root.Children.Add(pageTemplate);

    // render grid into PDF document
    var sz = new Size(rc.Width - pageSettings.Margins.Left - pageSettings.Margins.Right, rc.Height - pageSettings.Margins.Top - pageSettings.Margins.Bottom);
    var pages = grid.GetPageImages(pageSettings.ScaleMode, sz, 100);
    for (int i = 0; i < pages.Count; i++)
    {
        // skip a page when necessary
        if (i > 0)
        {
            pdf.NewPage();
        }

        // set content
        pageTemplate.PageContent.Child = pages;
        pageTemplate.PageContent.Stretch = System.Windows.Media.Stretch.Uniform;

        // set header/footer text
        pageTemplate.HeaderLeft.Text = FormatPlaceHolders(pageSettings.HeaderLeftText, i + 1, pages.Count);
        pageTemplate.HeaderRight.Text = FormatPlaceHolders(pageSettings.HeaderRightText, i + 1, pages.Count);
        pageTemplate.FooterLeft.Text = FormatPlaceHolders(pageSettings.FooterLeftText, i + 1, pages.Count);
        pageTemplate.FooterRight.Text = FormatPlaceHolders(pageSettings.FooterRightText, i + 1, pages.Count);

        // set header separator
        if (pageSettings.ShowHeaderSeparator)
        {
            pageTemplate.BorderHeader.BorderThickness = new Thickness(0, 0, 0, 1);
        }
        else
        {
            pageTemplate.BorderHeader.BorderThickness = new Thickness(0, 0, 0, 0);
        }

        // set footer separator
        if (pageSettings.ShowFooterSeparator)
        {
            pageTemplate.BorderFooter.BorderThickness = new Thickness(0, 1, 0, 0);
        }
        else
        {
            pageTemplate.BorderFooter.BorderThickness = new Thickness(0, 0, 0, 0);
        }

        // measure page element
        pageTemplate.Measure(new Size(rc.Width, rc.Height));
        pageTemplate.UpdateLayout();

        // add page element to PDF
        pdf.DrawElement(pageTemplate, rc);
    }

    // done with template
    root.Children.Remove(pageTemplate);

    return pdf;
}

Conclusion

The sample is a little complex, so I didn’t paste every line of code in this blog post. I encourage you to download the sample to see the entire code. The C1DataGrid sample works almost identically to C1FlexGrid with the only difference being in the GetPages implementation.

Download Sample

Issues Found

During my testing I was not completely satisfied with the performance of this solution. I found that the more pages needed the longer it takes to load the print preview. I decided to publish this sample anyway figuring it may be OK for most. It might be acceptable to display a progress bar as the preview window is loading. Also, for the record, FlexGrid outperforms DataGrid in this instance. The key differentiating factors here are the GetPages method on the control itself and the time it takes C1PdfDocument to draw each page. This solution writes each cell to the document as text so the document is searchable within the preview window. This might not be a necessary feature and we might be able to improve performance if we generate a bitmap of each page instead. That is a future project.

Tags