msdn05_capa.JPG

Clique aqui para ler todos os artigos desta edição

Visualizar e imprimir no Windows Forms App com o namespace de impressão do .NET

por Alex Calvo

A impressão é parte integral de todo e qualquer aplicativo completo baseado em Windows. Fornecer recursos de impressão robustos nesses aplicativos sempre foi uma tarefa entediante. Agora, imprimir do Windows Forms com o .NET Framework significa que você deve adotar uma abordagem document-centric, o que resulta em um código mais limpo e gerenciável. Enquanto o namespace System.Windows.Forms fornece integração ininterrupta com todas as caixas de diálogo de impressão padrão (como Visualizar Impressão, Configurar Página e Imprimir), o namespace System.Drawing.Printing oferece inúmeras classes que permitem extensibilidade e customização. Essas classes, bem como a forma como oferecem acesso aos recursos de impressão, são discutidas neste artigo. Outras técnicas úteis também são explicadas, como a impressão em segundo plano, que permite ao usuário prosseguir com outras tarefas.

Do ponto de vista do desenvolvimento, o Microsoft® .NET mudou em praticamente tudo. Algumas dessas mudanças, como Web Forms e ADO.NET, exigiram grandes alterações na forma como as coisas são feitas, enquanto outras tiveram natureza mais evolucionária, simplesmente melhorando tecnologias existentes (como o System.Xml). Para o desenvolvedor tradicional, que usa Visual Basic® e Visual C++®, a impressão no Windows® Forms representa uma grande mudança. Porém, como no caso de grande parte do .NET Framework, essa mudança é definitivamente um avanço.

É o fim da era do objeto Print do Visual Basic e de sua coleção Printers. No .NET Framework, não há objeto Print monolítico e nunca mais será necessário configurar propriedades CurrentX e CurrentY nem emitir comandos como EndDoc e NewPage. Se você está conhecendo o .NET a partir do Visual C++, provavelmente sabe que imprimir pode ser uma tarefa entediante. Ela requer, por exemplo, que você acompanhe cuidadosamente o processo de impressão usando a API Win32® para assegurar que as páginas sejam impressas corretamente. A mudança não significa que você não precisará mais fazer isso. Contudo, com o .NET, você terá uma lógica de impressão mais limpa e de manutenção muito mais fácil. As classes do .NET Framework permitem que seu código de impressão seja completamente encapsulado. Como é possível derivar seu código a partir de um conjunto de classes básicas, você pode obter todos os tipos de recursos extras sem custo adicional. Vincular uma caixa de diálogo do tipo Visualizar Impressão é tarefa trivial no .NET, só para citar um exemplo.

A maioria dos exemplos de códigos deste artigo vem do exemplo do aplicativo de impressão disponível para download. Este exemplo de aplicativo Windows Forms, mostrado na Figura 1, demonstra muitos dos novos recursos aos quais você terá acesso ao imprimir no .NET. Ele permite que você escolha qualquer documento de texto e envie-o à caixa de diálogo Visualizar Impressão ou a uma impressora específica. Para fins de demonstração, forneço a opção para selecionar a exibição de uma marca d’água em cada página (opção Display watermark na Figura 1).

 

image001.gif

Figura 1 Aplicativo Windows Forms

 

Quando imprimir usando a caixa de diálogo Visualizar Impressão, você pode ativar ou desativar o recurso interno de anti-aliasing, que processa texto e elementos gráficos na tela com uma aparência mais suave. Lembre-se, no entanto, de que isso afeta a velocidade da saída de impressão. Além disso, a caixa de diálogo Visualizar Impressão aproveita automaticamente qualquer suavização de fonte fornecida pelo Windows (ClearType), reduzindo, assim, a necessidade de usar o anti-aliasing. Ao enviar a saída para a impressora, o exemplo do aplicativo de impressão (disponível para download) também permite que você selecione várias outras opções. Assim, você poderá decidir se deseja ou não exibir uma caixa de diálogo de status, mostrar uma impressora animada na barra de status ou imprimir em uma thread em segundo plano.

