En el artículo anterior, vimos los fundamentos del modelo de clases para imprimir desde nuestras aplicaciones de Silverlight 4.  Recordemos que la propiedad HasMorePages indica si nuestro documento tiene más páginas que imprimir; si HasMorePages es true entonces el evento PrintPage se disparará nuevamente hasta que HasMorePages sea false.

En este artículo veremos cómo aprovechar esta característica para poder mandar a imprimir un catálogo de artículos (en este ejemplo, un catálogo de álbumes de música) ya que este escenario será bastante común en las Aplicaciones de Línea de Negocio que construyamos con Silverlight 4.

Escenario

Dado el siguiente escenario:

1_thumb1

Podemos apreciar que tenemos un ListBox el cual contiene una lista de álbumes de música.  Además estamos cambiando el DataTemplate para poder mostrar de una mejor manera cada uno de esos elementos.  El siguiente fragmento de código muestra el Xaml de la aplicación:

<UserControl x:Class="Demo.SL4.ImpresionMultiple.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Demo.SL4.ImpresionMultiple"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="500">
<UserControl.Resources>
<local:Albumes x:Key="albumes" />
<DataTemplate x:Key="VistaDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Border BorderBrush="Black"
Padding="3"
BorderThickness="2"
CornerRadius="10">
<Image Source="{Binding Foto}"
Width="100" />
</Border>
<StackPanel Grid.Column="1">
<TextBlock Text="{Binding Titulo}"
FontSize="20" />
<TextBlock Text="{Binding Banda}" />
</StackPanel>
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot"
Background="White"
DataContext="{StaticResource albumes}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox Grid.Column="1"
HorizontalAlignment="Left"
Margin="5"
Name="listBox1"
VerticalAlignment="Top"
Width="450"
Height="450"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource VistaDataTemplate}" />
<StackPanel Height="228"
HorizontalAlignment="Left"
Name="stackPanel1"
VerticalAlignment="Top"
Margin="5">
<Button Height="50"
Name="button1"
Width="120">
<Button.Content>
<StackPanel>
<TextBlock HorizontalAlignment="Center">Imprimir</TextBlock>
<TextBlock HorizontalAlignment="Center">ListBox</TextBlock>
</StackPanel>
</Button.Content>
</Button>
<Button Height="50"
Name="button2"
Width="120">
<Button.Content>
<StackPanel>
<TextBlock HorizontalAlignment="Center">Impresión</TextBlock>
<TextBlock HorizontalAlignment="Center">Personalizada</TextBlock>
</StackPanel>
</Button.Content>
</Button>
</StackPanel>
</Grid>
</UserControl>

Impresión de Elementos

La propiedad PageVisual del objeto de argumentos en el evento PrintPage indica ultimadamente cuál es el contenido a imprimir, siendo este contenido parte del árbol de Xaml o no.  Es decir que, si establecemos el ListBox como el PageVisual…

pd.PrintPage += (s, a) =>
{
//Establecemos el ListBox como el PageVisual
a.PageVisual = listBox1;
};

…nuestro resultado de impresión será el siguiente (impresión en XPS):

2_thumb1

Tal vez este mecanismo no sea el adecuado para presentar correctamente la impresión, ya que se imprime el elemento tal y como aparece en pantalla; es decir, una “fotografía” del elemento (incluyendo las barras de scroll, los bordes, etc.).

Impresión Personalizada

Podemos lograr un grado más alto de personalización creando de manera dinámica el contenido Xaml que deseamos imprimir.  En el caso de este ejemplo generaremos dinámicamente un Canvas que contenga un encabezado, además de un ItemsControl que despliegue todos y cada uno de los elementos a imprimir. Además de lo anterior, nos aseguraremos que la impresión permita múltiples páginas y que se impriman los registros en cada página de manera correcta.

Canvas

Tal como en el artículo anterior, creamos un Canvas que tenga el mismo tamaño del área de impresión disponible, según la impresora seleccionada:

pd.PrintPage += (s, a) =>
{
canvas = new Canvas() { Width = a.PrintableArea.Width, Height = a.PrintableArea.Height };

Encabezado

Para cada página a imprimir, se agregará un encabezado que contenga un logo a la izquierda, el título del encabezado centrado, y un texto que indique el número de página actual.

Nota: Hay un issue con Silverlight 4 Beta que impide la impresión de imágenes creadas dinámicamente.

Lista de Elementos

Esta es la parte más importante de este escenario.  Necesitamos soportar el hecho de que habrá más de una página al momento de imprimir.  En el ejemplo de este artículo tenemos un objeto llamado Albumes (List<Album>) el cual tiene un total de 42 álbumes.

Para la impresión personalizada crearemos otro DataTemplate llamado ImpresionDataTemplate el cual desplegará de manera diferente los mismos elementos de la pantalla.  Por ejemplo, quitaremos la imagen de cada álbum, además desplegaremos los elementos dentro de un Grid con cuatro columnas.  Las columnas que mostraremos serán Titulo, Banda, Inventario y FechaLanzamiento.  El siguiente fragmento de Xaml muestra el DataTemplate para la impresión:

<DataTemplate x:Key="ImpresionDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Titulo}" FontWeight="Bold" Grid.Column="0" />
<TextBlock Text="{Binding Banda}" Grid.Column="1" />
<TextBlock Text="{Binding Inventario}" Grid.Column="2" />
<TextBlock Text="{Binding FechaLanzamiento}"
Grid.Column="3" />
</Grid>
</DataTemplate>

En el método de creación del listado por página (método CrearLista()) crearemos un ItemsControl el cual su DataTemplate sea el descrito arriba.  Asimismo, estableceremos como su fuente de datos (propiedad ItemsSource) únicamente los registros que quepan en la página actual.  Con los métodos de extensión Skip() y Take() podemos fácilmente leer cierto número de registros del objeto Albumes según la página actual.  El siguiente fragmento de código muestra este concepto:

void CrearLista(int pagina)
{
ItemsControl lista = new ItemsControl();
lista.Width = canvas.Width;
lista.ItemTemplate = this.Resources["ImpresionDataTemplate"] as DataTemplate;

//Obtiene únicamente los registros que van en la página actual
//identificada en el argumento 'pagina' del método
lista.ItemsSource = fuenteDatos.Skip((int)(pagina*lineasPorPagina)).Take((int)lineasPorPagina);
//Agregamos la lista al Canvas
canvas.Children.Add(lista);

//Respetamos el alto del encabezado
Canvas.SetTop(lista, altoEncabezado);

//Incrementamos el contador
paginaActual++;
}

Finalmente, en el evento PrintPage invocamos los métodos de creación del encabezado y el de la creación de elementos por página:


CrearEncabezado("Lista de Álbumes");

CrearLista(paginaActual);

a.PageVisual = canvas;

a.HasMorePages = !(paginaActual == totalPaginas);

Muy importante en el código anterior es el valor de la propiedad HasMorePages.  En este ejemplo estamos indicando si hay más páginas siempre y cuando el contador (variable paginaActual) sea diferente al número total de páginas (variable totalPaginas).  Recordemos que si HasMorePages es true, el evento PrintPage nuevamente se disparará, provocando así la impresión de todas y cada una de las páginas del listado.

El resultado:

3_thumb1

Cabe mencionar que el DataTemplate específico para la impresión no es el único mecanismo para hacerlo de manera personalizada, también pudimos haber hecho un StackPanel o Grid de manera dinámica y leer programáticamente (un foreach tal vez) cada Album de Albumes programáticamente e ir agregando al contenedor los elementos manualmente.

Resumen

A través de la propiedad HasMorePages tenemos el control para indicar si nuestro documento a imprimir tiene múltiples páginas.  Asimismo vimos que un DataTemplate específico para la impresión es un mecanismo que nos facilita la manera en la que queremos que se imprima una lista de elementos en un reporte.

Pueden descargar el código fuente aquí

Pueden ver el demo en vivo aquí (requieres Silverlight 4 Beta instalado)