Press "Enter" to skip to content

Stampare i dati contenuti in una DataTable

In risposta ad una domanda sul forum Microsoft da parte di un utente principiante che voleva stampare dei dati da una applicazione Windows Forms,  Il thread lo trovate a questo indirizzo, ho implementato una mini funzione di stampa dei dati letti dal programma scritto per il post Come eseguire una query filtrata su SQL Server alla modifica del testo su una textbox dove potete leggere la costruzione della form.

Oggi ho semplicemente aggiunto un il Button btnPrint in fondo alla form ed il suo event handler Andiamo a vedere cosa ho implementato.

mainform_01

L’Event Handler del button

Questo codice prepara e lancia la stampa.

private void btnPrint_Click(object sender, EventArgs e)
{
PrintDialog prd = new PrintDialog();
if (prd.ShowDialog() == DialogResult.OK)
{
PrintPreviewDialog prnPrev = new PrintPreviewDialog();

PrintDocument prnDoc = new PrintDocument();

prnDoc.DefaultPageSettings.Landscape = true;
prnDoc.PrintPage += new PrintPageEventHandler(prnDoc_Printpage);
prnPrev.Document = prnDoc;
mPrintRowIndex = 0;
prnPrev.Owner = this;
prnPrev.Width = 640;
prnPrev.Height = 480;
prnPrev.ShowDialog();

}

}

Come potete vedere, il codice dell’event Handler che ho implementato, predispone e lancia la stampa. Si tratta di qualcosa di molto basico, vi consiglio di cercare in rete tutto quello che si può configurare per costruire delle stampe professionali.

Intanto vediamo di cosa abbiamo avuto bisogno:

  • PrintDialog – è la finestra per selezionare la stampante, la lanciamo prima della stampa in modo tale che la stampante selezionata sia utilizzata dalla PrintPreviewDialog che useremo fisicamente per la stampa.
  • PrintPreviewDialog – è’ l’oggetto che utilizzeremo per visualizzare il nostro documento prima di stamparlo dando modo all’utente di confermare o annullare la stampa.
  • PrintDocument – è’ l’oggetto su cui disegneremo la nostra stampa.
  • mPrintRowIndex – è una variabile definita a livello di classe, che è fondamentale per gestire la stampa, la inizializziamo a 0 perché sarà il nostro cursore per scorrere le righe della DataTable che stamperemo.

Nel codice qui sopra, abbiamo fatto le seguenti cose:

PrintDialog prd = new PrintDialog();
if (prd.ShowDialog() == DialogResult.OK)
{

Abbiamo lanciato la PrintDialog, in modo che l’utente scelga la stampante.

PrintPreviewDialog prnPrev = new PrintPreviewDialog();

Abbiamo creato la PrintPreviewDialog, che si occuperà di lanciare la stampa.

PrintDocument prnDoc = new PrintDocument();

Abbiamo creato il PrintDocument per poter creare la stampa.

prnDoc.DefaultPageSettings.Landscape = true;
prnDoc.PrintPage += new PrintPageEventHandler(prnDoc_Printpage);

Abbiamo indicato al PrintDocument di stampare in orizzontale sulla nostra pagina, e associato al PrintDocument un event handler per l’evento PrintPage tale Event Handler, conterrà tutto il codice necessario a produrre la stampa.

prnPrev.Document = prnDoc;

Abbiamo Inserito il nostro PrintDocument nell’apposita property della PrintPreviewDialog

mPrintRowIndex = 0;

Abbiamo azzerato la nostra variabile che ci servirà da cursore per scorrere la DataTable.

prnPrev.Owner = this;
prnPrev.Width = 640;
prnPrev.Height = 480;

Abbiamo Indicato che la nostra form è l’owner della PrintPreviewDialog non obbligatorio ma a me piace. Indicato le dimensioni della PrintPreviewDialog (sugli schermi attuali, la dimensione automatica è minuscola).

prnPrev.ShowDialog();

Abbiamo lanciato la PrintPreviewDialog che ci mostrerà la stampa e ci permetterà di lanciarla sulla stampante selezionata in precedenza.

Il codice che crea le pagine da stampare

Il codice seguente, definisce la nostra variabile cursore (è importante e per questo ve la faccio notare) e poi costruisce la stampa di una pagina del nostro documento. La sua definizione è stata inserita all’inizio della form come tutte le definizioni di variabili di classe.

private int mPrintRowIndex = 0;
private void prnDoc_Printpage(object sender, PrintPageEventArgs e)
{
Graphics graphics = e.Graphics;
SolidBrush fColor = new SolidBrush(Color.Black);
int fontHeight = (int)mDocFont.GetHeight(e.Graphics);
int topMargin = e.MarginBounds.Top;
int printableH = e.MarginBounds.Height;
int printableW = e.MarginBounds.Width;
int startX = 10;
int startY = 10;
int linesPerPage = ((e.MarginBounds.Height / fontHeight) ) - 1;

graphics.DrawString("COMPANY NAME", mDocFontInt, fColor, startX, startY);
startX += 400;
graphics.DrawString("ADDRESS", mDocFontInt, fColor, startX, startY);
startX += 400;
graphics.DrawString("CITY", mDocFontInt, fColor, startX, startY);
startX = 10;
startY += fontHeight + 5;
graphics.DrawLine(new Pen(Color.Black, 2), startX, startY, (startX + 1150), startY);
startY += 10;
e.HasMorePages = true;

for (int x = 0; x < linesPerPage; x++)
{
if (mPrintRowIndex < mItemsSource.Rows.Count)
{
DataRow row = mItemsSource.Rows[mPrintRowIndex];
mPrintRowIndex++;

graphics.DrawString(row["CompanyName"].ToString(), mDocFont, fColor, startX, startY);
startX += 400;
graphics.DrawString(row["Address"].ToString(), mDocFont, fColor, startX, startY);
startX += 400;
graphics.DrawString(row["City"].ToString(), mDocFont, fColor, startX, startY);
startX = 10;

startY += fontHeight+3;

}
else
{
e.HasMorePages = false;
mPrintRowIndex = 0;
break;
}
}
}

Commentiamolo pezzetto per pezzetto così da capire cosa fa realmente:

Graphics graphics = e.Graphics;
SolidBrush fColor = new SolidBrush(Color.Black);
int fontHeight = (int)mDocFont.GetHeight(e.Graphics);
int topMargin = e.MarginBounds.Top;
int printableH = e.MarginBounds.Height;
int printableW = e.MarginBounds.Width;

Per prima cosa, recuperiamo dai dati degli Event Argument passati il Grafical Context graphics, questo è l’oggetto su cui andremo a disegnare la pagina.
Creiamo un Brush nero con cui disegneremo il contenuto del report. Leggiamo alcune misure che utilizzeremo poi per la stampa, ovvero l’altezza della font, il margine superiore, e l’altezza e larghezza dell’area di stampa.

int startX = 10;
int startY = 10;
int linesPerPage = ((e.MarginBounds.Height / fontHeight) ) - 1;

Stabiliamo il punto di partenza iniziale della pagina (le coordinate x e y nel rettangolo interno ai margini, e calcoliamo quante righe stanno in una pagina.

graphics.DrawString("COMPANY NAME", mDocFontInt, fColor, startX, startY);
startX += 400;
graphics.DrawString("ADDRESS", mDocFontInt, fColor, startX, startY);
startX += 400;
graphics.DrawString("CITY", mDocFontInt, fColor, startX, startY);
startX = 10;
startY += fontHeight + 5;
graphics.DrawLine(new Pen(Color.Black, 2), startX, startY, (startX + 1150), startY);
startY += 10;

Stampiamo l’intestazione delle 3 colonne che stamperemo, utilizzando l’oggetto Graphics e il metodo DrawString che come dice il suo nome, permette di disegnare una stringa sulla pagina. Usiamo una Font che abbiamo definito a livello di form.

private Font mDocFontInt = new Font("Arial", 12, FontStyle.Bold);
private Font mDocFont = new Font("Arial", 10);

Le due definizioni qui sopra sono all’inizio della form e definiscono i 2 font che useremo per la stampa, uno un po’ più grande per le intestazioni, uno un po’ più piccolo per i dati.

La sola cosa da notare nel codice della stampa dell’intestazione è che abbiamo spostato le coordinate di partenza di ogni elemento stampato sull’asse x e y in particolare, le stringhe sono tutte alla stessa altezza, startY rimane costante, mentre varia startX che è la posizione orizzontale (angolo superiore sinistro del rettangolo che contiene la stringa).

startY viene modificato invece per tracciare una riga orizzontale dopo i titoli.

e.HasMorePages = true;

Questa riga di codice è importante, se dobbiamo fare una stampa con più pagine, questo elemento è quello che indica al gestore delle stampe che ci sono ulteriori pagine da stampare, pertanto lo mettiamo a true (si ci sono altre pagine) in seguito, lo cambieremo se necessario quando saremo arrivati alla fine delle righe della tabella.

for (int x = 0; x < linesPerPage; x++)
{
if (mPrintRowIndex < mItemsSource.Rows.Count)
{

Il ciclo che stampa le righe nella pagina, usiamo il numero di righe calcolato per stabilire quante righe stampare sulla pagina, attenzione, questo è un esempio e il calcolo è approssimativo, se dovete fare dei report complessi, perdete un po’ di tempo a sperimentare come calcolare una altezza riga buona.

Notate che facciamo per prima cosa un controllo che ci dice se abbiamo finito di stampare tutte le righe della tabella. Lo facciamo prima per non dare errori se non ci sono righe.

DataRow row = mItemsSource.Rows[mPrintRowIndex];
mPrintRowIndex++;

graphics.DrawString(row["CompanyName"].ToString(), mDocFont, fColor, startX, startY);
startX += 400;
graphics.DrawString(row["Address"].ToString(), mDocFont, fColor, startX, startY);
startX += 400;
graphics.DrawString(row["City"].ToString(), mDocFont, fColor, startX, startY);
startX = 10;

startY += fontHeight + 3;

Se ci sono ancora righe da stampare, stampiamo una riga, anche in questo caso le posizioni orizzontali cambiano, quella verticale viene incrementata dopo aver stampato tutta la riga. Anche l’altezza dell’incremento in questo caso è stata calcolata in modo empirico guardando il risultato.

else
{
e.HasMorePages = false;
mPrintRowIndex = 0;
break;
}

Se non ci sono più righe da stampare, per prima cosa mettiamo a false l’indicatore fornito dagli Event Arguments, così che il gestore della stampa sia informato che è l’ultima pagina. Azzeriamo immediatamente anche il cursore di scorrimento delle righe, e poi, interrompiamo il ciclo, attenti che:

se non azzerate mPrintRowIndex sorprendentemente (ma non tanto) se cliccate il bottone di stampa dalla preview vi verrà stampata solo una pagina con l’intestazione perché? Perché la PrintPreviewDialog effettua una stampa per mostrare l’anteprima e il bottone Print effettua nuovamente la stampa, se mPrintRowIndex non viene azzerato qui, il cursore sarà già oltre il massimo numero di righe e non stamperà nulla.

Se non interrompiamo il ciclo invece faremmo un danno perché ricominceremo a stampare le righe dall’inizio.

Cos’altro c’è in questo progetto?

Un riepilogo per i pigri che non hanno letto l’articolo correlato:

  • Una DataTable che viene caricata con una query su SQL Server sul database Northwind e legge la tabella Customers del leggendario database di test Microsoft.
  • Una TextBox per scrivere del testo e attivare una ricerca con filtro sui dati della tabella
  • Un trucco da programmatori sgamati, permette di evitare che ad ogni pressione di un tasto venga rilanciata la query sul database quando l’utente scrive sulla textbox.

Cosa vediamo quando proviamo a stampare?

printdialog_01

La finestra di selezione stampante

printpreview_01

La preview della stampa, e se premiamo il tasto con la stampante nel mio caso la stampa viene effettuata su Foxit Pdf Reader e quindi.

pdf_01

Viene creato un pdf con il contenuto della stampa.

Potete scaricare il progetto esempio dal link qui indicato:

Per qualsiasi domanda, approfondimento, commento, o per segnalare un errore potete usare il link al modulo di contatto in cima alla pagina.