Skip Navigation Links

Silverlight Printing

It is possible to print from Silverlight Applications, kind of.... It just takes a few pieces of the puzzle all coming together to make it happen. I don't profess that this is the only way, but it is one that worked for me. The pieces of the puzzle are:

  1. A Silverlight UIElement
  2. WritableBitmap
  3. iTextSharp
  4. A Web Service
  5. An ASPX page to display the PDF created by iTextSharp.

My requirements were to create a form in a silverlight application, and then print that form without all the header and footer you get when you print from an HYML page. My forms needed to be clean in the sence that I didn't want anything on the page that I didn't put there. The second requirement is that I needed to print exactly what I was displaying on the screen WYSIWYG. This led me down the path of capturing an image from the control I was populating, then grabbing an image of that control to store in a database or in my case print out.

Another thing I did was to store the collected image into a session variable so that my PDF page would have access to it. For this, I had to declare the WebMethod as that of Session Enabled. With the EnableSession = true, I can store the stream into a memory stream to save into a database or pull right out of memory to display.

The first piece of the puzzle and a decent place to start is a Web Service. I knew that I needed to get the image into some sort of either storage or memory, so I decided to put it into memory using a web service. This way I could get to it later. You could also put this into a database and pull it later, but session memory was good enough for me. The only way I know to get something to print cleanly from a web page is to stick it into a PDF. If if you just want to return an image, you can probably omit this step, but you can use the other pieces to return the image or store it into a database.


Here is the guts of the web service. When you create a web service, the other code is generated automatically.
//Note, I pulled this from one of my Web Services that does a lot more than just this example, so there may be uses not needed, but I didn't want to leave any out.
using System;
using System.Collections;
using System.ComponentModel;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Text;
using System.Configuration;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Threading;

namespace Jimanize
{  
   
[WebService(Namespace = "http://www.Jimanize.com/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ToolboxItem(false)]

public class JimanizeWebService : System.Web.Services.WebService
{
    [WebMethod(EnableSession = true)]
    public bool StoreTemporaryImageInMemory(int Width, int Height, int[] ImageArray)
    {
        try
        {
            //First take the integer array received from the WritableBitmap and generate an image memory stream from it.
            MemoryStream msRet = GetPNGStream(Width, Height, ImageArray, false);

            //Add the stream to the session variable
            Session.Contents.Add("TemporaryStoredImage", msRet);

            //In my case I wanted to check that all went well, so I returned a bool for success
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }


    private static MemoryStream GetPNGStream(int width, int height, int[] ImageArray, bool bCompress)
    {
        //First we will create an empty bitmap to hold the image we are about to write to it.
        Bitmap bmp = new Bitmap(width, height);

        //Now loop through our integer array and paint each pixel to the new bitmap
        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                int iColor = ImageArray[(j * width) + i];
                Color c = Color.FromArgb((byte)((iColor >> 0x18) & 0xff),
                        (byte)((iColor >> 0x10) & 0xff),
                        (byte)((iColor >> 8) & 0xff),
                        (byte)(iColor & 0xff));
                bmp.SetPixel(i, j, c);
            }
        }
       
        //Now create a memory stream to hold our final stream that we will hold im memory
        //You could also pass this stream into a database for later use
        MemoryStream msRet = new MemoryStream();

        //Depending on if I wanted to print this or just display it on the screen, I added a boolean to variable to compress the image
        if (bCompress == true)\\ I added a variable to compress the image for displaying on the screen or not when printing
            bmpImage.SetResolution(96, 96);

        // I used a Png format. You can also use jpeg if you choose
        bmp.Save(msRet, System.Drawing.Imaging.ImageFormat.Png);
        msRet.Position = 0;
        return msRet;
   }
}
My next task was to pull it from memory and stuff it into a PDF. Below are the aspx and aspx.cs pages that are pretty straight forward.

Here is the aspx page code.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="PDFImage.aspx.cs" Inherits="DPMSWeb2008_v2.Reports.PDFImage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
  <div>
   
  </div>
</form>
</body>
</html> 

Here is the aspx.cs page code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using DPMSWeb2008_V2;
using System.Drawing;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace DPMSWeb2008_v2.Reports
{
    public partial class PDFImage : System.Web.UI.Page
   {
       private Utilities util;
       string sReportRequested;

       protected void Page_Load(object sender, EventArgs e)
      {
          CreateReport();
      }

      private void CreateReport()
     {
        //Clear the response and set the new response to a PDF 
        Response.Clear();
        Response.ContentType = "application/pdf";

        //Create a new bitmap image from the session temporary variable
        //You could also pull from a database if needed
        Bitmap bmp = new Bitmap((MemoryStream)Session.Contents["TemporaryStoredImage"]);

        //Create a new memory stream to hold the
        MemoryStream ms = new MemoryStream();

        //Save the image to a stream
        bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png);

        //Create a byte array of the information so that the iTextSharp routine can consume the image
        byte[] bOut = ms.GetBuffer();

        //Create the PDF stream that we will return
        MemoryStream m = new MemoryStream();

        //Create a new iTextSharp PDF document
        Document document = new Document(iTextSharp.text.PageSize.LETTER, 25, 25, 25, 25);

        //Create a PDF Writer
        PdfWriter writer = PdfWriter.GetInstance(document, m);

        //Open the document
        document.Open();

        //Create the image that we can insert into the PDF
        iTextSharp.text.Image img = iTextSharp.text.Image.GetInstance(bImg);

        //Add the image to the document
        document.Add(img);

        //Close the Document
        document.Close();

        //Flushing the writer makes sure all data written to the document is cleared from the buffer and written to the memory stream
        writer.Flush();

        //Put the PDF memory stream into the response to display in a web browser.
        Response.OutputStream.Write(m.GetBuffer(), 0, m.GetBuffer().Length);
        Response.OutputStream.Flush();
        Response.OutputStream.Close();
        Response.End();

        ////To just export as an image on the page, you can use this
        //Response.Clear();
        //Response.ContentType = "image/jpeg";

        //Bitmap bmp = new Bitmap((MemoryStream)Session.Contents["TemporaryStoredImage"]);
        //MemoryStream ms = new MemoryStream();
        //bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
        //Response.OutputStream.Write(ms.GetBuffer(), 0, ms.GetBuffer().Length);
        //Response.OutputStream.Flush();
        //Response.OutputStream.Close();
        //Response.End();
    }
   }
}

The final step (which is actually the first step when running this) is to capture an image of a UIElement and pass to the web service.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Browser;
using System.Windows.Media.Imaging;

namespace Jimanize
{
    public partial class PrintPreview : ChildWindow
    {
        private JimanizeWebService.JimanizeDataServiceSoapClient srv = null;
        public PrintPreview()
        {
&nbs            InitializeComponent();
   
            //Consume the Web Service
            this.srv = GetCurrentDataService();
            srv.StoreTemporaryImageInMemoryCompleted +=
                new EventHandler<Jimanize.JimanizeWebService.StoreTemporaryImageInMemoryCompletedEventArgs>(srv_StoreTemporaryImageInMemoryCompleted);
        }

