Printing in Silverlight - Part 1



Printing a document is easy in Silverlight. The tricky part comes when you print multiple pages. In this post, I will cover all the aspects of printing in Silverlight except printing content outside the visual tree. I suppose, this is because of visualization in Silverlight. This topic (Printing content outside the visual tree) will be covered, maybe in the next post as I couldn’t find an easy solution to this. In case if you need that, you may check this post.

To print a document in Silverlight, you need the following reference.

System.Windows.Printing;

Simple Print (Print content from a collection of items) - Limited to single page with portrait view


Printing a collection is very simple. All you need is the print object to print and a little logic to develop the output you need to print.

Here is how you declare and initialize the PrintDocument object
private PrintDocument printDocument;

public void Print()
{
    if (this.printDocument == null)
    {
        this.printDocument = new PrintDocument();
        this.printDocument.PrintPage += this.OnPrintPage;
    }

    this.printDocument.Print("Print Header");
}

Print method will initialize printing, by allowing the user to select the print device. The actual printing takes place in the OnPrintPage method, which is invoked on PrintPage event. You need to set the PageVisual to print the document.
private void OnPrintPage(object sender, PrintPageEventArgs e)
{
        var width = e.PrintableArea.Width;
        var height = e.PrintableArea.Height;
        e.PageVisual = this.GetPrintCanvas(width, height);
}


Now, GetPrintCanvas method. This will return a canvas with all the desired property values, which is to be print is added to it. PrintableArea.Width and Height are based on the selected properties.
private Canvas GetPrintCanvas(double width, double height)
{
        var canvas = new Canvas { Width = width, Height = height };
        var verticalPanel = new StackPanel { Orientation = Orientation.Vertical, Margin = new Thickness(20) };
        var horizontalPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(2) };

        var textBlock = new TextBlock { Text = "Code", Width = 80, Margin = new Thickness(2), FontWeight = FontWeights.Bold };
        horizontalPanel.Children.Add(textBlock);

        textBlock = new TextBlock { Text = "Name", Width = 300, Margin = new Thickness(2), FontWeight = FontWeights.Bold };
        horizontalPanel.Children.Add(textBlock);

        // Adding Headers
        verticalPanel.Children.Add(horizontalPanel);

        foreach (var item in collection)
        {
            horizontalPanel = new StackPanel { Orientation = Orientation.Horizontal };
            textBlock = new TextBlock { Text = item.Code, Width = 80, Margin = new Thickness(2) };
            horizontalPanel.Children.Add(textBlock);
            textBlock = new TextBlock { Text = item.Name, Width = 300, Margin = new Thickness(2) };
            horizontalPanel.Children.Add(textBlock);

            // Adding Data as Rows
            verticalPanel.Children.Add(horizontalPanel);
        }

        canvas.Children.Add(verticalPanel);
        return canvas;
}

Done!!!!
You will get a decent output in a single page with portrait orientation!

Print collection in multiple pages. - Multiple page with portrait view

Here, In this example, I am going to print a collection which is much larger than the one in the first example. There will be multiple pages to be printed. We will use a similar example used in demonstrating simple print. The logic used in this example is different.

/// <summary>
/// Holds the object of print document
/// </summary>
private PrintDocument printDocument;

/// <summary>
/// Holds a pages for printing
/// </summary>
private List<UIElement> pages;

/// <summary>
/// Holds the current index for enumerable (while loop)
/// </summary>
private int index;

/// <summary>
/// Holds the current page index
/// </summary>
private int pageIndex;

The print method initializes all the objects declared.
public void Print()
{
        if (this.printDocument == null)
        {
                this.printDocument = new PrintDocument();
                this.printDocument.PrintPage += this.        if (this.pages == null)
                {
                    this.pages = new List<UIElement>();
                }

                this.pages.Clear();
                this.index = 0;
                this.pageIndex = 0;
                this.printDocument.Print("Print Document Header");  
        }
}

The print event will be called multiple times. Pages are added to a list and this pages with the canvas is created in a single GetPrintCanvas method call. The print event is as follows. Here you can see, GetPrintCanvas method is called when the index is set to 0, this is because all the pages with canvas is already created before the print begins.
private void OnPrintPage(object sender, PrintPageEventArgs e)
{
        var width = e.PrintableArea.Width;
        var height = e.PrintableArea.Height;

        if (this.pageIndex == 0)        
        {
                this.GetPrintCanvas(width, height);
        }

        e.PageVisual = this.pages[this.pageIndex];
        this.pageIndex += 1;
        e.HasMorePages = this.pageIndex < this.pages.Count;
}

But how do you create pages with canvas!!! Let's see!
private void GetPrintCanvas(double width, double height)
{
    var canvas = new Canvas { Width = width, Height = height };
        var verticalPanel = new StackPanel { Orientation = Orientation.Vertical, Margin = new Thickness(20) };
        var horizontalPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(2) };

        var textBlock = new TextBlock { Text = "Code", Width = 80, Margin = new Thickness(2), FontWeight = FontWeights.Bold };
        horizontalPanel.Children.Add(textBlock);

        textBlock = new TextBlock { Text = "Name", Width = 300, Margin = new Thickness(2), FontWeight = FontWeights.Bold };
        horizontalPanel.Children.Add(textBlock);

        // Adding Headers
        verticalPanel.Children.Add(horizontalPanel);
        
    while (this.index < this.collection.Count)
        {    
        var item = collection[this.index];
                if (item == null)
                {
                    break;
                }

                horizontalPanel = new StackPanel { Orientation = Orientation.Horizontal };
                textBlock = new TextBlock { Text = item.Code, Width = 80, Margin = new Thickness(2) };
                horizontalPanel.Children.Add(textBlock);
                textBlock = new TextBlock { Text = item.Name, Width = 300, Margin = new Thickness(2) };
                horizontalPanel.Children.Add(textBlock);

                // Adding Data as Rows
                verticalPanel.Children.Add(horizontalPanel);

                // Mesuare method will update the desired size
                verticalPanel.Measure(new Size(width, double.PositiveInfinity));
                if (verticalPanel.DesiredSize.Height > height)
                {
                    // Check for more pages
                    // Remove last panel
                    verticalPanel.Children.Remove(horizontalPanel);
                    canvas.Children.Add(verticalPanel);
                    this.pages.Add(canvas);
                    this.GetPrintCanvas(width, height);
                    return;
                }

                this.index += 1;
            }

            canvas.Children.Add(verticalPanel);
            this.pages.Add(canvas);
        }
}


The method used is a traditional method. Foreach loop can also be used instead. There can be a change in the implementation but the logic will remain the same.




Comments