Imprimindo com C# - Introdução ao PrintDocument

Não é raro onde precisamos imprimir relatórios ou até uma impressão "padrão" de um grid em nossas aplicações. Para isso podemos usar o PrintDocument, que é uma classe para esta finalidade: impressão.

Em muitos casos o uso deste recurso não é viável ou inteligente, assim vejo utilizade no uso deste recurso somente quando trabalhamos com dados dinâmicos como um grid qualquer. Quando as coisas ficam mais "fixas", como imprimir um pedido de venda ou orçamentos por exemplo, podemos usar a ferramenta padrão de relatórios que o Visual Studio oferece, mas isso fica para um próximo post.

O PrintDocument

Esta classe representa nosso documento que será impresso, mas devemos codificar o que queremos que seja impresso. Quero mostrar quatro eventos importantes do PrintDocument: BeginPrint, QueryPageSettings, PrintPage e EndPrint.

Os eventos BeginPrint e EndPrint são executados uma única vez, uma no início da impressão e outra ao final respectivamente. Estes eventos são úteis quando precisamos definir fontes, carregar recursos e até descarregar os recursos usados ao final da impressão.

Já os eventos QueryPageSettings e PrintPage são mais executados pois para cada página, cada um dos dois é executado, e sempre será na ordem: QueryPageSettings e depois PrintPage. No evento QueryPageSettings podemos fazer coisas como calcular quantas linhas caberão na página, etc (sua imaginação é boa, eu confio).

Imprimindo e pré-visualizando

Existem duas formas de usar o PrintDocument: através do PringDialog e do PrintPreviewDialog. O primeiro exibe uma janela que realmente imprime seu documento, enquanto com o segundo podemos conferir a impressão rapidamente.

Para vermos os Dialogs em ação, vamos ver alguns exemplos de como chamá-los:

PrintViewDialog

PrintPreviewDialog previewDialog = new PrintPreviewDialog();
previewDialog.Document = new PrintDocument();
previewDialog.ShowDialog();```

**_PrintDialog_**
```csharpPrintDocument documento = new PrintDocument();

PrintDialog dialog = new PrintDialog();
dialog.Document = documento;
if (dialog.ShowDialog() == DialogResult.OK)
{
documento.Print();
}

Mão na massa

Agora vamos ver um pouco do PrintDocument. Mas antes, adicione a referência ao System.Drawing em seu projeto.

Vamos começar pelo BeginPrint e EndPrint. Primeiro vamos instanciar o PrintDocument e vincular os eventos:

class Program
{
static void Main(string[] args)
{
PrintDocument documento = new PrintDocument();
documento.BeginPrint += documento_BeginPrint;
documento.EndPrint += documento_EndPrint;
}

static void documento_BeginPrint(object sender, PrintEventArgs e)
{
}

static void documento_EndPrint(object sender, PrintEventArgs e)
{
}
}

Você pode notar que ambos eventos recebem o PrintEventArgs como parâmetro. Porém o PrintEventArgs nos disponibiliza apenas duas coisas úteis: as propriedades Cancel e PrintAction.

Na propriedade Cancel é boleana e podemos definir o valor true caso seja necessário cancelar a impressão. Já a PrintAction é um enum que nos informa como será feita a impressão: em arquivo, pré-visualização ou na impressora. Veja a definição:

#region Assembly System.Drawing.dll, v4.0.0.0
// C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.5System.Drawing.dll
#endregion

using System;

namespace System.Drawing.Printing
{
// Summary:
// Specifies the type of print operation occurring.
public enum PrintAction
{
// Summary:
// The print operation is printing to a file.
PrintToFile = 0,
//
// Summary:
// The print operation is a print preview.
PrintToPreview = 1,
//
// Summary:
// The print operation is printing to a printer.
PrintToPrinter = 2,
}
}

Vamos definir uma fonte bem bonita para o documento e vamos liberá-la ao final da impressão:

class Program
{
private static Font fonte;

static void Main(string[] args)
{
PrintDocument documento = new PrintDocument();
documento.BeginPrint += documento_BeginPrint;
documento.EndPrint += documento_EndPrint;
}

static void documento_BeginPrint(object sender, PrintEventArgs e)
{
fonte = new Font("Comic Sans", 12);
}

static void documento_EndPrint(object sender, PrintEventArgs e)
{
fonte.Dispose();
}
}

Não vou mostrar o QueryPageSettings neste post, deixo para você este trabalho de casa. Mas vamos usar o PrintPage:

class Program
{
private static Font fonte;

static void Main(string[] args)
{
PrintDocument documento = new PrintDocument();
documento.BeginPrint += documento_BeginPrint;
documento.EndPrint += documento_EndPrint;
documento.PrintPage += documento_PrintPage;
}

static void documento_BeginPrint(object sender, PrintEventArgs e)
{
fonte = new Font("Comic Sans", 12);
}

static void documento_EndPrint(object sender, PrintEventArgs e)
{
fonte.Dispose();
}

private static void documento_PrintPage(object sender, PrintPageEventArgs e)
{
}
}

Como pode ver, o evento PrintPage tem um PrintPageEventArgs como parâmetro, algumas propriedades interessantes são:

Todas as operações que faremos deve ser feita na propriedade Graphics do PrintPageEventArgs, pois através do Graphics podemos desenhar textos, formas geométricas e até imagens.

Uma coisa interessante do Graphics em relação aos textos, é que podemos calcular o tamanho de um texto em determinada fonte através do método Measurestring, isso é útil para calcularmos se o texto desejado caberá na folha, etc.

Vamos escrever "Relatório do João" centralizado no topo da página:

private static void documento_PrintPage(object sender, PrintPageEventArgs e)
{
string titulo = "Relatório do João";

SizeF tamanho = e.Graphics.MeasureString(titulo, fonte);

float posicao = (e.PageBounds.Width / 2) - (tamanho.Width / 2);

e.Graphics.DrawString(titulo, fonte, new SolidBrush(Color.Black), posicao, 0.0f);
}

O método usado para escrever um texto foi o DrawString. Ele recebe o texto, uma fonte, um pincel (tradução livre rs) e a posição no eixo X e Y.

Para podermos ver o resultado vamos usar o PrintPreviewDialog, não se esqueça de referenciar o namespace System.Windows.Forms no seu projeto.

static void Main(string[] args)
{
PrintDocument documento = new PrintDocument();
documento.BeginPrint += documento_BeginPrint;
documento.EndPrint += documento_EndPrint;
documento.PrintPage += documento_PrintPage;

PrintPreviewDialog dialog = new PrintPreviewDialog();
dialog.Document = documento;
dialog.ShowDialog();
}

Vamos melhorar um pouco o nosso título para que ele fique nas margens do documento:

private static void documento_PrintPage(object sender, PrintPageEventArgs e)
{
string titulo = "Relatório do João";

SizeF tamanho = e.Graphics.MeasureString(titulo, fonte);

float posicao = (e.MarginBounds.Width / 2) - (tamanho.Width / 2);

e.Graphics.DrawString(titulo, fonte, new SolidBrush(Color.Black), e.MarginBounds.Left + posicao, e.MarginBounds.Top);
}

Bem melhor não?

Conclusão

Basicamente isto é o PrintDocument, não vejo muito mais o que explicar para uma introdução. Uma sugestão é você experimentar o uso da propriedade HasMorePages dentro do evento PrintPage, mas tome cuidado para não executar o evento infinitamente.

Se desejar baixar o projeto, deixei um repositório no GitHub.

Até mais.