Technology Experience

Contenuti gestiti da Igor Damiani
posts - 949, comments - 2741, trackbacks - 15120

My Links

News

  • Questo blog si propone di raccogliere riflessioni, teoriche e pratiche, su tutto quello che riguarda il world-computing che mi sta attorno: programmazione in .NET, software attuale e futuro, notizie provenienti dal web, tecnologia in generale, open-source.

    L'idea è quella di lasciare una sorta di patrimonio personale, una raccolta di idee che un giorno potrebbe farmi sorridere, al pensiero di dov'ero e cosa stavo facendo.

    10/05/2005,
    Milano

Archives

Post Categories

Generale

[MCAD.13] Far stampare la nostra applicazione...senza anteprima!

Ho in mente parecchie idee carine sull'applicazione che sta fungendo da base di studio per MCAD. Prima di passare a cose più serie, o comunque a cose che bene o male ho già visto, (come ADO.NET o XML) ho preferito questa volta vedere qualcosa che non ho mai preso in considerazione: la stampa . Non che le mie applicazioni non abbiamo mai stampato, semplicemente mi sono sempre appoggiato a librerie o software esterni (come il mitico Crystal Reports).

La Study Guide di Lorenzo ci parla di alcuni controlli e di alcuni component da mettere sulla WF per implementare e per permettere al nostro software di stampare. Essi sono:

  1. PrintDocument
  2. PrintPreviewDialog
  3. PageSetupDialog
  4. PrintDialog

Sono tutte classi contenute nel framework .NET per fare tutto quello che ci serve. In questo post (e presumibilmente nel prossimo), ci occuperemo dei primi due. Il più importante è sicuramente la classe PrintDocument: contiene infatti il documento vero e proprio che deve essere stampato, con tutto il testo, la grafica e in generale l'output che deve essere mandato alla stampante. Vi state chiedendo come funziona? Vediamolo subito, sono qui per questo!!

Innanzitutto, una precisazione. Venendo da una lunga esperienza con VB6, sono abituato ad aggiungere sulla WF i controlli che mi servono a design-time, indipendentemente dal fatto che mi servano sempre oppure no. Per esempio, se su un form di VB6 devo lanciare una stampa, mettevo il controllo di Crystal Reports, se dovevo leggere dalla COM, mettevo il controllo MSCOMM.OCX, anche se dovevo leggere dalla seriale solo una volta ogni tanto. Questo con .NET può (e, aggiungo io, deve, se possibile) essere evitato. Perchè? Perchè aggiungere un controllo a design-time vuol dire aggiungere codice, appesantire la InitializeComponent(); e quindi eseguire del codice che magari non sempre serve, istanziando classi, facendo aspettare l'utente più del dovuto e consumando memoria inutilmente. Il grande vantaggio di .NET è quello di poter dichiarare oggetti solo quando servono , come è il caso degli esempi che sto per andare a fare sul mio progetto.

Quindi, morale della favola. E' vero che possiamo trascinare dalla Toolbox il component PrintDocument, ma noi non facciamolo: io ho preferito aggiungere un Button btnPrint e creare la seguente function:

private void btnPrint_Click(object sender, System.EventArgs e)
{
    PrintDocument prnDoc = 
new PrintDocument();
    prnDoc.PrintPage += 
new PrintPageEventHandler(this.printBirthday);
    prnDoc.DocumentName = resMan.GetString("TITLE_DOCUMENT");
    prnDoc.Print();
}

In pratica, istanzio un oggetto della classe PrintDocument solo quando l'utente clicca sul Button e non prima, quando potrebbe anche non servire. Poi, delego l'evento PrintPage alla function printBirthday(), do un nome al documento e lancio il metodo Print() della classe per eseguire la stampa vera e propria. Vi sembra un po' poco, vero? Devo scrivo il codice per indicare quello che voglio effettivamente stampare? Vediamolo!

Il succo del discorso è nell'evento PrintPage della classe PrintDocument. Questo evento viene sollevato per ogni pagina che voglio stampare. Al suo interno, usando GDI+ scrivo, tramite i metodi della classe Graphics, quello che voglio. Ecco il codice che ho scritto io (ma ovviamente potrebbe essere qualsiasi cosa):

private void printBirthday(object sender, PrintPageEventArgs ev)
{
    
// questo evento viene eseguito per ogni pagina
    
PageNumer++;
    Font printFontHeader = 
new Font("Tahoma", 20, FontStyle.Bold);
    Font printFontText = 
new Font("Tahoma", 14, FontStyle.Regular);
    Font printFontFooter = 
new Font("Tahoma", 9, FontStyle.Italic);

    
// posizione dell'header
    
float posy = 50;
    
float posx = ev.MarginBounds.Left;
    
// spaziatura tra una riga e l'altra
    
float spaz = 5;
    
    
// scrivo l'header in alto a sinistra
    
ev.Graphics.DrawString(((PrintDocument) sender).DocumentName, printFontHeader, Brushes.Black, posx, posy);
    posy += printFontHeader.GetHeight() + (spaz * 3);

    
// disegno una linea di separazione
    
Point p1 = new Point((int) posx, (int) posy);
    Point p2 = 
new Point(ev.MarginBounds.Right, (int) posy);
    ev.Graphics.DrawLine(
new Pen(Brushes.Red, 1), p1, p2);

    posy += 20;
    
// scrivo una linea per ciascun Item della ListBox
    
foreach (string item in this.lstResults.Items)
    {
        ev.Graphics.DrawString(item, printFontText, Brushes.Black, posx, posy);
        posy += printFontText.GetHeight() + spaz;
    }

    
// scrivo il numero di pagina in basso a sinistra
    
posy = ev.MarginBounds.Bottom;
    ev.Graphics.DrawString(resMan.GetString("PAGE") + 
this.PageNumer, printFontText, Brushes.Black, posx, posy);
    posx = ev.MarginBounds.Right - 120;
    ev.Graphics.DrawString(DateTime.Now.ToShortDateString(), printFontText, Brushes.Black, posx, posy);
}

L'evento printBirthday ha due parametri: sender (che è il nostro oggetto PrintDocument) e ev (di tipo PrintPageEventArgs). ev in particolare espone la proprietà Graphics, e grazie a questa io posso utilizzare metodi come DrawString, DrawLine e così via, per disegnare quello che voglio sul foglio. Vediamo qualche dettaglio che può essere sempre utile, magari a chi non ha mai lavorato con GDI+ (che è estremamente importante sapere per gestire la stampa in questo modo).

Innanzitutto, credo per comodità 3 istanze di Font: una contiene il font per l'header, una per il testo normale, e l'ultima per il footer. Con la riga di codice

ev.Graphics.DrawString(((PrintDocument) sender).DocumentName, printFontHeader, Brushes.Black, posx, posy);

scrivo il titolo del documento prelevato dal file di risorsa ("Lista dei compleanni" in italiano, "List of birthday" in inglese). Uso un brush dato da Brushes.Black. La posizione iniziale è data dalle variabili posx e posy. posx viene inizialmente impostata in base al margine sinistro (ev.MarginBounds.Left).
Traccio una linea di separazione tra il titolo e il testo rimanente:

// disegno una linea di separazione
Point p1 = new Point((int) posx, (int) posy);
Point p2 = 
new Point(ev.MarginBounds.Right, (int) posy);
ev.Graphics.DrawLine(
new Pen(Brushes.Red, 1), p1, p2);

Poi comincio un ciclo foreach per ciclare tutti gli elementi contenuti nella ListBox sulla Windows Form e li scrivo uno per uno usando ancora una volta DrawString. Notate l'uso di GetHeight per calcolare la posizione verticale dell'elemento successivo: questo metodo di Font in pratica ritorna l'altezza di un testo scritto con quel particolare Font. Aggiungo al valore ottenuto da GetHeight una variabile spaz per dare un po' più di spazio per maggiore leggibilità. Con la stessa logica, scrivo in basso a sinistra il numero della pagina, e in basso a destra la data corrente (DateTime.Now.ToShortDateString()).

A questo punto, bisognerebbe controllare se serve una nuova pagina: in tal caso è sufficiente impostare
ev.HasMorePages = true.In questo modo l'evento printBirthday() viene eseguito di nuovo, creando una seconda pagina, e così via. Quando tutte le pagine sono state create secondo il metodo che abbiamo appena visto, l'output va direttamente alla stampante, senza anteprima, perciò...attenti!

Se volete mostrare prima un'anteprima di stampa, dovete utilizzare il controllo PrintPreviewDialog, di cui parlerò la prossima volta. In questo momento, ho alcuni test e un po' di debug da fare!

powered by IMHO 1.2

Print | posted on venerdì 15 luglio 2005 18:46 | Filed Under [ MCAD ]

Feedback

Gravatar

# Ereditare da PrintDocument e decorare la nuova classe con un custom Attribute

16/07/2005 13:53 | Technology Experience
Gravatar

# [70-526.2] Struttura dell'esame e qualche rimando al passato

28/11/2006 13:18 | Technology Experience
Gravatar

# [70-526, #7] Stampare con il Framework 2.0

29/01/2007 14:18 | Technology Experience
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET