Click here to Skip to main content
15,885,985 members
Articles / Programming Languages / C#

Simplified UI Printing in Silverlight

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
1 Dec 2010CPOL3 min read 23.3K   9   4
A simplified Extension Method which allows you to print any element with standard options such as Landscape printing, Shrink to Fit, Page Centering, and Print Margins.

Introduction

The ability to print in Silverlight 4 opens the door to creating better web-based applications than ever before. Silverlight now provides us with the opportunity to tie into a user's printer to create rich printouts of the very content they may be viewing.

While setting up a PrintDocument is a rather simple task, and there is much we can do with it, Silverlight leaves much to be desired in terms of how the item appears on the page.

This article aims to provide a very simplified extension method that will allow virtually any control to be printed in a clean and consistent manner.

Background

If you've been developing any kind of application in Silverlight, particularly ones which render large data sets, you've undoubtedly had the need to print the datasets. While there are a number of ways to do this - including pulling the user out of the application and into a separate browser window which renders an HTML table - none of the methods take full advantage of the Silverlight Printing APIs, while also providing a simple and clean document print out.

The difficult part with the printing APIs is that they too don't offer much help in rendering your Silverlight control on the print page - often bleeding off the edges, or otherwise appearing on odd locations on the page.

With the careful use of Transforms, and a simple extension method, a clean and consistent printing function can be achieved.

Using the code

While I've implemented this code by extending FrameworkElement, this method can be easily modified to be implemented in other locations. I prefer the extension method, as it allows for a very simple execution, such as:

C#
//ControlToPrint.Print(PrintDocName, HorizontalAlignment,
//   VerticalAlignment, PageMargin, LandscapePrinting,
//   ShrinkToFit, OnCompleteCallback);
MyControl.Print("My Print Document", HorizontalAlignment.Center, 
                VerticalAlignment.Top, new Thickness(100), true, true, null);

To print multiple items, you simply call the same function on a list of items, such as:

C#
//(List(<Grid>)).Print(PrintDocName, HorizontalAlignment,
//   VerticalAlignment, PageMargin, LandscapePrinting,
//   ShrinkToFit, OnCompleteCallback);
List<Grid> myListOfGrids = new List<Grid>();
...
myListOfGrids.Print("My Print Document", 
  HorizontalAlignment.Center, VerticalAlignment.Top, 
  new Thickness(100), true, true, null);

Grid g;
...
g.Children.Print"My Print Document", HorizontalAlignment.Center, 
   VerticalAlignment.Top, new Thickness(100), true, true, null);

So let's see how it all works.

First, we create our extension method's static class, which will house our extension method. Once this is done, any class that needs to make use of it will make a reference to the extension method namespace.

C#
public static class Extensions
{  }

Then, we want to provide three methods, two which overload the last. The first method is on a single element, the second being for a UIElementCollection, while the last is a for generic List of elements, and will ultimately be the method which does all the work.

C#
public static class Extensions
{
      public static void Print(this FrameworkElement element, string Document, 
             HorizontalAlignment HorizontalAlignment, 
             VerticalAlignment VerticalAlignment, Thickness PageMargin, 
             bool PrintLandscape, bool ShrinkToFit, Action OnPrintComplete)
      {
          Print(new List<FrameworkElement>() { element }, Document, 
                HorizontalAlignment, VerticalAlignment, PageMargin, 
                PrintLandscape, ShrinkToFit, OnPrintComplete);
      }

      public static void Print(this UIElementCollection elements, 
             string Document, HorizontalAlignment HorizontalAlignment, 
             VerticalAlignment VerticalAlignment, Thickness PageMargin, 
             bool PrintLandscape, bool ShrinkToFit, Action OnPrintComplete)
      {
          Print(elements.ToList(), Document, HorizontalAlignment, VerticalAlignment, 
                PageMargin, PrintLandscape, ShrinkToFit, OnPrintComplete);
      }

      public static void Print<T>(this List<T> elements, string Document, 
             HorizontalAlignment HorizontalAlignment, 
             VerticalAlignment VerticalAlignment, Thickness PageMargin, 
             bool PrintLandscape, bool ShrinkToFit, Action OnPrintComplete)
      {
         ...
      }
}

