Print Screen in Silverlight

One of the simple tasks I needed to work on last week was to implement simple screen printing on certain screens in a Silverlight application.  Once I got home, I wrote a very generic routine that could print any control at all.  I wrote it as an extension method on System.Windows.Controls.Control class.  I used a few other useful classes in my implementation.

WritableBitMap allows us to convert any XAML layout into a bitmap.  This is very useful for my goal.  I can convert a control into a bitmap, then pass it into PrintDocument.  However, this does not work really well if the size of Silverlight application is larger than standard 8.5 x 11 page.  If your control is larger, it will fall off the page.  Also, you need to take into account page margins.  So my goals adjusted for printing restrictions are:

  • Resize the control to fit onto 11 x 8.5 page with 1/2 inch margins around it. 
  • Support landscape only because my app is larger horizontally than vertically, fitting nicely onto standard monitors.
  • Scale the content to fit onto the page instead of cutting off excess content.

One more important point is that Silverlight is displayed in standard 96 DPI on any resolution.  I have to account for that when computing scales.

Here is what I ended up with.  I tried to document my code to explain what I am doing in-line.  I am using Size structure for my page sizes – standard page and printable area.  I use the difference to compute margins.  You also have to be careful how long it takes before you call PrintDocument.Print.  If you take too long (more than a few seconds) you will get an error that dialogs must be initiated by user events.  You could also inject additional data onto your control before you print.  You just have to be careful to remove additional elements after your print.

 

using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Printing;

// ReSharper disable CheckNamespace - we are putting it into the same namespace as control to avoid using statements everywhere.
namespace System.Windows.Controls
// ReSharper restore CheckNamespace
{
    public static class ControlExtensions
    {
        private static readonly Size StandardLandscapePageSize = new Size(11, 8.5);
        private static readonly Size StandardLandscapePrintableAreaSize = new Size(10, 7.5);
        private const double DotsPerInch = 96;

        public static void PrintScreen(this Control control)
        {
            try
            {
                double scaleX = 1;
                double scaleY = 1;
                double pixelWidth = DotsPerInch * StandardLandscapePrintableAreaSize.Width;
                double pixelHeight = DotsPerInch * StandardLandscapePrintableAreaSize.Height;
                // determine scale ratio to fit on a page
                if (control.ActualWidth > pixelWidth)
                    scaleX = pixelWidth / control.ActualWidth;

                if (control.ActualHeight > pixelHeight)
                    scaleY = pixelHeight / control.ActualHeight;
                
                // create scale transform to use the scale above to automatically resize the
                // control to be printed.
                var transform = new ScaleTransform
                {
                    CenterX = 0,
                    CenterY = 0,
                    ScaleX = scaleX,
                    ScaleY = scaleY
                };
                //create bit map for control, scaled appropriately
                var writableBitMap = new WriteableBitmap(control, transform);
                // put bit map on canvas
                var canvas = new Canvas
                {
                    Width = pixelWidth,
                    Height = pixelHeight,
                    Background = new ImageBrush { ImageSource = writableBitMap, Stretch = Stretch.Fill }
                };
                // create outer canvas to setup printable area margins
                var outerCanvas = new Canvas
                {
                    Width = StandardLandscapePageSize.Width * DotsPerInch,
                    Height = StandardLandscapePageSize.Height * DotsPerInch
                };
                outerCanvas.Children.Add(canvas);
                //setup margins
                canvas.SetValue(Canvas.LeftProperty, DotsPerInch * (StandardLandscapePageSize.Width - StandardLandscapePrintableAreaSize.Width) / 2);
                canvas.SetValue(Canvas.TopProperty, DotsPerInch * (StandardLandscapePageSize.Height - StandardLandscapePrintableAreaSize.Height) / 2);
                //fore refresh just in case
                canvas.InvalidateMeasure();
                canvas.UpdateLayout();
                // create printable document
                var printDocument = new PrintDocument();
                printDocument.PrintPage += (s, args) =>
                {
                    args.PageVisual = outerCanvas;
                    args.HasMorePages = false;
                };
                // launch print with the tile of Print Screen
                printDocument.Print("Print Screen");
            }
            catch (Exception exception)
            {
                // replace with real error handling
                MessageBox.Show("Error occurred while printing. Error message is " + exception.Message);
            }

        }

    }
}

 

You can download sample app here.

Enjoy.

PS.  I know, nobody is using Silverlight any more : – ).  This of course is not true in the least, there are still plenty of companies that have and support Silverlight apps, even if they do not create new ones.

Thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *