[Windows 10] Imprimiendo desde nuestras Apps UWP

Print - 01Introducción

Con Windows 10 tenemos gran cantidad de novedades interesantes relacionadas con el desarrollo de aplicaciones empresariales. Desde el gran potencial de Continuum a novedades en la Store pasando por nuevas APIs para trabajar con datos biométricos, bluetooth, criptografía, etc. Entre todas las características que podríamos necesitar, brilla con luz propia la posibilidad de imprimir.

En este artículo vamos a centrarnos en las posibilidades disponibles en los namespaces Windows.Graphics.Printing y Windows.UI.Xaml.Printing.

Nuestra aplicación

Nuestro objetivo será crear una aplicación UWP que mostrará un listado de la compra permitiendo imprimir la misma.

Crearemos un nuevo proyecto UAP:

Nueva App UAP
Nueva App UAP

Añadimos las carpetas Views, ViewModels y Services además de las clases base necesarias para implementar el patrón MVVM de la misma forma que vimos en este artículo.

Comenzamos definiendo la vista de nuestro ejemplo:

<Grid>
     <Grid.RowDefinitions>
         <RowDefinition Height="*" />
         <RowDefinition Height="50" />
     </Grid.RowDefinitions>
     <StackPanel 
        x:Name="PrintableContent">
        <TextBlock 
             Text="Shopping List"
             FontWeight="Black"
             Margin="12"/>
         <ListView
             ItemsSource="{Binding Products}"/>
     </StackPanel>
     <Button 
        Grid.Row="1"
        Content="Print"
        HorizontalAlignment="Center"/>
</Grid>

Sencilla. Cuenta con un listado que mostrará el listado de productos a comprar además de un botón que tendrá como objetivo realizar la impresión de la lista. La lista se encuentra enlazada a una propiedad pública llamada Products:

private ObservableCollection<string> _products;
public ObservableCollection<string> Products
{
     get { return _products; }
     set
     {
          _products = value;
          RaisePropertyChanged();
     }
}

Una colección de cadenas que contendrá cada producto. Cargaremos una lista desde local para simplificar el ejemplo y centrar toda nuestra atención en la impresión.

private void LoadProducts()
{
     Products = new ObservableCollection<string>
     {
          "Milk",
          "Apples",
          "Water",
          "Chicken meat",
          "Coffee",
          "Yogurt",
     };
}

Si ejecutamos el ejemplo veremos:

Nuestro ejemplo
Nuestro ejemplo

Registro para imprimir

En nuestro ejemplo, donde utilizamos el patrón MVVM y otras buenas prácticas, vamos a crear un servicio de impresión lo más genérico posible con el objetivo de poder reutilizarlo de forma rápida y sencilla entre diferentes desarrollos.

El primer paso para poder imprimir desde nuestras aplicaciones pasa por el registro del contrato de impresión. Nuestra aplicación debe realizar este proceso en cada pantalla desde la que deseamos permitir imprimir. Sólo podremos registrar la vista activa por lo también debemos preocuparnos de un proceso en el que eliminar el registro. Si nuestra aplicación navega a una nueva vista de la que deseamos imprimir de una anterior registrada para la impresión, al salir de la previa debe eliminar el registro correctamente.

Para realizar las operaciones básicas de impresión entre las que se encuentra el registro debemos utilizar la clase PrintManager  disponible en el namespace Windows.Graphics.Printing. PrintManager lo utilizaremos para indicar que la aplicación desea participar en la impresión además de poder iniciar la misma programáticamente.

También utilizaremos la clase PrintDocument disponible en el namespace Windows.UI.Xaml.Printing. En esta clase contamos con los métodos y propiedades necesarias para establecer el contenido a enviar a la impresora.

Definimos dos variables con cada una de las clases anteriores en nuestro servicio:

protected PrintManager Printmgr;
protected PrintDocument PrintDoc;

Nos centrámos en el proceso de registro:

public virtual void RegisterForPrinting()
{
     PrintDoc = new PrintDocument();
     PrintDoc.Paginate += CreatePrintPreviewPages;
     PrintDoc.GetPreviewPage += GetPrintPreviewPage;
     PrintDoc.AddPages += AddPrintPages;

     Printmgr = PrintManager.GetForCurrentView();

     Printmgr.PrintTaskRequested += PrintTaskRequested;
}

Instanciamos las clases PrintManager y PrintDocument además de realizar la subscripción a los eventos necesarios.

Para realizar el registro del contrato de impresión desde nuestra ViewModel utilizando el servicio, lo inyectaremos vía constructor:

private IPrintService _printService;

public MainViewModel(IPrintService printService)
{
     _printService = printService;
}

Y utilizamos el método creado RegisterForPrinting:

_printService.RegisterForPrinting();

NOTA: Al salir de la vista debemos quitar la subscripción al contrato de impresión, es decir, todos los eventos a los que nos registramos durante el registro. Si contámos con una aplicación con múltiples vistas, sin elimimar la subscripción, si dejamos la vista y regresamos obtendremos una excepción.

El contenido a imprimir

Ya que tenemos lo suficiente para registrar el contrato de impresión, debemos establecer el contenido que deseamos imprimir. En nuestro ejemplo contámos con un listado de productos correspondientes a una lista de la compra que deseamos imprimir.

¿Cómo funciona?

protected UIElement PrintContent;

La clase UIElement es una clase base de la mayoría de elementos visuales en Windows Runtime. Podemos utilizar cualquier elemento visual derivado de esta clase para imprimir. Nuestro contenido de impresión será por lo tanto un UIElement.

Tenemos donde almacenar el contenido a imprimir, necesitaremos también la operación de impresión. Para ello, utilizaremos una instancia de la clase PrintTask, la operación de impresión, incluyendo el contenido.

protected PrintTask Task;

Cuando realizamos el registro del contrato de impresión, nos registramos a múltiples eventos entre los que destaca PrintTaskRequested. Este evento correspondiente al PrintManager será el primero en lanzarse cuando el usuario quiera imprimir y le mostremos la ventana con la previsualización del contenido.

protected virtual void PrintTaskRequested(PrintManager sender,
            PrintTaskRequestedEventArgs e)
{
     Task = e.Request.CreatePrintTask(PrintTitle, async sourceRequested =>
     {
          PrintTaskOptionDetails printDetailedOptions = PrintTaskOptionDetails.GetFromPrintTaskOptions(Task.Options);

          printDetailedOptions.DisplayedOptions.Clear();
          printDetailedOptions.DisplayedOptions.Add(StandardPrintTaskOptions.Copies);

          Task.Options.Orientation = PrintOrientation.Portrait;

          Task.Completed += async (s, args) =>
          {
               if (args.Completion == PrintTaskCompletion.Failed)
               {
                    await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                    {
                         Debug.WriteLine("Failed to print.");
                    });
               }
           };

           await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
           {
                sourceRequested.SetSource(PrintDoc?.DocumentSource);
           });
     });
}

En este evento, instanciamos PrintTask utilizando el método PrintTaskRequest.CreatePrintTask.

NOTA: Le podemos pasar una cadena de texto al método CreatePrintTask. Se mostrará el texto en el título de la ventana de previsualización del contenido a imprimir.

Tras crear la tarea de impresión, se verifica el número de páginas a imprimir. Se lanza el evento Paginate correspondiente a la interfaz IPrintPreviewPageCollection.

protected virtual void CreatePrintPreviewPages(object sender, PaginateEventArgs e)
{
     PrintDoc.SetPreviewPageCount(1, PreviewPageCountType.Final);
}

Este evento puede lanzarse en múltiples ocasiones. Cada vez que el usuario modifique parámetros de configuración en la vista de previsualización de la impresión, el evento se lanzará para ajustar el contenido.

NOTA: Hemos simplificado nuestro ejemplo añadiendo directamente una única página. Podemos definir y crear tantas páginas como sea necesario.

Cuando en la vista de previsualización se va a mostrar una página, se lanza el evento GetPreviewPage.

protected virtual void GetPrintPreviewPage(object sender, GetPreviewPageEventArgs e)
{
     PrintDocument printDoc = (PrintDocument)sender;
     printDoc.SetPreviewPage(e.PageNumber, PrintContent);
}

Sencillamente establecemos el contenido de previsualización al PrintDocument.

Por último, contámos con el evento AddPages del conjunto de eventos al que nos registramos al registrar el contrato de impresión.

protected virtual void AddPrintPages(object sender, AddPagesEventArgs e)
{
     PrintDoc.AddPage(PrintContent);

     PrintDoc.AddPagesComplete();
}

En este evento añadimos el contenido a añadir a la colección de páginas de PrintDocument, es decir, el contenido a enviar a la impresora.

Estableciendo el contenido

Tras repasar cada unos de los eventos que componen el proceso de impresión, ¿cómo añadimos contenido desde nuestra ViewModel?. En nuestro servicio:

public virtual void SetPrintContent(UIElement content)
{
     if (content == null)
          return;

     PrintContent = content;
}

Tenemos un sencillo método que nos permite establecer cualquier elemento de la interfaz derivado de UIElement como contenido.

Añadir contenido desde la ViewModel será muy sencillo:

_printService.SetPrintContent(GetPrintableContent());

Para añadir el contenido utilizamos un Helper que nos permite obtener un elemento específico de UI utilizando su nombre:

private UIElement GetPrintableContent()
{
     return VisualHelper.FindChild<UIElement>(
                Window.Current.Content, "PrintableContent");
}

