Una de las características más esperadas y solicitadas por la comunidad de Silverlight es el soporte de impresión dentro de las aplicaciones, esto es, la capacidad de poder mandar a imprimir el contenido que estemos viendo en la aplicación ya sea una lista de datos de un DataGrid o ListBox o simplemente tomarle una “foto” a la pantalla y enviarla a papel o alguna de las impresoras virtuales instaladas en nuestros equipos (por ejemplo: OneNote, XPS, PDF, etc.).  Silverlight 4 Beta incluye esta característica a través de un modelo de clases muy sencillo.  De hecho este modelo me recuerda al modelo de impresión que tenemos en las aplicaciones Windows Forms desde hace ya mucho tiempo.

Clase System.Windows.Printing.PrintDocument

Las clases para el soporte de impresión dentro de Silverlight han sido incorporadas al espacio de nombres System.Windows.Printing, el cual contiene entre otras cosas la clase PrintDocument.  Esta clase PrintDocument es la clase responsable de mandar a imprimir el contenido que deseemos.

La clase PrintDocument no expone alguna propiedad para que podamos determinar el contenido que deseemos imprimir.  No obstante, el método Print() al ser ejecutado abre la caja de diálogo de impresión de nuestro sistema operativo.

1_thumb

Una vez seleccionada la impresora destino (física o virtual), disparará la siguiente secuencia de eventos:

  1. StartPrint
  2. PrintPage
  3. EndPrint
Evento StartPrint

El evento StartPrint se dispara una vez cerrada la caja de diálogo de selección de impresora.  Inmediatamente después se dispara el siguiente evento: PrintPage.

Evento PrintPage

Seguido del evento StartPrint se disparará el evento PrintPage.  Este es el evento más importante en el modelo de impresión ya que es en los argumentos donde debemos establecer efectivamente el contenido que deseamos imprimir.  Esto es definido en la propiedad PageVisual que es de tipo UIElement.  Ya que es de tipo UIElement podemos establecer como contenido en realidad cualquier cosa: ya sea que esté presente en el árbol de Xaml o que sea contenido creado de manera dinámica (a través de código o usando XamlReader.Load(), etc.).  Por ejemplo, si deseamos tomar una “fotografía” a la aplicación actual e imprimirla, podemos establecer como PageVisual el objeto contenedor raíz (ej. LayoutRoot).  O de lo contrario, si queremos crear una impresión personalizada podemos generar contenedores y elementos de manera dinámica tal y como lo muestra este artículo.

Además de la propiedad PageVisual contamos con un par de propiedades: HasMorePages y PrintableArea.

HasMorePages es una propiedad que indica si todavía hay más páginas que imprimir, por ejemplo en el caso de documentos largos, listas, catálogos etc.  Si establecemos esta propiedad a true, entonces el evento PrintPage volverá a dispararse.

PrintableArea indica el tamaño que tiene el área de impresión según la impresora seleccionada.  Por ejemplo no es el mismo tamaño si mandamos a imprimir a OneNote que a un PDF o a una impresora física.  Esta propiedad es de tipo Size y nos servirá también para calcular márgenes, posiciones de elementos, etc.

Cabe mencionar que, si la impresora seleccionada requiere todavía de la interacción del usuario, esta continuará (para indicar el nombre de un archivo .xps o .pdf por ejemplo).

Evento EndPrint

El evento EndPrint se disparará una vez terminado el proceso de impresión.

Ejemplo

Comencemos con crear un nuevo proyecto de Silverlight 4 a través de Visual Studio 2010 (Beta 2 es la versión más reciente en el momento de escribir este artículo).  A este proyecto le pondremos de nombre Demo.SL4.Impresion.  Visual Studio creará la solución junto con el proyecto adecuado tal y como lo esperamos.

Agregaremos a nuestro MainPage.xaml el siguiente código:



        

Los botones nos servirán para mandar a imprimir por medio de la ejecución del método Print() de la clase PrintDocument y el TextBlock nos servirá para identificar el estatus de la impresión.

En el clic del primer botón crearemos una nueva instancia de PrintDocument y estableceremos su propiedad DocumentName de manera adecuada.  El valor de esta propiedad será lo que aparezca en la ventana de estado de la impresora.

PrintDocument pd = new PrintDocument() { DocumentName = "Ejemplo de Impresión" };

Ahora bien, manejaremos el evento PrintPage.  En este caso usaremos una expresión lambda pero pudiera ser también un método:

pd.PrintPage += (s, a) =>;
{
//canvas es una variable a nivel de clase
//Establecemos el ancho y alto de canvas a usando la propiedad PrintableArea
canvas = new Canvas() { Width = a.PrintableArea.Width, Height = a.PrintableArea.Height };

//Agregamos el título deseado para la página
CrearTitulo("Esto es una Prueba de Impresión");

//Establecemos el contenido en la propiedad PageVisual
a.PageVisual = canvas;
};

En el código anterior suceden varias cosas:

Creamos un objeto de tipo Canvas, el cual nos servirá como lienzo para dibujar en él los elementos visuales que queramos imprimir (en este ejemplo el contenido será un TextBlock creado en el método CrearTitulo()).  El ancho y alto del objeto Canvas los establecemos a partir de la propiedad PrintableArea, la cual, nos indica el tamaño de impresión según la impresora seleccionada (por ejemplo, el área de impresión es diferente en OneNote que un .xps).

Finalmente, establecemos este contenido que creamos dinámicamente en la propiedad PageVisual.  Esta propiedad indica el contenido visual que vamos a imprimir.  El siguiente fragmento de código contiene todo el código completo de este primer ejemplo:

Canvas canvas = null;
int pagina = 0;

private void button1_Click(object sender, RoutedEventArgs e)
{
PrintDocument pd = new PrintDocument() { DocumentName = "Ejemplo de Impresión" };
pd.PrintPage += (s, a) =>
{
//canvas es una variable a nivel de clase
//Establecemos el ancho y alto de canvas a usando la propiedad PrintableArea
canvas = new Canvas() { Width = a.PrintableArea.Width, Height = a.PrintableArea.Height };

//Agregamos el título deseado para la página
CrearTitulo("Esto es una Prueba de Impresión");

//Establecemos el contenido en la propiedad PageVisual
a.PageVisual = canvas;
};
pd.StartPrint += (s, a) => textBlock1.Text = "Iniciando impresión…";
pd.EndPrint += (s, a) => textBlock1.Text = "Impresión finalizada";
pd.Print();
}

void CrearTitulo(string titulo)
{
//Crea un TextBlock que servirá como encabezado
TextBlock contenido = new TextBlock() { Text = titulo, FontSize = 20 };
contenido.Effect = new DropShadowEffect();
canvas.Children.Add(contenido);
Canvas.SetLeft(contenido, canvas.Width / 2 – (contenido.ActualWidth / 2));
}

Y el resultado, tanto en un archivo .xps como en OneNote:

2_thumb

3_thumb

Noten cómo también soporta Efectos visuales.  Tal es el caso del DropShadowEffect que se está estableciendo en el título.

En el segundo botón tendremos una ligera variante: mandaremos a imprimir varias páginas.  Esto es logrado a partir de establecer la propiedad HasMorePages = true.  En el caso de este ejemplo hemos establecido una variable a nivel de clase llamada pagina que nos servirá de bandera.  Esta bandera la estaremos revisando hasta que se completen 4 páginas en total.

pd.PrintPage += (s, a) =>
{
canvas = new Canvas() { Width = a.PrintableArea.Width, Height = a.PrintableArea.Height };
CrearTitulo("Esto es una Prueba de Impresión");
CrearSubtitulo(string.Format("Página: {0}", pagina+1));
a.PageVisual = canvas;

a.HasMorePages = !(pagina == 3);
pagina++;

};

Cabe recordar que al establecer la propiedad HasMorePages a true, el evento PrintPage se vuelve a disparar, de esta manera, podemos tener el control del contenido a imprimir en cada página.  El código completo se muestra a continuación:

void CrearSubtitulo(string titulo)
{
//Crea un TextBlock que servirá como subtitulo
TextBlock contenido = new TextBlock() { Text = titulo, FontSize = 14 };
canvas.Children.Add(contenido);
Canvas.SetLeft(contenido, canvas.Width / 2 – (contenido.ActualWidth / 2));
Canvas.SetTop(contenido, 40);
}

private void button2_Click(object sender, RoutedEventArgs e)
{
PrintDocument pd = new PrintDocument() { DocumentName = "Ejemplo de Impresión" };
pd.PrintPage += (s, a) =>
{
canvas = new Canvas() { Width = a.PrintableArea.Width, Height = a.PrintableArea.Height };
CrearTitulo("Esto es una Prueba de Impresión");
CrearSubtitulo(string.Format("Página: {0}", pagina+1));
a.PageVisual = canvas;

a.HasMorePages = !(pagina == 3);
pagina++;

};
pd.StartPrint += (s, a) => textBlock1.Text = "Iniciando impresión…";
pd.EndPrint += (s, a) => textBlock1.Text = "Impresión finalizada";
pd.Print();
}

El resultado final de la impresión de este segundo botón se muestra en la siguiente figura (las flechas rojas se añadieron posteriormente para indicar el número de cada página):

4_thumb

Resumen

Silverlight 4 Beta incluye la funcionalidad de impresión, a través de la clase System.Windows.Printing.PrintDocument.  Esta clase incluye el método Print(), que al ser ejecutado dispara una serie de eventos, entre ellos el evento PrintPage en donde podemos establecer el contenido deseado para imprimir, conocer el tamaño del área de impresión así como determinar si hay más páginas para imprimir (para escenarios de documentos de más de 1 página).

Puedes descargar el código aquí.

Puedes ver el demo en vivo aquí (requiere Silverlight 4 Beta instalado).