If an object does not derive from FrameworkElement, an exception should be thrown. We also need to make sure that each item that is being printed has actually been rendered on the control. The reason for this is that we require use of the ActualWidth and ActualHeight of an element - this means the element must appear and have a parent object.

It's possible to get around this restriction by rendering your controls to a parent control which has its Opacity set to zero. Then clear the parent control when printing is complete.

C#
if (!typeof(FrameworkElement).IsAssignableFrom(elements[currentItemIndex].GetType()))
{
     throw new Exception("Element must be an object inheriting from FrameworkElement");
}

FrameworkElement element = elements[currentItemIndex] as FrameworkElement;

if (element.Parent == null || element.ActualWidth == double.NaN || 
    element.ActualHeight == double.NaN)
{
     throw new Exception("Element must be rendered, " + 
                         "and must have a parent in order to print.");
}

When printing begins, the PrintPage event is called. This event is what we tie into to render our pages. When this occurs, we want to apply a TranslateTransform, and then our RotateTransform (if we're printing in Landscape mode), and then ScaleTransform. Lastly, we'll apply the TranslateTransform to position the element on the page, as specified by the parameters.

It is important to note that we want to do this in this particular order so we know the point around which we're applying all our transforms.

It is in this event method that we iterate over our list of FrameworkElements, applying the transforms individually, then printing the page. We set HasMorePages to true each time, until we reach the end of our list.

Lastly, if we are printing in Landscape mode, it is important to note that we want to compare our element's width to the page's height, and the element's height to the page's width.

C#
if (!typeof(FrameworkElement).IsAssignableFrom(elements[currentItemIndex].GetType()))
{
    throw new Exception("Element must be an object inheriting from FrameworkElement");
}

FrameworkElement element = elements[currentItemIndex] as FrameworkElement;

if (element.Parent == null || element.ActualWidth == double.NaN || 
                              element.ActualHeight == double.NaN)
{
    throw new Exception("Element must be rendered, " + 
                        "and must have a parent in order to print.");
}
                
TransformGroup transformGroup = new TransformGroup();

//First move to middle of page...
transformGroup.Children.Add(new TranslateTransform() { 
  X = (evt.PrintableArea.Width - element.ActualWidth) / 2, 
  Y = (evt.PrintableArea.Height - element.ActualHeight) / 2 });
double scale = 1;
if (PrintLandscape)
{
    //Then, rotate around the center
    transformGroup.Children.Add(new RotateTransform() { Angle = 90, 
       CenterX = evt.PrintableArea.Width / 2, 
       CenterY = evt.PrintableArea.Height / 2 });

    if (ShrinkToFit)
    {
        if ((element.ActualWidth + PageMargin.Left + PageMargin.Right) > 
                                   evt.PrintableArea.Height)
        {
            scale = Math.Round(evt.PrintableArea.Height / 
              (element.ActualWidth + PageMargin.Left + PageMargin.Right), 2);
        }
        if ((element.ActualHeight + PageMargin.Top + PageMargin.Bottom) > 
                                    evt.PrintableArea.Width)
        {
            double scale2 = Math.Round(evt.PrintableArea.Width / 
              (element.ActualHeight + PageMargin.Top + PageMargin.Bottom), 2);
            scale = (scale2 < scale) ? scale2 : scale;
        }
    }
}
else if (ShrinkToFit)
{
    //Scale down to fit the page + margin
                    
    if ((element.ActualWidth + PageMargin.Left + PageMargin.Right) > 
                               evt.PrintableArea.Width)
    {
        scale = Math.Round(evt.PrintableArea.Width / 
                          (element.ActualWidth + PageMargin.Left + PageMargin.Right), 2);
    }
    if ((element.ActualHeight + PageMargin.Top + PageMargin.Bottom) > 
                                evt.PrintableArea.Height)
    {
        double scale2 = Math.Round(evt.PrintableArea.Height / 
          (element.ActualHeight + PageMargin.Top + PageMargin.Bottom), 2);
        scale = (scale2 < scale) ? scale2 : scale;
    }
}

//Scale down to fit the page + margin
if (scale != 1)
{
    transformGroup.Children.Add(new ScaleTransform() { ScaleX = scale, 
         ScaleY = scale, CenterX = evt.PrintableArea.Width / 2, 
         CenterY = evt.PrintableArea.Height / 2 });
}

if (VerticalAlignment == VerticalAlignment.Top)
{
    //Now move to Top
    if (PrintLandscape)
    {
        transformGroup.Children.Add(new TranslateTransform() { X = 0, 
          Y = PageMargin.Top - (evt.PrintableArea.Height - 
                               (element.ActualWidth * scale)) / 2 });
    }
    else
    {
        transformGroup.Children.Add(new TranslateTransform() { X = 0, 
          Y = PageMargin.Top - (evt.PrintableArea.Height - 
                               (element.ActualHeight * scale)) / 2 });
    }
}
else if (VerticalAlignment == VerticalAlignment.Bottom)
{
    //Now move to Bottom
    if (PrintLandscape)
    {
        transformGroup.Children.Add(new TranslateTransform() { X = 0, 
          Y = ((evt.PrintableArea.Height - 
               (element.ActualWidth * scale)) / 2) - PageMargin.Bottom });
    }
    else
    {
        transformGroup.Children.Add(new TranslateTransform() { X = 0, 
          Y = ((evt.PrintableArea.Height - 
               (element.ActualHeight * scale)) / 2) - PageMargin.Bottom });
    }
}

if (HorizontalAlignment == HorizontalAlignment.Left)
{
    //Now move to Left
    if (PrintLandscape)
    {
        transformGroup.Children.Add(new TranslateTransform() { 
          X = PageMargin.Left - (evt.PrintableArea.Width - 
          (element.ActualHeight * scale)) / 2, Y = 0 });
    }
    else
    {
        transformGroup.Children.Add(new TranslateTransform() { 
          X = PageMargin.Left - (evt.PrintableArea.Width - 
                                (element.ActualWidth * scale)) / 2, Y = 0 });
    }
}
else if (HorizontalAlignment == HorizontalAlignment.Right)
{
    //Now move to Right
    if (PrintLandscape)
    {
        transformGroup.Children.Add(new TranslateTransform() { 
          X = ((evt.PrintableArea.Width - 
               (element.ActualHeight * scale)) / 2) - PageMargin.Right, Y = 0 });
    }
    else
    {
        transformGroup.Children.Add(new TranslateTransform() { 
          X = ((evt.PrintableArea.Width - 
               (element.ActualWidth * scale)) / 2) - PageMargin.Right, Y = 0 });
    }
}

evt.PageVisual = element;
evt.PageVisual.RenderTransform = transformGroup;
                
//Increment to next item,
currentItemIndex++;
                
//If the currentItemIndex is less than the number of elements, keep printing
evt.HasMorePages = currentItemIndex < elements.Count;

Finally, when we've finished printing, we'll want to undo all the transforms we did. For this, we tie into the EndPrint method and iterate over our list of elements, resetting all transforms.

We end by calling the OnPrintComplete Action, if it is set.

C#
foreach (var item in elements)
{
     FrameworkElement element = item as FrameworkElement;
     //Reset everything...
     TransformGroup transformGroup = new TransformGroup();
     transformGroup.Children.Add(new ScaleTransform() { ScaleX = 1, ScaleY = 1 });
     transformGroup.Children.Add(new RotateTransform() { Angle = 0 });
     transformGroup.Children.Add(new TranslateTransform() { X = 0, Y = 0 });
     element.RenderTransform = transformGroup;
}

//Callback to complete
if (OnPrintComplete != null)
{
     OnPrintComplete();
}

Put together, our final product looks like:

C#
public static class Extensions
{
    public static void Print(this FrameworkElement element, 
           string Document, HorizontalAlignment HorizontalAlignment, 
           VerticalAlignment VerticalAlignment, Thickness PageMargin, 
           bool PrintLandscape, bool ShrinkToFit, Action OnPrintComplete)
    {
        Print(new List<FrameworkElement>() { element }, Document, 
              HorizontalAlignment, VerticalAlignment, PageMargin, 
              PrintLandscape, ShrinkToFit, OnPrintComplete);
    }

    public static void Print(this UIElementCollection elements, string Document, 
           HorizontalAlignment HorizontalAlignment, 
           VerticalAlignment VerticalAlignment, Thickness PageMargin, 
           bool PrintLandscape, bool ShrinkToFit, Action OnPrintComplete)
    {
        Print(elements.ToList(), Document, HorizontalAlignment, 
              VerticalAlignment, PageMargin, PrintLandscape, 
              ShrinkToFit, OnPrintComplete);
    }

    public static void Print<T>(this List<T> elements, 
           string Document, HorizontalAlignment HorizontalAlignment, 
           VerticalAlignment VerticalAlignment, Thickness PageMargin, 
           bool PrintLandscape, bool ShrinkToFit, Action OnPrintComplete)
    {
        PrintDocument printDocument = new PrintDocument();
        PageMargin = PageMargin == null ? new Thickness(10) : PageMargin;
        Document = (string.IsNullOrEmpty(Document)) ? "Print Document" : Document;
        int currentItemIndex = 0;
        printDocument.PrintPage += delegate(object sender, PrintPageEventArgs evt)
        {
            if (!typeof(FrameworkElement).IsAssignableFrom(
                         elements[currentItemIndex].GetType()))
            {
                throw new Exception("Element must be an " + 
                      "object inheriting from FrameworkElement");
            }

            FrameworkElement element = elements[currentItemIndex] as FrameworkElement;

            if (element.Parent == null || element.ActualWidth == double.NaN || 
                element.ActualHeight == double.NaN)
            {
                throw new Exception("Element must be rendered, " + 
                          "and must have a parent in order to print.");
            }
                
            TransformGroup transformGroup = new TransformGroup();

            //First move to middle of page...
            transformGroup.Children.Add(new TranslateTransform() { 
              X = (evt.PrintableArea.Width - element.ActualWidth) / 2, 
              Y = (evt.PrintableArea.Height - element.ActualHeight) / 2 });
            double scale = 1;
            if (PrintLandscape)
            {
                //Then, rotate around the center
                transformGroup.Children.Add(new RotateTransform() { Angle = 90, 
                   CenterX = evt.PrintableArea.Width / 2, 
                   CenterY = evt.PrintableArea.Height / 2 });

                if (ShrinkToFit)
                {
                    if ((element.ActualWidth + PageMargin.Left + 
                          PageMargin.Right) > evt.PrintableArea.Height)
                    {
                        scale = Math.Round(evt.PrintableArea.Height / 
                          (element.ActualWidth + PageMargin.Left + PageMargin.Right), 2);
                    }
                    if ((element.ActualHeight + PageMargin.Top + PageMargin.Bottom) > 
                                                evt.PrintableArea.Width)
                    {
                        double scale2 = Math.Round(evt.PrintableArea.Width / 
                          (element.ActualHeight + PageMargin.Top + PageMargin.Bottom), 2);
                        scale = (scale2 < scale) ? scale2 : scale;
                    }
                }
            }
            else if (ShrinkToFit)
            {
                //Scale down to fit the page + margin
                    
                if ((element.ActualWidth + PageMargin.Left + 
                        PageMargin.Right) > evt.PrintableArea.Width)
                {
                    scale = Math.Round(evt.PrintableArea.Width / 
                      (element.ActualWidth + PageMargin.Left + PageMargin.Right), 2);
                }
                if ((element.ActualHeight + PageMargin.Top + PageMargin.Bottom) > 
                             evt.PrintableArea.Height)
                {
                    double scale2 = Math.Round(evt.PrintableArea.Height / 
                      (element.ActualHeight + PageMargin.Top + PageMargin.Bottom), 2);
                    scale = (scale2 < scale) ? scale2 : scale;
                }
            }

            //Scale down to fit the page + margin
            if (scale != 1)
            {
                transformGroup.Children.Add(new ScaleTransform() { ScaleX = scale, 
                   ScaleY = scale, CenterX = evt.PrintableArea.Width / 2, 
                   CenterY = evt.PrintableArea.Height / 2 });
            }

            if (VerticalAlignment == VerticalAlignment.Top)
            {
                //Now move to Top
                if (PrintLandscape)
                {
                    transformGroup.Children.Add(new TranslateTransform() { 
                      X = 0, Y = PageMargin.Top - (evt.PrintableArea.Height - 
                      (element.ActualWidth * scale)) / 2 });
                }
                else
                {
                    transformGroup.Children.Add(new TranslateTransform() { X = 0, 
                      Y = PageMargin.Top - (evt.PrintableArea.Height - 
                      (element.ActualHeight * scale)) / 2 });
                }
            }
            else if (VerticalAlignment == VerticalAlignment.Bottom)
            {
                //Now move to Bottom
                if (PrintLandscape)
                {
                    transformGroup.Children.Add(new TranslateTransform() { X = 0, 
                      Y = ((evt.PrintableArea.Height - 
                      (element.ActualWidth * scale)) / 2) - PageMargin.Bottom });
                }
                else
                {
                    transformGroup.Children.Add(new TranslateTransform() { X = 0, 
                      Y = ((evt.PrintableArea.Height - 
                      (element.ActualHeight * scale)) / 2) - PageMargin.Bottom });
                }
            }

            if (HorizontalAlignment == HorizontalAlignment.Left)
            {
                //Now move to Left
                if (PrintLandscape)
                {
                    transformGroup.Children.Add(new TranslateTransform() { 
                      X = PageMargin.Left - (evt.PrintableArea.Width - 
                      (element.ActualHeight * scale)) / 2, Y = 0 });
                }
                else
                {
                    transformGroup.Children.Add(new TranslateTransform() { 
                      X = PageMargin.Left - (evt.PrintableArea.Width - 
                      (element.ActualWidth * scale)) / 2, Y = 0 });
                }
            }
            else if (HorizontalAlignment == HorizontalAlignment.Right)
            {
                //Now move to Right
                if (PrintLandscape)
                {
                    transformGroup.Children.Add(new TranslateTransform() { 
                      X = ((evt.PrintableArea.Width - 
                      (element.ActualHeight * scale)) / 2) - PageMargin.Right, Y = 0 });
                }
                else
                {
                    transformGroup.Children.Add(new TranslateTransform() { 
                      X = ((evt.PrintableArea.Width - 
                      (element.ActualWidth * scale)) / 2) - PageMargin.Right, Y = 0 });
                }
            }

            evt.PageVisual = element;
            evt.PageVisual.RenderTransform = transformGroup;
                
            //Increment to next item,
            currentItemIndex++;
                
            //If the currentItemIndex is less than the number of elements, keep printing
            evt.HasMorePages = currentItemIndex < elements.Count;
        };

        printDocument.EndPrint += delegate(object sender, EndPrintEventArgs evt)
        {
            foreach (var item in elements)
            {
                FrameworkElement element = item as FrameworkElement;
                //Reset everything...
                TransformGroup transformGroup = new TransformGroup();
                transformGroup.Children.Add(
                  new ScaleTransform() { ScaleX = 1, ScaleY = 1 });
                transformGroup.Children.Add(new RotateTransform() { Angle = 0 });
                transformGroup.Children.Add(
                  new TranslateTransform() { X = 0, Y = 0 });
                element.RenderTransform = transformGroup;
            }

            //Callback to complete
            if (OnPrintComplete != null)
            {
                OnPrintComplete();
            }
        };

        printDocument.Print(Document);
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionMine works but... Pin
childg7-Jul-11 10:31
childg7-Jul-11 10:31 
GeneralWould be great if it worked Pin
MasterRanger27-May-11 9:40
MasterRanger27-May-11 9:40 
QuestionImplementing this code give error on elements.ToList() Pin
ProSys_Lester22-Mar-11 6:44
ProSys_Lester22-Mar-11 6:44 
AnswerRe: Implementing this code give error on elements.ToList() Pin
Mike Beaton1-Aug-11 4:37
Mike Beaton1-Aug-11 4:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.