Vamos fazer um tour pela impressão do Windows Forms examinando primeiro a maneira rápida e “suja” de enviar saída para a impressora. Depois, analisarei em detalhes a forma correta de imprimir no Windows Forms — usando uma classe derivada PrintDocument.

Usando a classe PrintDocument

No Windows Forms, o processo de impressão é document-centric e orientado por eventos. A maior parte do esforço será aplicada no uso de um objeto PrintDocument genérico ou na implementação de uma classe derivada PrintDocument. A melhor opção é herdar da classe PrintDocument básica — pelos motivos que explicarei em breve. Entretanto, às vezes pode ser mais rápido e simples usar uma instância da classe básica PrintDocument.

Imprimir com uma classe PrintDocument básica exige que você envie o evento PrintPage da classe para um método de manipulação (estático ou de instância) cuja assinatura corresponda ao delegate PrintPageEventHandler. Esse evento será disparado quando seu código chamar o método Print em uma instância de um objeto PrintDocument. Para desenhar sua página, use a propriedade Graphics do objeto PrintPageEventArgs. Uma instância de uma classe PrintPageEventArgs é passada como argumento ao manipulador de evento PrintPage. A propriedade Graphics do objeto PrintPageEventArgs expõe um objeto GDI+ que encapsula a superfície sobre a qual você desenhou a página. (Voltarei a falar sobre os comandos GDI+ básicos mais adiante.) Para imprimir mais de uma página, notifique o controlador de impressão subjacente de que há mais coisas para imprimir. Faça isso usando a propriedade HasMorePages do objeto PrintPageEventArgs. Definir a propriedade HasMorePages como true garantirá que o manipulador de evento PrintPage seja chamado novamente.

Além disso, você pode configurar manipuladores de evento para outros eventos de impressão comuns, como BeginPrint, EndPrint e QueryPageSettings. BeginPrint é um bom ponto de partida para inicializar os objetos (como Fonts) dos quais a rotina PrintPage depende. O evento QueryPageSettings é imediatamente disparado antes de cada evento PrintPage. Ele permite que você imprima cada página usando diferentes configurações, o que pode ser feito modificando a propriedade QueryPageSettingsEventArgs.PageSettings. Para modificar as configurações de página para o documento inteiro, você pode usar a propriedade DefaultPageSettings da classe PrintDocument.

Veja um exemplo que ilustra como iniciar um trabalho de impressão usando a classe PrintDocument básica:

 

PrintDocument printDoc = new PrintDocument();

printDoc.PrintPage += new PrintPageEventHandler(this.printDoc_PrintPage);

printDoc.Print();

 

// O evento PrintPage é disparado para cada página a ser impressa.

private void printDoc_PrintPage(object sender, PrintPageEventArgs e)

{

    // TODO: Imprimir a página usando o objeto e.Graphics GDI+

    // Avisa o PrintController se existem mais  páginas a serem impressas

    e.HasMorePages = false;

}

 

Como você pode ver, existem várias desvantagens nessa abordagem. A maior delas é ter que permanecer atento ao estado dos objetos entre as chamadas subseqüentes ao manipulador de evento PrintPage. Por exemplo, se você estiver imprimindo um documento de texto, precisará manter um objeto StreamReader aberto. Você poderia inicializar o StreamReader durante o evento BeginPrint e, depois, fechá-lo durante o evento EndPrint. Não importa a forma como é feito, a variável StreamReader precisará ser enxergada fora do manipulador de evento PrintPage junto com outras variáveis. Quando isso ocorre, seu código de impressão fica exposto e vulnerável e pode prejudicar o restante do código.

Implementando uma classe derivada PrintDocument

Quando imprimir a partir do Windows Forms, a melhor abordagem é implementar uma classe herdada da classe PrintDocument genérica. Assim, você colherá rapidamente os frutos do encapsulamento. Em vez de implementar manipuladores de evento BeginPrint, EndPrint e PrintPage, você sobrescreve os métodos OnBeginPrint, OnEndPrint e OnPrintPage da classe básica PrintDocument subjacente. Qualquer objeto usado pelo método OnPrintPage pode agora ser mantido em campos de classes privadas. Isso elimina completamente os possíveis problemas de código mencionados acima. Além disso, agora você está livre para adicionar propriedades, métodos, eventos e construtores personalizados à classe PrintDocument derivada.

