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:
- A Silverlight UIElement
- WritableBitmap
- iTextSharp
- A Web Service
- 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.