C# - Imprimindo em uma aplicação Windows Forms


Imprimir textos e gráficos é uma das muitas tarefas que um programador tem que realizar em uma aplicação Windows Forms. Enviar um texto para a impressora é muito simples , mas a plataforma .NET não fornece os recursos de impressão para tarefa mais complexas como configurar uma impressora ou uma página para impressão, definir margins e imprimir mais que uma cópia. Neste aspecto a Microsoft ficou devendo...

Neste artigo eu vou apresentar uma breve introdução sobre como efetuar a impressão com as classes da .NET Framework usando a linguagem C#. Para fornecer as funcionalidades básicas para imprimir em aplicações Windows Forms devemos estar familiarizados com a classe Graphics do namespace System.Drawing.

Para imprimir usamos o namespace System.Drawing.Printing onde a classe principal é a classe PrintDocument que representa um objeto que envia a saida para a impressora.

Para imprimir um texto ou um gráfico chamamos o método Print da classe PrintDocument e um dos eventos que o método Print invoca é o evento PrintPage. (Você precisa vincular um manipulador de evento a este evento e escrever o código para enviar a saída para a impressora. )

O manipulador de eventos irá receber um argumento do tipo PrintPageEventArgs contendo os dados relacionados ao evento PrintPage e uma das propriedades deste argumento é a propriedade Graphics a partir da qual você pode obter um objeto Graphics.

Este objeto Graphics representa uma página a ser impressa e para enviar uma string para a impressora, por exemplo, você pode usar o método DrawString da classe Graphics. Você pode chamar outros método desta classe como FillRectangle, DrawEllipse, etc.

Para ilustrar como imprimir usando os membros do namespce System.Drawing.Printing vamos usar um exemplo simples onde mostrarei como usar os recursos de impressão usando C#.

Criando o projeto Windows Forms - Editor de Textos básico

Vou usar SharpDevelop mas podemos usar também o Visual C# Express Edition e criar um novo projeto do tipo Windows Application com o nome imprimindoC#;

Esta aplicação simula um editor de textos básico  usando o controle RichTextBox para exibir o conteúdo de um arquivo e permite salvar alterações feitas, imprimir, configurar e visualizar impressão;

No formulário principal vamos definir o leiaute e incluir os componentes conforme demonstrado a seguir:

A partir da ToolBox seção Printing:
  • PrintDialog
  • PrintDocument
  • PrintPreviewDialog
A partir da ToolBox seção Windows Forms:
  • MenuStrip
  • OPenFileDialog
  • SaveFileDialog
  • RichTextBox

Os nomes dados aos itens do Menu são:

  • AbrirArquivo
  • Salvar
  • ConfigurarImpressao
  • Imprimir
  • VisualizarImpressao
  • Sair

Embora não seja o objetivo deste artigo vou exibir, sem entrar em detalhes, o código no evento click dos itens de menu Abrir e Salvar.

A rotina para abrir um arquivo colocada no evento Click do item Abrir do menu é exibida no código abaixo onde usamos um StreamReader para ler um arquivo a partir da opção de seleção do usuário usando um componente OpenFileDialog;

void AbrirArquivoClick(object sender, System.EventArgs e)
{
	  try
      {
        if (this.openFileDialog1.ShowDialog() == DialogResult.OK)
        {
          FileStream fs = new FileStream(openFileDialog1.FileName , FileMode.Open, FileAccess.Read);
          StreamReader m_streamReader = new StreamReader(fs); 
          // Lendo para o arquivo usando a classe StreamReader

          m_streamReader.BaseStream.Seek(0, SeekOrigin.Begin);

          // Lê cada linha do stream e efetua o parse até alcançar a última linha
          this.richTextBox1.Text = "";
          string linha = m_streamReader.ReadLine();
          while (linha != null)
          {
            this.richTextBox1.Text += linha + "\n";
           linha = m_streamReader.ReadLine();
          }
          //Fecha o Stream
          m_streamReader.Close();
        }   
      }
      catch(Exception em)
      {
      	 MessageBox.Show(" Erro : "+ em.Message.ToString()); 
      }
}

A rotina para salvar alterações feitas no arquivo é dada a seguir no evento Click do item de menu Salvar onde usamos um StreamWriter para salvar as alterações feitas usando um componente SaveFileDialog para escolher o caminho onde o arquivo será salvo;