O exemplo do aplicativo de impressão (disponível para download) usa uma classe PrintDocument derivada do tipo TextPrintDocument, conforme mostrado na Listagem 1. A classe TextPrintDocument expõe um construtor sobrecarregado (overloaded) que recebe um nome de arquivo como argumento. O nome de arquivo também pode ser definido e lido com uma propriedade FileToPrint personalizada. Essa propriedade gera uma exception quando definida para um arquivo inexistente. Essa classe também expõe um campo booleano (Boolean) público, chamado Watermark, que ativa ou desativa um elemento gráfico de plano de fundo na página. (Esse elemento gráfico é armazenado no assembly como um recurso incorporado [embedded] chamado Watermark.gif.) Finalmente, a classe derivada TextPrintDocument expõe uma propriedade Font que especifica a fonte correta a ser usada no processamento da página.

 

Listagem 1 Classe Derivada PrintDocument

using System;

using System.Drawing;

using System.Drawing.Printing;

using System.IO;

using System.Windows.Forms;

 

namespace PrintingDemo

{

    public class TextPrintDocument : PrintDocument

    {

        private Font printFont;

        private TextReader printStream;

        private string fileToPrint;

        private Bitmap imgWatermark;

 

        public bool Watermark = false;

 

        public TextPrintDocument()

        {

            imgWatermark = new Bitmap(GetType(), "Watermark.gif");

        }

 

        public TextPrintDocument(string fileName) : this()

        {

            this.FileToPrint = fileName;

        }

 

        public string FileToPrint

        {

            get

            {

                return fileToPrint;

            }

            set

            {

                if (File.Exists(value))

                {

                    fileToPrint = value;

                    this.DocumentName = value;

                }

                else

                    throw(new Exception("File not found."));

            }

        }

 

        public Font Font

        {

            get { return printFont; }

            set { printFont = value; }

        }

 

        protected override void OnBeginPrint(PrintEventArgs e)

        {

            base.OnBeginPrint(e);

            printFont = new Font("Verdana", 10);

            printStream = new StreamReader(fileToPrint);

        }

 

        protected override void OnEndPrint(PrintEventArgs e)

        {

            base.OnEndPrint(e);

            printFont.Dispose();

            printStream.Close();

        }

 

        protected override void OnPrintPage(PrintPageEventArgs e)

        {

            base.OnPrintPage(e);

 

            // Slow down printing for demo.

            System.Threading.Thread.Sleep(200);

 

            Graphics gdiPage = e.Graphics;

            float leftMargin = e.MarginBounds.Left;

            float topMargin = e.MarginBounds.Top;

            float lineHeight = printFont.GetHeight(gdiPage);

            float linesPerPage = e.MarginBounds.Height / lineHeight;

            int lineCount = 0;

            string lineText = null;

 

            // Watermark?

            if (this.Watermark)

            {

                int top = Math.Max(0,

                         (e.PageBounds.Height - imgWatermark.Height) / 2);

                int left = Math.Max(0,

                         (e.PageBounds.Width - imgWatermark.Width) / 2);

                gdiPage.DrawImage(imgWatermark, left, top,

                         imgWatermark.Width, imgWatermark.Height);

            }

               

            // Print each line of the file.

            while (lineCount < linesPerPage &&

                  ((lineText = printStream.ReadLine()) != null))

            {

                gdiPage.DrawString(lineText, printFont, Brushes.Black,

                leftMargin, (topMargin + (lineCount++ * lineHeight)));

            }

 

            // If more lines exist, print another page.

            if(lineText != null)

                e.HasMorePages = true;

            else

                e.HasMorePages = false;

        }

    }

}

 

A essência da classe TextPrintDocument pode ser encontrada no método OnPrintPage. É nela que você pinta a página usando a superfície de desenho GDI+ fornecida pela propriedade PrintPageEventArgs Graphics. Alem disso, o objeto PrintPageEventArgs contém as seguintes propriedades: Cancel, HasMorePages, MarginBounds, PageBounds e PageSettings. Cancel permite o cancelar o trabalho de impressão. A propriedade MarginBounds retorna um objeto Rectangle que representa a parte da página dentro das margens. Você pode usar esse retângulo para determinar onde iniciar e terminar a impressão em cada uma das páginas. PageBounds, por outro lado, representa a área total da página, incluindo as margens