        public JimanizeWebService.JimanizeDataServiceSoapClient GetCurrentDataService()
        {
            BasicHttpBinding bind = new BasicHttpBinding();
            bind.MaxBufferSize = 50000000;
            bind.MaxReceivedMessageSize = 50000000;
            bind.SendTimeout = new TimeSpan(0, 1, 0);
            bind.ReceiveTimeout = new TimeSpan(0, 1, 0);
            string uri = System.Windows.Browser.HtmlPage.Document.DocumentUri.ToString();

            //The location of the Service in reference to the Silverlight Application
            string webserviceendpoint = uri.Substring(0, uri.LastIndexOf('/')) + "/Services/JimanizeWebService.asmx";
            EndpointAddress endpoint = new EndpointAddress(webserviceendpoint);
            return new Jimanize.JimanizeWebService.JimanizeDataServiceSoapClient(bind, endpoint);
        }

        private void btnPrint_Click(object sender, RoutedEventArgs e)
        {
            //Get a reference to my UIElement. I have populated this stack panel in other methods that are shown to the user
            StackPanel spContent = (StackPanel)svMainContent.Content;

            //Create the WritableBitmap from the UIElement you want to print
            WriteableBitmap wb = new WriteableBitmap(System.Convert.ToInt32(spContent.ActualWidth), System.Convert.ToInt32(spContent.ActualHeight));
            wb.Render(spContent, new TranslateTransform());
            wb.Invalidate();

            //Create an Integer Array to pass into the Web Service
            int[] iArray = new int[wb.Pixels.Length];

            //Load the Integer Array with the pixels from the WritableBitmap
            for (int i = 0; i < wb.Pixels.Length; i++)
                iArray[i] = wb.Pixels[i];

            //Finally Call the Web Service passing in the information for the image
            //When the Web Service returns, we will then call the page to display the image passed into memory in a PDF
            srv.StoreTemporaryImageInMemoryAsync(wb.PixelWidth, wb.PixelHeight, iArray);
        }

        void srv_StoreTemporaryImageInMemoryCompleted(object sender, Jimanize.JimanizeWebService.StoreTemporaryImageInMemoryCompletedEventArgs e)
        {
            //Get the URL of the current Silverlight Application
            string sUrl = Application.Current.Host.Source.AbsoluteUri;

            //Find the position of the ClientBin (this is where the XAP lives)
            int iPos = sUrl.ToUpper().IndexOf("/CLIENTBIN/");
            sUrl = sUrl.Substring(0, iPos);

            //Set the url to the PDF page created above (your link location may be different)
            sUrl += "/PDFImage.aspx";
       
            //Tell Silverlight to open the new page in a blank window
            HtmlPage.Window.Navigate(new Uri(sUrl), "_blank");
        }
    }
}    

Some of this information, like how to write the pixels of a writable bitmap, came from my searching the web so that I could print from Silverlight. Hopefully this tutorial will save you some time if you need to accomplish the same. I pulled this example from a running application and then tried to clean it up a bit to remove any extraneous code that I was using for other things. If you find I omitted something, introduced any errors, or there is a better way, let me know and I will fix this sample.