void SalvarClick(object sender, EventArgs e)
{
     try
     {
        // Obtem o nome do arquivo para salvar
        if (this.saveFileDialog1.ShowDialog() == DialogResult.OK)
        { 
          //abre o stream para escrever e cria um StreamWriter para usar na implementacao do stream
          FileStream fs = new FileStream(@saveFileDialog1.FileName , FileMode.OpenOrCreate, FileAccess.Write);
          StreamWriter m_streamWriter = new StreamWriter(fs);
          m_streamWriter.Flush();

          // Escreve o artigo usando a classe StreamWriter
          m_streamWriter.BaseStream.Seek(0, SeekOrigin.Begin);

          // Escrever no controle rich edit control
          m_streamWriter.Write(this.richTextBox1.Text);

          // fecha o arquivo
          m_streamWriter.Flush();
          m_streamWriter.Close();
        }
      }
      catch(Exception em)
      {
      	 MessageBox.Show(" Erro : "+ em.Message.ToString()); 
      }
  }

Para imprimir o arquivo aberto vamos usar o evento Click do item de menu Imprimir usando o código a seguir:

void ImprimirClick(object sender, EventArgs e)
{
   printDialog1.Document = printDocument1;
   string texto = this.richTextBox1.Text;
   leitor = new StringReader(texto);
      		
   if (printDialog1.ShowDialog() == DialogResult.OK)
   {
      this.printDocument1.Print();
   }
}

Esta rotina atribui o compononte PrintDialog ao documento a ser impresso: printDialog1.Document = printDocument1;

O diálogo é exibir usando o componente ShowDialog de maneira que o usuário pode definir as suas opções de impressão e decidir se imprime ou não o documento: if (printDialog1.ShowDialog() == DialogResult.OK)

Depois que o método Print do componente PrintDocument é chamado - this.printDocument1.Print(); - a impressão é iniciada.

Um exemplo bem simples de como enviar um texto para a impressora é exibido no código abaixo:
void printDocument1_PrintPage(object sender, PrintPageEventArgs e) {
  Graphics g = e.Graphics;
  using( Font font = new Font("Lucida Console", 36) ) {
    g.DrawString("Um alo do Macoratti...,\n para todos", font, ...);
  }
}

Para concluir a impressão precisamos definir o código no evento PrintPage conforme mostro abaixo:

void PrintDocument1PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
       //variaveis usadas para definir as configurações da impressão
       float linhasPorPagina = 0;
      float yPosicao = 0;
      int contador = 0;
      float margemEsquerda = e.MarginBounds.Left;
      float margemSuperior = e.MarginBounds.Top;
      string linha = null;
      Font fonteImpressao = this.richTextBox1.Font;
      SolidBrush mCaneta = new SolidBrush(Color.Black);

      // Define o numero de linhas por pagina, usando MarginBounds.
      linhasPorPagina = e.MarginBounds.Height / fonteImpressao.GetHeight(e.Graphics);

      // Itera sobre a string usando StringReader, imprimindo cada linha
      while(contador < linhasPorPagina && ((linha=leitor.ReadLine()) != null)) 
      {
        // calcula a posicao da proxima linha baseada
        // na altura da fonte de acordo com o dispositivo de impressão
        yPosicao = margemSuperior + (contador * fonteImpressao.GetHeight(e.Graphics));

        // desenha a proxima linha no controle RichTextBox
        e.Graphics.DrawString(linha, fonteImpressao, mCaneta, margemEsquerda, yPosicao, new StringFormat());
        contador++;
      }
      // Verifica se existe mais linhas para imprimir
      if(linha != null)
        e.HasMorePages = true;
      else
        e.HasMorePages = false;
      
       //libera recursos		
        mCaneta.Dispose();
}

O evento PrintPage é automaticamente chamado pelo sistema para imprimir a página selecionada. O evento e do tipo PrintPageEventArgs contém o contexto do dispositivo e.Graphics usado para imprimir para a impressora.

A propriedade PageBounds da classe PrintPageEventArgs representa um retângulo da página inteira , já a propriedade MarginBounds representa um retângulo da área do interior das margens conforme mostra a figura ao lado.

Ambas as propriedades são usadas na unidade de 100 dpi, de maneira que um formato carta de 8.5 x 11 polegadas terá sempre um retângulo PageBounds de medida igual a {0, 0, 850, 1100}. Com uma margem padrão de 1 polegada , o retângulo MarginBounds terá as seguintes medidas {100, 100, 650, 900}.

A margem é útil porque muitas impressoras não podem imprimir a partir do início das bordas das páginas. Para evitar a perda de impressão nestes casos o objetop Graphics que você manuseia quando você imprime, inicia a impressão no top superior esquerdo da área de impressão da página.

Neste exemplo efetuamos a leitura de cada linha de texto do controle RichTextBox usando um StringReader que nos dá a habilidade de tratar uma string como um stream e tomar vantagem da função ReadLine para ler cada linha de texto no controle.

