Ok, dopo qualche giorno di pausa, riprendiamo un po' con
la nostra guida ad MCAD. Nel mio ultimo post di venerdì scorso, abbiamo visto come
utilizzare la classe PrintDocument per stampare quello che
vogliamo. Il succo sta nel creare un oggetto PrintDocument e gestire l'evento
PrintPage della classe con un handler adatto, come abbiamo
fatto qui:
prnDoc.PrintPage += new PrintPageEventHandler(this.printBirthday);
Quindi, abbiamo creato una function printBirthday(); e usando GDI+ abbiamo
scritto tutto quello che vogliamo sul PrintDocument. Questo
metodo, sebbene funzioni alla perfezione, ha un grosso difetto: non produce
un'anteprima di stampa, ma indirizza l'output direttamente alla stampante
predefinita del sistema. Questo può anche andar bene nel caso di
applicazioni server, o applicazioni schedulate in notturna, o comunque in quei
casi dove è necessario appunto generare una stampa senza intervento da parte
dell'utente. D'altro canto, però, un'applicazione moderna che si rispetti deve
avere la possibilità di visualizzare una bella anteprima di stampa, cosicchè
l'utente possa avere un'idea di quello che sta ottenendo su carta. Questo è
l'obiettivo di oggi.
Come avevamo accennato la volta precedente, abbiamo bisogno di una classe in
più, la PrintPreviewDialog, che non sostituisce la
PrintDocument, ma la complementa e la integra.
Come funziona? Cominciamo col dire che la logica e la struttura che abbiamo già
visto non cambiano: dobbiamo creare un oggetto PrintDocument,
dobbiamo gestire il PrintPage e usare GDI+ per disegnare e
scrivere il contenuto del documento. Vediamo un attimo come ho modificato il
codice:
private void btnPrint_Click(object sender, System.EventArgs e)
{
this.PageNumer = 0;
PrintPreviewDialog prnPreview = new PrintPreviewDialog();
prnPreview.UseAntiAlias = true;
PrintDocument prnDoc = new PrintDocument();
prnDoc.PrintPage += new PrintPageEventHandler(this.printBirthday);
prnDoc.DocumentName = resMan.GetString("TITLE_DOCUMENT");
this.TotalPage = this.lstResults.Items.Count / this.LinePerPage;
if (this.TotalPage == 0) this.TotalPage = 1;
//prnDoc.Print();
prnPreview.Document = prnDoc;
prnPreview.ShowDialog();
}
Innanzitutto ho commentato la riga
prnDoc.Print(): se la lasciassi, manderei in stampa il
documento, esattamente come succedeva prima. Ho istanziato un oggetto
prnPreview, ho associato la property Document
all'oggetto PrintDocument che voglio mandare in stampa (prnDoc) e poi ho
chiamato il metodo ShowDialog() per mostrare l'anteprima. La
chiamata a ShowDialog() scatena l'evento PrintPage del PrintDocument, che
viene gestito esattamente nello stesso modo e con lo stesso codice del mio post
precedente. Possiamo dire quindi che il PrintPreviewDialog funge da container
del PrintDocument: l'output viene reindirizzato su questo oggetto invece che
sulla stampante.
Per fare dei test e delle prove interessanti, ho usato delle
variabili PageNumber e TotalPage: contengono
rispettivamente il numero della pagina corrente e il numero totale delle pagine
che devono essere stampate. In questo modo, nella function
printBirthday ho potuto scrivere quanto
segue:
if (this.PageNumer == this.TotalPage)
ev.HasMorePages = false;
else
ev.HasMorePages = true;
Oppure, aprofittando delle forme contratte di .NET che mi
piacciono veramente tanto:
ev.HasMorePages = !(this.PageNumer == this.TotalPage);
Se proviamo a compilare il codice (che pubblicherò presto),
vediamo come il click sul Button btnPrint mostra una finestra con la nostra
anteprima di stampa che abbiamo appena realizzato. Fantastico!!! Forse l'unica
cosa che mi fa storcere il naso è, per una volta, il troppo controllo che
abbiamo in questo contesto: abituato come sono da anni a lavorare con Crystal
Reports o altri engine di stampa, qui può essere davvero troppo lungo e
macchinoso produrre report di una certa qualità. Vero è però che mai come adesso
posso avere il pieno controllo di quello che succede sul report, potendo fino
all'ultimo lavorare con GDI+ per posizionare bitmap, scritte orizzontali,
verticali, ruotate (fatelo un po' con CR e impazzirete)...non solo, ma il tutto
controllato da codice come si vuole, veramente cose dell'altro mondo!!!
Consiglio inoltre una lettura al mio post di sabato Ereditare da PrintDocument e decorare la nuova classe con un
custom Attribute perchè secondo me è un buon suggerimento su come
velocizzare la creazione di report con gli Attribute di .NET.
Ultima chicca
(opzionale): a qualcuno potrebbe non piacere l'aspetto della
finestra di anteprima generata dalla classe PrintPreviewDialog.
Non so se ci avete fatto caso, ma questa classe eredita da System.Windows.Forms,
per cui, ciliegina sulla torta, abbiamo a disposizione la collection
Controls, per cui possiamo aggiungere controlli a piacimento
sulla WF dell'anteprima. In realtà non è così semplice: io sono riuscito ad
aggiungere un Button btnClose, definito via codice, alla
ToolBar, inserendo questo codice prima della chiamata a
ShowDialog() che abbiamo visto prima. Vediamo un
po':
foreach (object ctl in prnPreview.Controls)
{
if (ctl is ToolBar)
{
ToolBar tbl = (ToolBar) ctl;
tbl.Buttons.Add(btnClose);
}
}
Con un ciclo foreach sulla collection
Controls, trovo l'oggetto ToolBar e faccio il solito
tbl.Buttons.Add (btnClose) per aggiungere il nuovo pulsante
alla ToolBar. Ho evitato di postare il codice per creare il Button
btnClose perchè il solito che si può vedere in qualsiasi InitializeComponent() delle WF. L'unico problema è che il
Button aggiuntivo viene posizionato (chissà perchè) sotto il Button
Close previsto per default: Il ToolBarButton non espone la
property Location, per cui......la soluzione c'è, ma esula
dallo scopo di questo post. Alla prossima...