Imprimindo com GDI+

As peculiaridades do GDI+ necessitariam de um artigo independente. Por isso, para o objetivo deste artigo, ao falar do GDI+, abordarei apenas a forma de processamento da classe TextPrintDocument em cada página. Em seguida, discutirei as chamadas GDI+ com mais chances de ocorrer durante um processo típico de impressão.

Primeiro, o método OnPrintPage determina a altura da fonte atual usando o método GetHeight da classe Font. O método GetHeight pode determinar os pontos por página (dpi – dots per inch) da página atual recebendo um objeto Graphics como argumento. Uma vez determinada a altura da fonte corrente, o número de linhas por página é calculado usando o MarginBounds.Height corrente. Em seguida, o arquivo de texto é lido, uma linha por vez, e impresso com o método DrawString. Se for alcançado o fim da página antes do fim do arquivo, a propriedade HasMorePages será definida como verdadeira.

Como você pode ver, é possível obter uma impressão básica usando simplesmente DrawString. O GDI+, no entanto, fornece mais de 15 métodos de desenho, cada um contendo várias sobrecargas (overloads). Você pode imprimir usando gráficos vetoriais (como DrawBezier, DrawEllipse) e gráficos raster1 (como DrawImage, DrawIcon). Observe como o método OnPrintPage usa o DrawImage para exibir a marca d’água. Você também pode aproveitar as vantagens dos vários recursos avançados do GDI+, como corte e mudanças de formas (clipping e transformations). Tente fazer isso com o objeto Print do Visual Basic!

A classe PrintController

Mencionei anteriormente como o exemplo do aplicativo de impressão (mostrado na Figura 1) permite exibir uma caixa de diálogo de status opcional e/ou um ícone animado na barra de status (do tipo que mostra uma página saindo da impressora). Ambos os recursos são implementados com as classes de controlador de impressão derivadas. PrintController é uma classe abstrata que é implementada por três classes concretas diferentes dentro do .NET Framework: StandardPrintController, PrintControllerWithStatusDialog e PreviewPrintController.

O controlador de impressão é responsável pelo modo de impressão do documento. A classe PrintDocument expõe seu controlador de impressão subjacente como uma propriedade. Chamar um método Print do documento de impressão dispara os métodos OnStartPrint, OnEndPrint, OnStartPage e OnEndPage do controlador de impressão subjacente. A Figura 2 mostra a seqüência de eventos que ocorrem entre o documento de impressão e o controlador de impressão. OnStartPage é o único método que retorna um valor. O valor de retorno é do tipo Graphics e, como você já deve ter percebido, é a superfície de desenho GDI+ que é passada ao documento de impressão através do argumento PrintPageEventArgs.

 

 

image002.gif 

Figura 2 Fluxo de impressão

 

O controlador de impressão padrão é do tipo PrintControllerWithStatusDialog. Por isso, se desejar desativar a caixa de diálogo de status de impressão, você precisará usar StandardPrintController. PreviewPrintController é usado pelas classes PrintPreviewDialog e PrintPreviewControl. PrintControllerWithStatusDialog pode se encontrado no namespace System.Windows.Forms, enquanto StandardPrintController e PreviewPrintController estão localizados sob o namespace System.Drawing.Printing. PrintControllerWithStatusDialog fornece um construtor sobrecarregado que recebe outro controlador de impressão como argumento. Isso permite que você combine o PrintControllerWithStatusDialog com qualquer outro recurso que deseje adicionar ao seu próprio controlador de impressão. Ao executar o exemplo do aplicativo de impressão, tente marcar ambas as caixas de seleção PrintControllerWithStatusDialog e PrintControllerWithStatusBar para ver o que acontece. Veja a seguir um breve exemplo de como o código funciona:

 

 CustomPrintDocument printDoc = new

    CustomPrintDocument();

 CustomPrintController printCtl = new

    CustomPrintController();

 printDoc.PrintController = new

    PrintControllerWithStatusDialog(

       printCtl, "Dialog Caption");

 printDoc.Print();

 

