Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Print Contract con JS/HTML5

Link copiato negli appunti

In questo articolo vedremo come accedere al sistema di stampa di Windows da un'applicazione Windows Store scritta in HTML5/JavaScript. Se siete interessati potete anche leggere come effettuare la stampa da un'app sviluppata in XAML/C#.

Per stampare da un'applicazione Windows Store, è sufficiente attivare la Charms Bar e selezionare il charm "Devices" . Tuttavia, perché una Windows Store app possa accedere al sistema di stampa di Windows è necessario che abbia implementato il Print contract; in caso contrario, attivando il Devices charm otterremmo solo il messaggio: "This app can't send to other devices right now", mostrato nella prossima immagine.

Figura 1.


Oltre che su iniziativa dell'utente, è possibile attivare Devices Charm da codice, tramite il metodo ShowPrintUIAsync. Il seguente snippet mostra un esempio dell'uso del metodo ShowPrintUIAsync all'interno dell'handler di un evento di click di un ipotetico pulsante:

function printButton_click(args) {
    Windows.Graphics.Printing.PrintManager.showPrintUIAsync().done();
}

La documentazione ufficiale Microsoft scoraggia l'uso di questo metodo in applicazioni generiche: è sempre meglio lasciare all'utente la decisione di attivare il processo di stampa tramite l'apposito charm, in modo da garantire l'uniformità della user experience con il resto della piattaforma e delle applicazioni Windows Store.

Altra strada possibile, ma anch'essa sconsigliata, è quella di utilizzare la classica funzione JavaScript window.print per stampare il contenuto della app. Tuttavia, in questo caso viene stampato solo il contenuto mostrato e, considerato che questo contenuto non è quasi mai pensato per la stampa, l'uso di questa funzione potrebbe non rappresentare la soluzione migliore, ed è pertanto scoraggiato dalla documentazione ufficiale di Microsoft.

È importante notare come, nel caso di app in HTML5/JavaScript l'implementazione del Print contract risulti molto più semplice rispetto a un'app in XAML/C#. Mentre in quest'ultimo caso è necessario procedere all'impaginazione del contenuto da inviare al pannello di preview, nonché gestire una pluralità di eventi che segnano le diverse fasi del processo di stampa, nel caso di un'app in HTML5/JavaScript tutte le operazioni di impaginazione e di preview sono portate avanti direttamente dal sistema di stampa di WinRT, semplificando notevolmente l'implementazione del relativo contratto. L'unica cosa che l'app deve fare è fornire il contenuto da stampare.

Lo svantaggio è che, in un'applicazione HTML5/JavaScript, non è possibile modificare l'impaginazione o il contenuto del documento una volta inviato al sistema di preview (ad esempio, per adeguarlo alle opzioni di stampa scelte dall'utente nel pannello di preview), né creare opzioni di stampa custom.

In estrema sintesi, in un'applicazione Windows Store sviluppata in HTML5/JavaScript, l'implementazione del Print contract richiede due passaggi fondamentali, più un terzo opzionale:

  1. Ottenere una reference a un oggetto di tipo PrintManager, la classe che orchestra l'intero flusso di stampa, e abbonarsi al relativo evento PrintTaskRequested.
  2. Creare un oggetto di tipo PrintTask, che rappresenta una specifica operazione di stampa.
  3. Decidere quali opzioni di stampa mostrare all'utente e impostare il loro valore iniziale (opzionale).

Recuperare una reference ad un oggetto PrintManager

Cominciamo con il creare un nuovo progetto Windows Store app usando il template XAML Blank App messo a disposizione da Visual Studio:

Figura 2.


Il primo passo consiste nel recuperare una reference a un oggetto di tipo Windows.Graphics.Printing.PrintManager, ossia la classe responsabile della gestione dell'intero processo di stampa, tramite il metodo statico Windows.Graphics.Printing.PrintManager.getForCurrentView.

Il passo successivo consiste nel sottoscrivere l'evento PrintManager.printTaskRequested, il quale viene sollevato ogniqualvolta l'utente attiva il Devices Charm per accedere al pannello di preview.

Il seguente codice illustra entrambi questi passaggi:

app.onloaded = function () {
    registerForPrintContract();
}
function registerForPrintContract() {
    var printManager = Windows.Graphics.Printing.PrintManager.getForCurrentView();
    printManager.onprinttaskrequested = onPrintTaskRequested;
}

Bisogna ricordare di cancellare la sottoscrizione all'evento PrintTaskRequested ogni volta che la tua app mostra del contenuto che non può o non deve essere stampato. In questo modo, evitiamo che l'utente attivi il processo di stampa anche quando non c'è nessun contenuto da stampare.

Creare un oggetto di tipo PrintTask e gestire i relativi eventi

Una volta sottoscritto l'evento printTaskRequested, è necessario implementare l'operazione di stampa vera e propria creando un oggetto di tipo PrintTask. La classe PrintTask rappresenta infatti una specifica operazione di stampa e include tanto il contenuto da stampare (nella forma di un oggetto di tipo PrintDocument, su cui torneremo fra breve), quanto una serie di opzioni relative alle modalità di stampa (come colore, qualità della stampa, formato e dimensioni della pagina, ecc.).

L'oggetto di tipo PrintTask che rappresenta l'operazione corrente è creato tramite il metodo createPrintTask, esposto - tramite la proprietà request (che a sua volta è di tipo PrintTaskRequest; quest'ultima classe, come il nome suggerisce, rappresenta la richiesta di stampa attivata dall'utente tramite il Devices Charm) - dall'oggetto di tipo PrintTaskRequestedEventArgs ricevuto come parametro dall'handler dell'evento printTaskRequested.

Il seguente snippet mostra l'implementazione di base dell'handler dell'evento printTaskRequested.

function onPrintTaskRequested(printEvent) {
    var printTask = printEvent.request.createPrintTask(
				"HTML.it Print Sample in HTML5/JavaScript",
				function (args) {
					// codice omesso
				});
}

Il metodo CreatePrintTask accetta due parametri: il primo è una stringa che rappresenta il nome da assegnare a quella particolare operazione di stampa, ed è lo stesso nome che contraddistinguerà il nostro documento all'interno del pool di stampa, come mostrato nella prossima immagine:

Figura 3.

Il secondo parametro è rappresentato da un delegate che referenzia il metodo da richiamare quando l'utente selezionerà la periferica da utilizzare per la stampa tra i device disponibili (nel codice sopra riportato il delegate è presentato sotto forma di lambda expression).

Se a questo punto eseguite l'applicazione e attivate il Devices Charm, questa volta verrà mostrato direttamente l'elenco delle periferiche di stampa disponibili sul sistema. Tuttavia, dato che per adesso non abbiamo ancora fornito alcun contenuto da inviare al sistema di stampa per la preview, se selezionassimo una qualunque delle periferiche incluse nell'elenco, otterremmo solo il seguente messaggio: "The app didn't provide anything to print".

Figura 4.

Prima di procedere oltre, è importante sottolineare come WinRT metta a disposizione un tempo relativamente limitato (200 millisecondi) per il completamento delle operazioni all'interno dell'handler dell'evento PrintTaskRequested. Per ovviare a questo limite, la classe PrintTaskRequestedEventArgs espone, tramite la proprietà Request, il metodo GetDeferral, che consente di sfruttare il meccanismo di deferral di WinRT. Tramite questo meccanismo, WinRT attenderà il completamento delle operazioni asincrone fino a quando il deferral non sarà marcato come completato tramite una chiamata al metodo Complete, come illustrato nel seguente snippet:

function onPrintTaskRequested(printEvent) {
    var deferral = printEvent.request.getDeferral();
    var printTask = printEvent.request.createPrintTask(
				"Html.it Print Sample in HTML5/JavaScript",
				function (args) {
					// operazione asincrona
				});
    deferral.complete();
}

Gli eventi della classe PrintTask

Dopo che l'oggetto di tipo PrintTask è stato istanziato, è possibile abbonarsi ai quattro eventi che questa classe espone. Questi eventi sono scatenati in diversi momenti del flusso di stampa e permettono di avere un feedback sullo stato del processo:

Evento Quando si verifica
Completed Al completamento del processo di stampa
Previewing Quando viene inizializzato il pannello di preview della stampa
Progressing ogni volta una nuova pagina è stata inviata al sistema di stampa, in modo da permettere di conoscere, tramite la proprietà DocumentPageCount della classe PrintTaskRequestedEventArgs, lo stato di avanzamento del processo di stampa
Submitting Quando il contenuto viene inviato al sistema di stampa per essere stampato

In particolare, l'evento Completed offre importanti indicazioni sul completamento dell'operazione di stampa. È importante sottolineare come l'evento Completed non venga sollevato solo nel caso in cui il contenuto viene effettivamente inviato al sistema di preview per essere stampato, ma anche quando il print task è stato cancellato dall'utente, è fallito, o è stato abbandonato per qualsiasi motivo.

L'evento consente di dare un feedback all'utente circa il risultato finale dell'operazione. Per conoscere questo risultato, possiamo utilizzare la proprietà Completion dell'oggetto di tipo PrintTaskCompletedEventArgs ricevuto come parametro dal relativo event handler. Questa proprietà può assumere uno dei seguenti valori:

Valore Descrizione
Abandoned L'operazione è stata abbandonata. Questo accade quando, per qualche motivo, l'operazione di stampa si interrompe dopo che il delegate di tipo PrintTaskSourceRequestedHandler è stato eseguito. Da non confondere con il successivo, che invece viene sollevato a seguito di una azione dell'utente
Canceled L'operazione è stata annullata dall'utente (in genere chiudendo il Devices Charm prima di aver avviato l'operazione di stampa)
Submitted Il contenuto è stato inviato al sistema di stampa (a seguito della pressione del pulsante Stampa nel pannello di preview)
Failed L'operazione di stampa è fallita

Il seguente codice mostra, a titolo d'esempio, come gestire l'evento Completed.

function onPrintTaskRequested(printEvent) {
    var printTask = printEvent.request.createPrintTask(
				"Html.it Print Sample in HTML5/JavaScript",
				function (args) {
					// aggiungiamo un delegate
					printTask.oncompleted = onPrintTaskCompleted;
				});
}
//  codice del delegate
function onPrintTaskCompleted(printTaskCompletionEvent) {
    if (printTaskCompletionEvent.completion ===
            Windows.Graphics.Printing.PrintTaskCompletion.failed) {
        Windows.UI.Popups.MessageDialog("Print Task Fallito!");
    }
}

Aggiungere del contenuto da stampare

Una volta creato l'oggetto PrintTask, è necessario fornire il contenuto da stampare. Per far questo, occorre passare al PrintTask una reference al documento HTML che deve essere stampato tramite il metodo SetSource dell'oggetto PrintTaskRequestedEventArgs ricevuto come parametro dal relativo event handler.

Il documento viene recuperato mediante il metodo MSApp.getHtmlPrintDocumentSource, e può consistere nel documento root, un IFrame, un frammento o in un documento di tipo SVG. Dobbiamo considerare comunque che deve trattarsi di un documento, ma non di un semplice elemento HTML. Il prossimo snippet mostra questo punto:

function onPrintTaskRequested(printEvent) {
    var printTask = printEvent.request.createPrintTask(
				"HTML.it Print Sample in HTML5/JavaScript",
				function (args) {
					args.setSource(MSApp.getHtmlPrintDocumentSource(document));
					printTask.oncompleted = onPrintTaskCompleted;
				});
}

Aggiungiamo adesso del codice HTML per mostrare a video un testo d'esempio, testo che poi verrà passato al sistema di stampa per la preview. Il seguente listato mostra il codice HTML usato per la pagina default.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Html.it.Demo.PrintContract.JS</title>
    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
    <!-- PrintToSample references -->
    <link href="/css/default.css" rel="stylesheet" />
    <script src="/js/default.js"></script>
</head>
<body>
    <div id="myDocument">
        <div id="myTitle">
            <h1>Html.it Print Sample in HTML5/JavaScript</h1>
        </div>
        <div id="myDocumentBody">
            <p><img id="sampleImage" src="images/print_1.png" alt="Sample Image" />Lorem ipsum dolor sit amet, consectetur adipiscing elit...</p>
            <p>Ut vitae pretium lorem. Aliquam eleifend leo et mollis commodo. Quisque ac pretium nibh, sit amet suscipit elit...</p>
            <p>Integer id dapibus purus. Integer a elit sed risus rhoncus scelerisque ac vel nisi...</p>
        </div>
    </div>
</body>
</html>

Nota: nei template per Windows 8 con Visual Studio 2012, le versioni degli script esterni inclusi riporteranno la versione 1.0 del framework WinJS (Microsoft.WinJS.1.0 invece che Microsoft.WinJS.2.0, che è la versione che troviamo in Windows 8.1 e Visual Studio 2013).

La prossima immagine mostra la pagina iniziale dell'app assieme al pannello di preview.

Figura 5.


Creare un contenuto custom

Fino ad ora ci siamo limitati a mandare in stampa lo stesso documento così come appare a schermo. Questo non è molto diverso da quello che otterremmo utilizzando il metodo window.print in un'applicazione web. Adesso vedremo come migliorare l'esperienza di stampa dell'utente utilizzando un documento specificamente formattato per la stampa.

Esistono almeno due strade per implementare questo scenario tramite l'uso di attributi CSS.

La prima strada, che è anche la più semplice, è data dall'attributo HTML media, il quale permette di indicare che un certo stile si applica unicamente a determinati tipi di contenuto. Il seguente codice mostra l'header di una pagina HTML che utilizza l'attributo media per puntare a un file CSS specificamente pensato per la stampa.

<head>
    <meta charset="utf-8" />
    <title>Html.it.Demo.PrintContract.JS</title>
    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
    <!-- Html.it.Demo.PrintContract.JS references -->
    <link href="/css/default.css" rel="stylesheet" />
    <link rel="stylesheet" type="text/css" href="/css/print.css" media="print" />
    <script src="/js/default.js"></script>
</head>

Lo stesso risultato può essere raggiunto utilizzando l'analogo attributo CSS media, come mostrato qui di seguito:

@media print {
    body {
        width: 100%;
        height: 100%;
        font-size: 18px;
    }
}

In entrambi i casi, il documento verrà formattato utilizzando uno stile specificamente pensato per la stampa.

L'altra strada da percorrere è quella di utilizzare l'attributo rel dell'elemento link, in combinazione con l'attributo media, in modo da inviare al sistema di stampa un documento HTML differente rispetto a quello mostrato a video (magari semplicemente formattato per la stampa).

Il vantaggio, in questo caso, è che il documento per la stampa potrà avere una struttura anche radicalmente diversa rispetto a quello mostrato all'utente (ad esempio, con un header e un footer specifici per la stampa e una impaginazione adatta alla lettura su carta rispetto a quella a video).

Il seguente snippet mostra la pagina default.html che punta al documento secondario tramite l'elemento rel:

<head>
    <meta charset="utf-8" />
    <title>HTML.it.Demo.PrintContract.JS</title>
    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
    <!-- HTML.it.Demo.PrintContract.JS references -->
    <link href="/css/default.css" rel="stylesheet" />
    <link rel="alternate" href="printsample.html" media="print" />
    <script src="/js/default.js"></script>
</head>

Il seguente listato mostra la definizione completa della pagina secondaria che sarà passata al sistema di stampa di Windows.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Html.it.Demo.PrintContract.JS</title>
    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
    <!-- PrintToSample references -->
    <link href="/css/print.css" rel="stylesheet" />
    <script src="/js/default.js"></script>
</head>
<body>
    <div id="myDocument">
        <div id="header">
            <img id="logo" src="images/microsoft-logo.png" alt="Your logo" />
        </div>
        <div id="myTitle">
            <h1>Html.it Print Sample in HTML5/JavaScript</h1>
        </div>
        <div id="myDocumentBody">
            (codice omesso per brevità)
        </div>
    </div>
</body>
</html>

Come si vede, la pagina secondaria contiene un riferimento a un diverso file CSS (print.css al posto del classico default.css aggiunto di default da Visual Studio), nonché un header con il logo della società che apparirà nel documento stampato (ma non a video). La prossima immagine mostra le differenze tra la pagina visualizzata a schermo e il documento inviato al sistema di preview.

Figura 6.


Finora abbiamo sfruttato attributi HTML/CSS per fornire al sistema di stampa un documento HTML specificamente formattato per la stampa. È possibile ottenere lo stesso risultato manipolando programmaticamente il DOM (Document Object Model) della pagina prima di inviarla al sistema di preview. Il seguente snippet mostra un esempio di come modificare la sezione head di una pagina html in modo da raggiungere lo stesso risultato:

var alternateLink = document.createElement("link");
alternateLink.setAttribute("id", "alternateContent");
alternateLink.setAttribute("rel", "alternate");
alternateLink.setAttribute("href", "printsample.html");
alternateLink.setAttribute("media", "print");
document.getElementsByTagName("head")[0].appendChild(alternateLink);
args.setSource(MSApp.getHtmlPrintDocumentSource(document));

Come già detto, il metodo MSApp.getHtmlPrintDocumentSource accetta un qualunque documento (o frammento) HTML. Ad esempio, si può scaricare il documento da stampare dal Web e poi manipolarne la struttura prima di mandarlo al sistema di stampa, oppure crearlo programmaticamente.

Il seguente codice mostra come stampare solo un frammento di documento (nella forma di un oggetto IFrame) isolando il contenuto da inviare al sistema di preview.

var newdoc = document.getElementById("frameToPrint");
args.setSource(MSApp.getHtmlPrintDocumentSource(newdoc.contentDocument));

In alternativa, è possibile creare un nuovo documento HTML via codice, tramite il metodo createDocumentFragment, come mostrato nel prossimo snippet:

var p = document.createElement("p");
var txt = 'Sample paragraph';
var t = document.createTextNode(txt);
p.appendChild(t);
var doc = document.createDocumentFragment();
doc.appendChild(p);
args.setSource(MSApp.getHtmlPrintDocumentSource(doc));

In conclusione, l'implementazione del contratto di stampa in una Windows Store app in HTML5/JavaScript risulta relativamente semplice, soprattutto se confrontata con quanto richiesto in un'app in XAML/C#.

È sufficiente ottenere una reference all'oggetto PrintManager tramite il metodo GetForCurrentView, implementare l'handler per l'evento PrintManager.PrintTaskRequested, creare un oggetto di tipo PrintTask, che rappresenta una specifica operazione di stampa, e passare a quest'utlimo il documento da stampare (formattato per la stampa tramite una delle tecniche analizzate nelle pagine precedenti).

Si può anche decidere quali opzioni visualizzare nel pannello di preview, in quale ordine e come impostarne il valore iniziale. Vedremo le cararistiche più avanzate, a differenza di quanto accade in un'app in XAML/C#, in una Windows Store app in HTML5/JavaScript non è possibile - almeno per il momento - creare opzioni di stampa custom.

Ti consigliamo anche