El Helper hace uso internamente de la clase VisualTreeHelper.

Mostrar previsualización

Con el registro del contrato de impresión, preparación del contenido y todo lo necesario para poder imprimir, es hora de permitir la operación al usuario. En nuestr servicio, utilizaremos el método asíncrono ShowPrintUIAsync para mostrar una ventana modal al usuario con la previsualización del contenido, las opciones de impresión además de por supusto las opciones para completar o cancelar la impresión.

public async void ShowPrintUiAsync(string title)
{
     try
     {
          PrintTitle = title;

          await PrintManager.ShowPrintUIAsync();
     }
     catch (Exception e)
     {
          Debug.WriteLine("Error printing: " + e.Message + ", hr=" + e.HResult);
     }
}

Si en el momento de lanzar el método ShowPrintUIAsync la operación no es posible, se lanzará una excepción. Es importante capturar esta excepción y actuar en consecuencia, notificando al usuario.

Desde la ViewModel, utilizando nuestro servicio:

_printService.ShowPrintUiAsync("Print Sample");

La ventana de previsualización:

Previsualización de la impresión
Previsualización de la impresión

Además de la previsualización, podremos configurar las opciones de configuración. En estas opciones, además de configuraciones básicas como la impresora a elegir o el número de copias, podemos configurar orientación, modos de color y muchas otras opciones.

Tenéis el código fuente disponible en GitHub:

Ver GitHub

Recordad que podéis dejar cualquier comentario, sugerencia o duda en los comentarios.

Más información

[Xamarin.Forms] Soporte a UWP

Universal Windows Platform

Windows 10 ha llegado como la culminación en el viaje hacia la convergencia en el desarrollo entre plataformas Windows. Ahora hablamos de Apps Universales
escritas una única vez con un código común tanto para la lógica de
negocio como para la interfaz de usuario. Además, generamos un único
paquete que mantendrá una interfaz consistente y familiar para el
usuario pero adaptada a cada plataforma.

Windows 10

Windows 10

Podemos crear apps que funcionen en todo tipo de
dispositivos como teléfonos, tabletas, portátiles, dispositivos IoT,
Surface Hub e incluso HoloLens. Para ello tenemos las vías utilizadas
hasta este momento, es decir, u
tilizando C# y XAML (o VB, C++, etc).

Xamarin.Forms 2.0

Con la llegada de Xamarin 4 han aterrizado una gran
cantidad de diversas novedades entre las que destaca la versión 2.0 de
Xamarin Forms. Entre las novedades principales contamos con:

  • Soporte a UWP.
  • Compilación de XAML (XAMLC).
  • Optimización en Layouts.
  • Optimización de listados. Incluidas nuevas estrategias de cacheo.
  • Corrección de bugs.

Añadiendo UWP a App Xamarin.Forms

Vamos a crear un proyecto Xamarin.Forms y le añadiremos soporte a UWP. Necesitaremos:

  • Visual Studio 2015
  • Windows 10
  • Xamarin.Forms 2.0

Creamos un nuevo proyecto Xamarin.Forms:

Nueva App Xamarin.Forms

Nueva App Xamarin.Forms

Sobre la solución hacemos clic derecho y seleccionamos la opción añadir nuevo proyecto. Dentro de la categoría Universal elegimos la plantilla en blanco.

Nueva Aplicación Universal

Nueva Aplicación Universal

Tras añadir el nuevo proyecto, continuaremos añadiéndolo el paquete NuGet de Xamarin.Forms 2.0 necesario.

Añadimos la referencia a la PCL.

Tenemos las referencias a la PCL y a Xamarin.Forms, ahora debemos
realizar pequeñas modificaciones en código para inicializar Xamarin
Forms y la aplicación. En el método OnLaunched de App.xaml.cs utilizaremos el método Init para inicializar Xamarin.Forms:

Xamarin.Forms.Forms.Init (e);

En las vista principal, MainPage.xaml modificamos el tipo de la página de Page a WindowsPage utilizando el namespace Xamarin.Forms.Platform.UWP.

xmlns:forms="using:Xamarin.Forms.Platform.UWP"

Por último, añadimos una llamada al método LoadApplication en el contructor de la vista principal, MainPage.xaml.cs para inicializar la aplicación.

LoadApplication(new UWP.App());

Tenéis el código fuente disponible e GitHub:

Ver GitHub

Recordad que podéis dejar cualquier comentario, sugerencia o duda en los comentarios.

Limitaciones

El soporte es bastante elevado pero no es aún completo. Existen una serie de limitaciones conocidas:

  • El aspecto de algunas páginas o elementos no esta cerrado.
  • Existe algún posible error en navegación.
  • No hay soporte aún a mapas.

Más información