O exemplo do aplicativo de impressão usa um controlador de impressão personalizado do tipo PrintControllerWithStatusBar (veja a Listagem 2). PrintControllerWithStatusBar expõe uma propriedade StatusBarPanel que determina se o painel da barra de status exibirá o ícone animado de impressora. Usei um timer do tipo System.Timers.Timer para fazer a animação. A classe System.Timers.Timer funciona muito bem em um aplicativo multithread, como no caso da impressão em segundo plano.

 

Listagem 2 PrintControllerWithStatusBar

 

using System;

using System.Drawing;

using System.Drawing.Printing;

using System.Timers;

using System.Windows.Forms;

 

namespace PrintingDemo

{

    public class PrintControllerWithStatusBar : StandardPrintController

    {

        private int iCurrentPage;

        private int iCurrentIconFrame;

        private string strPriorStatus;

        private Icon icoPriorIcon;

        private Icon[] icoPrinters;

        private StatusBarPanel statusBarPanel;

        private System.Timers.Timer tmrIconAnimation;

 

        public bool ShowPrinterIcon = false;

           

        public PrintControllerWithStatusBar()

        {

            Icon icoPrinter1 = new Icon(GetType(), "Printer1.ico");

            Icon icoPrinter2 = new Icon(GetType(), "Printer2.ico");

            Icon icoPrinter3 = new Icon(GetType(), "Printer3.ico");

               

            icoPrinters = new Icon[3];

            icoPrinters[0] = icoPrinter1;

            icoPrinters[1] = icoPrinter2;

            icoPrinters[2] = icoPrinter3;

 

            tmrIconAnimation = new System.Timers.Timer();

            tmrIconAnimation.Enabled = false;

            tmrIconAnimation.Interval = 200;

            tmrIconAnimation.Elapsed += new

                             ElapsedEventHandler(tmrIconAnimation_Elapsed);

        }

 

        public PrintControllerWithStatusBar(StatusBarPanel sbp) : this()

        {

            statusBarPanel = sbp;

        }

 

        private void tmrIconAnimation_Elapsed(object sender,

                                              ElapsedEventArgs e)

        {

            if (statusBarPanel != null)

            {

                // Animate printer icon...

                statusBarPanel.Icon = icoPrinters[iCurrentIconFrame++];

                if (iCurrentIconFrame > 2)

                    iCurrentIconFrame = 0;

            }

        }

           

        public StatusBarPanel StatusBarPanel

        {

            get { return statusBarPanel; }

            set { statusBarPanel = value; }

        }

 

        public override void OnStartPrint(PrintDocument printDoc,

                                          PrintEventArgs e)

        {

            iCurrentPage = 1;

            iCurrentIconFrame = 0;

            if (statusBarPanel != null)

            {

                // Save prior settings...

                strPriorStatus = statusBarPanel.Text;

                icoPriorIcon = statusBarPanel.Icon;

                statusBarPanel.Text = "Printing...";

            }

            if (this.ShowPrinterIcon)

            {

                tmrIconAnimation.Start();

            }

            base.OnStartPrint(printDoc, e);

        }

 

        public override Graphics OnStartPage(PrintDocument printDoc,

                                             PrintPageEventArgs e)

        {

            if (statusBarPanel != null)

            {

                statusBarPanel.Text = "Printing page " + iCurrentPage++;

            }

            return base.OnStartPage(printDoc, e);

        }

 

        public override void OnEndPage(PrintDocument printDoc,

                                       PrintPageEventArgs e)

        {

            base.OnEndPage(printDoc, e);

        }

 

        public override void OnEndPrint(PrintDocument printDoc,

                                        PrintEventArgs e)

        {

            tmrIconAnimation.Stop();

 

            // DoEvents is required here, when not printing in a

            // background thread. It allows any pending messages to be

            // processed prior to restoring the StatusBar back to its

            // original settings.

            Application.DoEvents();

 

            if (statusBarPanel != null)

            {

                // Restore original panel settings...

                statusBarPanel.Icon = icoPriorIcon;

                statusBarPanel.Text = strPriorStatus;

            }

            base.OnEndPrint(printDoc, e);

        }

    }

}

 