Podemos calcular a posição das linhas baseadas nas margens e na altura da fonte, e com o objetivo de prever a altura da fonte para a impressora nós passamos a ela o contexto do dispositivo da impressora na função GetHeight (printFont.GetHeight(ev.Graphics()), pois a altura da fonte na tela em pixels é diferente da fonte na impressora.

O atributo HasMorePages da classe PrintPageEventArg é continuamente verificado de maneira que o evento PrintPage é disparado se existirem mais linhas do que a página atual pode suportar.

O código para visualizar a impressão atribuido ao evento Click do item de menu Visualizar Impressão é dado abaixo:

void VisualizarImpressaoClick(object sender, EventArgs e)
{
	   try
      {
        string texto = this.richTextBox1.Text;
        leitor = new StringReader(texto);
        PrintPreviewDialog printPreviewDialog1 = new PrintPreviewDialog();
        printPreviewDialog1.Document = this.printDocument1 ;
        printPreviewDialog1.FormBorderStyle = FormBorderStyle.Fixed3D ;
        printPreviewDialog1.ShowDialog();
      }
      catch(Exception exp)
      {
        MessageBox.Show(" Erro : "+ exp.Message.ToString()); 
      }
}

O componente PrintPreview também dispara o evento PrintPage mas ao invés de direcionar para a impressora a saida é direcionada para a tela de visualização:

Vemos acima o resultado da visualização da impressão para a aplicação exemplo.

O controle PreviewPrintController , o qual é usado para visualizar um documento antes de sua impressão, é incialmente usado pelo controle PrintPreviewControl o qual mostra previamente uma página do documento. O controle PrintPreviewControl está disponível na Toolbos e usa os comandos de desenho realizados no evento PrintPage para exibir uma área de visualização da impressão.

No evento Click do item do menu Configurar Impressão temos o código que permite ao usuário configurar a impressão através da janela PrintDialog:

void ConfigurarImpressaoClick(object sender, EventArgs e)
{
       this.printDialog1.Document = this.printDocument1;
       printDialog1.ShowDialog();
}

Quando você cria uma instância de PrintDialog , as propriedades read/write são definidas para os valores iniciais os quais são:

Propriedade Valor Inicial
AllowSomePages false
AllowPrintToFile true
AllowSelection false
Document null (Nothing no VB)
PrinterSettings null (Nothing no VB)
PrintToFile false
ShowHelp false
ShowNetwork true

Para obter as configurações que são modificadas pelo usuário com o PrintDialog utilize a propriedade PrinterSettings.

Finalmente, e apenas para constar , abaixo temos o código para encerrar a aplicação associado ao evento Click do item de menu Sair:

void SairClick(object sender, EventArgs e)
{
   DialogResult result1 = MessageBox.Show("Confirma encerramento da aplicação?", "Encerrar?",MessageBoxButtons.YesNo);
   if (result1 == DialogResult.Yes )
   {
	 Application.Exit();
   }
}

E para encerrar um dica lega: Obtendo informação sobre as impressoras instaladas

Para isso basta ler os nomes na coleção InstalledPrinters da classe System.Drawing.Printing.PrinterSettings veja abaixo um exemplo de código:

Obs: Não esqueça de definir a declaração do namespace System.Drawing.Printing;

void ImpressorasInstaladasClick(object sender, EventArgs e)
{
     foreach (string printerName in PrinterSettings.InstalledPrinters)
      {
        // Exibe o nome da impressora
         Console.WriteLine("Impressora: {0}", printerName);
 
        // Retorna as configurações da impressora
        PrinterSettings printer = new PrinterSettings();
        printer.PrinterName = printerName;
 
        // Verifica se a impressora é válida
        if (printer.IsValid)
        {
          // Exibe a lista de resoluções válidas
          Console.WriteLine("Resoluções suportadas:");
 
          foreach (PrinterResolution resolution in printer.PrinterResolutions)
          {
            Console.WriteLine("  {0}", resolution);
          }
          Console.WriteLine();
 
          // Exibe uma lista de tamanho de papeis validos
           Console.WriteLine("Tamanho de papeis suportados:");
 
          foreach (PaperSize size in printer.PaperSizes)
          {
            if (Enum.IsDefined(size.Kind.GetType(), size.Kind))
            {
              Console.WriteLine("  {0}", size);
            }
          }
          Console.WriteLine();
        }
      }
      Console.ReadLine();
 }

E com isso dei uma visão geral sobre os aspectos mais importantes da impressão em WIndows Forms usando a linguagem C#.

Pegue o projeto completo aqui: impressaoCSharp.zip

Eu sei é apenas C# , mas eu gosto...

Referências:


José Carlos Macoratti