Usando as caixas de diálogo de impressão

A beleza da impressão no .NET reside na forma elegante como o documento de impressão se encaixa às caixas de diálogo de impressão. Existem três caixas de diálogo diferentes no namespace System.Windows.Forms: PrintDialog, PrintPreviewDialog e PageSetupDialog. Além disso, existe uma classe PrintPreviewControl que não inclui a caixa de diálogo ao redor, fornecendo uma maior flexibilidade de design da UI. Para simplificar, o exemplo do aplicativo de impressão usa a classe PrintPreviewDialog (veja a Figura 3).

 

 

image003.gif 

Figura 3 Visualizar Impressão

 

A classe PrintDialog pode ser usada para exibir a caixa de diálogo padrão de impressão no Windows e permite que um usuário selecione uma impressora, especifique as páginas a serem impressas e determine o número de cópias. É fácil utilizá-la:

 

CustomPrintDocument printDoc = new CustomPrintDocument();

PrintDialog dlgPrint = new PrintDialog();

dlgPrint.Document = printDoc;

if (dlgPrint.ShowDialog() == DialogResult.OK)

{

    printDoc.Print();

}

 

A utilização da classe PrintPreviewDialog é ainda mais fácil. A caixa de diálogo Visualizar Impressão é muito útil durante o processo de desenvolvimento. Ela pode economizar papel durante a depuração dos documentos de impressão. Veja como ela funciona:

 

CustomPrintDocument printDoc = new CustomPrintDocument();

PrintPreviewDialog dlgPrintPreview = new PrintPreviewDialog();

// Defina aqui qualquer propriedade opcional do dlgPrintPreview

dlgPrintPreview.Document = printDoc;

dlgPrintPreview.ShowDialog();

 

Depois de criar uma instância da classe PrintPreviewDialog, você define sua propriedade Document para uma instância de qualquer classe que derive de PrintDocument. O método ShowDialog de PrintPreviewClass cuidará automaticamente do processamento da visualização de impressão do documento (veja a Figura 3). Como você sabe, esse é o máximo de simplificação possível.

A classe PageSetupDialog funciona de forma semelhante. No entanto, além de aceitar a propriedade Document, você pode optar por definir a propriedade PageSettings ou PrinterSettings para uma instância da classe PageSettings ou PrinterSettings. A classe PageSettings define as configurações que se aplicam à página impressa, como Margins e PaperSize, enquanto a classe PrinterSettings especifica informações sobre como o documento é impresso, como FromPage, ToPage e PrinterName. A classe PrinterSettings também permite que você obtenha uma lista de impressoras disponíveis usando o método estático InstalledPrinters, que retorna um objeto PrinterSettings.StringCollection. Ao retornar da caixa de diálogo Configurar Página, os objetos Document, PageSettings ou PrinterSettings serão modificados de acordo.

 

Impressão em segundo plano

O threading é um recurso complexo, e não pretendo negar a possibilidade de problemas durante a criação de um aplicativo multithread. A verdade é que o uso de uma background thread realmente melhora a experiência de impressão para os usuários. Ela permite que os usuários continuem usando o resto do aplicativo enquanto o trabalho de impressão está sendo processado em segundo plano. Para experimentar a impressão em segundo plano, é recomendável usar um documento grande e assegurar que a impressora esteja pausada. Por que desperdiçar papel? Você também pode reduzir a velocidade de impressão usando o método estático Sleep da classe Thread. O exemplo do aplicativo de impressão permite que você tente isso ao enviar a saída para a impressora. Não faz sentido usar a impressão em segundo plano com a caixa de diálogo Visualizar Impressão. Resumindo, é assim que ela funciona:

 

private void cmdBackgroundPrint_Click(object sender, System.EventArgs e)

{

    Thread t = new Thread(new ThreadStart(PrintInBackground));

    t.IsBackground = true;

    t.Start();

}

 

private void PrintInBackground()

{

    printDoc.Print();

}

 

Você também pode usar um delegate para realizar a mesma tarefa. Inicialmente, ao escrever o exemplo do aplicativo de impressão, utilizei a classe Thread. No entanto, como quis ser informado da conclusão da impressão, decidi usar um delegate. O método BeginInvoke do delegate permite que você especifique uma função callback para que seu código possa ser notificado quando uma operação assíncrona for concluída. Precisei fazer isso para reativar o botão Imprimir em uma thread-safe após a impressão (veja a Listagem 3).

 

Listagem 3 Código de exemplo de impressão da aplicação

using System;

using System.Data;

using System.Drawing;

using System.Drawing.Printing;

using System.ComponentModel;

using System.Threading;

using System.Windows.Forms;

 

namespace PrintingDemo

{

  public class PrintDialog : System.Windows.Forms.Form

  {

    private System.Windows.Forms.Button cmdPrint;

    private System.Windows.Forms.Label lblFile;

    private System.Windows.Forms.TextBox txtFileName;

    private System.Windows.Forms.Button cmdBrowse;

    private System.Windows.Forms.OpenFileDialog dlgOpenFile;

    private System.Windows.Forms.PrintPreviewDialog dlgPrintPreview;

    private System.ComponentModel.Container components = null;

 

    private System.Windows.Forms.PrintDialog dlgPrint;

    private System.Windows.Forms.CheckBox chkBackgroundThread;

    private System.Windows.Forms.RadioButton optPrinter;

    private System.Windows.Forms.RadioButton optPrintPreview;

 

    private System.Windows.Forms.Button cmdPageSettings;

    private System.Windows.Forms.PageSetupDialog dlgPageSettings;

    private System.Windows.Forms.CheckBox chkWatermark;

    private System.Windows.Forms.GroupBox fraSettings;

    private System.Windows.Forms.CheckBox chkAntiAlias;

    private System.Windows.Forms.StatusBar sbStatus;

    private System.Windows.Forms.StatusBarPanel simpleTextPanel;

    private System.Windows.Forms.CheckBox chkPrintControllerWithStatusBar;

    private System.Windows.Forms.CheckBox

        chkPrintControllerWithStatusDialog;

    private TextPrintDocument printDoc = new TextPrintDocument();

 

    delegate void PrintInBackgroundDelegate();

 

    public PrintDialog()

    {

      InitializeComponent();

      dlgPrintPreview.Icon = this.Icon;

    }

 

    protected override void Dispose( bool disposing )

    {

      if( disposing )

      {

        if (components != null)

        {

          components.Dispose();

        }

      }

      base.Dispose( disposing );

    }

 

    #region Windows Form Designer generated code

 

    [STAThread]

    static void Main()

    {

      Application.Run(new PrintDialog());

    }

 

    private void cmdBrowse_Click(object sender, System.EventArgs e)

    {

      if (dlgOpenFile.ShowDialog(this) == DialogResult.OK)

          txtFileName.Text = dlgOpenFile.FileName.ToString();

    }

 

    private void optPrintPreview_CheckedChanged(object sender,

                                                System.EventArgs e)

    {

      EnableDisableCheckBoxes();

    }

 

    private void optPrinter_CheckedChanged(object sender,

                                           System.EventArgs e)

    {

      EnableDisableCheckBoxes();

    }

 

    private void EnableDisableCheckBoxes()

    {

      chkAntiAlias.Enabled = optPrintPreview.Checked;

      chkPrintControllerWithStatusDialog.Enabled = optPrinter.Checked;

      chkPrintControllerWithStatusBar.Enabled = optPrinter.Checked;

      chkBackgroundThread.Enabled = optPrinter.Checked;

    }

 

    private void cmdPageSettings_Click(object sender, System.EventArgs e)

    {

      dlgPageSettings.Document = this.printDoc;

      dlgPageSettings.ShowDialog(this);

    }

 

    private void cmdPrint_Click(object sender, System.EventArgs e)

    {

      try

      {

        printDoc.FileToPrint = txtFileName.Text;

        printDoc.Watermark = chkWatermark.Checked;

 

        if (optPrintPreview.Checked)

        {

          dlgPrintPreview.UseAntiAlias = chkAntiAlias.Checked;

          dlgPrintPreview.Document = printDoc;

          dlgPrintPreview.ShowDialog(this);

        }

        else if (optPrinter.Checked)

        {

          dlgPrint.Document = printDoc;

 

          if (dlgPrint.ShowDialog(this) == DialogResult.OK)

          {

            this.Refresh();

 

            PrintController printController;

 

            if (chkPrintControllerWithStatusBar.Checked)

            {

              printController = new PrintControllerWithStatusBar();

              ((PrintControllerWithStatusBar) printController)

                  .StatusBarPanel = sbStatus.Panels[0];

              ((PrintControllerWithStatusBar) printController)

                  .ShowPrinterIcon = true;

            }

            else

              printController = new StandardPrintController();

           

            if (chkPrintControllerWithStatusDialog.Checked)

              printDoc.PrintController = new

                  PrintControllerWithStatusDialog(

                  printController, "Please wait...");

            else

              printDoc.PrintController = printController;

 

            if (chkBackgroundThread.Checked)

            {

              cmdPrint.Enabled = false;

              cmdPageSettings.Enabled = false;

              fraSettings.Enabled = false;

 

              PrintInBackgroundDelegate d = new

                  PrintInBackgroundDelegate(PrintInBackground);

              d.BeginInvoke(new AsyncCallback(PrintInBackgroundComplete),

                            null);

            }

            else

            {

              this.Cursor = Cursors.WaitCursor;

              printDoc.Print();

              this.Cursor = Cursors.Default;

            }

          }

        }

      }

      catch (Exception ex)

      {

        MessageBox.Show(ex.Message, Application.ProductName,

                        MessageBoxButtons.OK, MessageBoxIcon.Error);

      }

    }

 

    private void PrintInBackground()

    {

      try

      {

        printDoc.Print();

      }

      catch (Exception e)

      {

        MessageBox.Show(e.Message, Application.ProductName,

                        MessageBoxButtons.OK, MessageBoxIcon.Error);

      }

    }

 

    private void PrintInBackgroundComplete(IAsyncResult r)

    {

      cmdPrint.Enabled = true;

      cmdPageSettings.Enabled = true;

      fraSettings.Enabled = true;

    }

  }

}

 

A Listagem 3 contém a maior parte do código usado para orientar o exemplo do aplicativo de impressão. Especificamente, ele contém um delegate chamado PrintInBackgroundDelegate. O método BeginInvoke do delegate será chamado dentro do manipulador de evento Click do botão Imprimir se a caixa de seleção do Background thread estiver marcada. Caso contrário, o método Print do documento de impressão será chamado diretamente do UI thread principal.

Algumas armadilhas que você talvez encontre provavelmente envolverão objetos state-aware dos quais a classe do documento de impressão pode depender. Neste caso, usando o exemplo anterior, o que acontecerá se o usuário clicar em cmdBackgroundPrint mais de uma vez? Os resultados seriam imprevisíveis, na melhor das hipóteses. Como o evento PrintDocument.PrintPage geralmente depende de campos state-aware de classe, tudo pode ficar confuso nesse caso. Uma maneira simples de lidar com esses tipos de problemas é simplesmente desativar alguns controles da UI para evitar que o usuário use o mesmo comando mais de uma vez. Nesse meio tempo, eles podem continuar usando o resto do aplicativo. Essa é a abordagem utilizada pelo exemplo do aplicativo de impressão.

 

Conclusão

Embora este artigo tenha abordado a impressão nativa do Windows Forms no .NET, alguns outros métodos de impressão estão disponíveis para .NET, como o Crystal Reports. Em algumas situações, uma ferramenta de relatório pode ser mais adequada do que a impressão nativa. No entanto, se você não quiser pagar para atualizar um produto comercial e seu aplicativo exigir recursos de impressão personalizados ou proprietários – como aqueles encontrados em vários aplicativos desktop baseados no Windows – a impressão nativa é definitivamente a escolha mais adequada.

 

1 Gráfico raster é um método de desenho relacionada aos pixels que compõem a imagem.