 VB .NET - Impressão usando o System.Drawing.Printing
VB .NET - Impressão usando o System.Drawing.Printing
Uma das grandes lacunas existentes no VB é impressão. Com a versão do VB5 e VB6 tínhamos as seguintes opções para gerar relatórios e imprimir documentos:
A respeito deste panorama sombrio com relação a impressão no VB eu tenho duas notícias: uma boa e uma ruim. A ruim é que o VB .NET não resolveu todos os problemas existentes , e a boa é que ficou muito mais fácil imprimir documentos e gerar relatórios no VB .NET.
Neste artigo vou procurar mostrar como podemos usar o namespace System.Drawing.Printing , o sucessor do objeto Printer do VB , para criar relatórios usando código VB.NET.
Para começar vamos dar uma olhada nas principais classes para impressão .
PrintDocument :
- é usada para enviar a saida para uma impressora.
- Você instancia um PrintDocument , define algumas propriedades descrevendo o que deseja imprimir e chama o método Print.
- Esta classe dispara o evento PrintPage para cada página a ser impressa.
- Você geralmente inclui o seu código para impressão para um gerenciador de evento para este evento.
PrinterSettings : Fornece informações sobre como um documento será impresso.
PageSettings : Fornece informações sobre como uma página será impressa.
PrintPageEventArgs : Dados para o evento PrintPage de PrintDocument.
PrintEventArgs : Dados para os eventos BeginPrint e EndPrint em um PrintDocument. Permite cancelar o trabalho de impressão.
PrintPreviewControl : Um controle que exibe um PrintDocument. Permite criar um diálogo de visualizar a impressão.
PrintPreviewDialog : Diálogo que exibe um PrintDocument usando o PrintPreviewControl.
PageSetupDialog : Diálogo das propriedades da página.
PrintController : Controla um PrintDocument que é impresso. Temos dois PrintController
- DefaultPrintController - renderiza para a impressora
- PreviewPrintController - renderiza para o PrintPreviewControl.
Basicamente a lógica para uma impressão até feita usando os eventos de PrintDocument . Quando o método PrintDocument.Print() é chamado temos a seguinte sequência de eventos:
O tipo de argumentos do evento PrintPage possui a propriedade HasMorePages. Se ela for definida como True quando o evento retornar , PrintDocument define uma nova página e levanta o evento PrintPage novamente. Desta forma a lógica do seu código para impressão no evento PrintPage deverá ser basicamente a seguinte :
Da teoria para a prática
Vou mostrar como podemos criar relatórios via código usando o System.Drawing.Printing usando como exemplo a criação de um relatório que exibe os dados contidos em uma tabela de um banco de dados. Iremos portanto realizar as seguintes tarefas:
Para não termos muito trabalho vou escolher uma tabela de um banco de dados já existente. O escolhido é o já famoso saco de pancadas Northwind.mdb . Iremos acessar a tabela produtos deste banco de dados e exibir em um relatório os campos : Código do Produto, Nome do Produto , Preço Unitário
A estrutura da tabela produtos do banco de dados Northwind.mdb é a seguinte :

O layout do relatório deverá ser algo parecido com :
|  | 
O acesso aos dados
Como iremos montar um relatório que vai exibir os dados da tabela Pedidos teremos que criar uma conexão e extrair os dados que desejemos exibir via instrução SQL.
Como iremos acessar uma base de dados Access irei usar o namespace System.Data.OleDb e o provedor "Provider=Microsoft.Jet.OLEDB.4.0;".
Vamos utilizar um objeto DataReader para obter as informações da tabela via objeto OleDbCommand.
Conforme já mostrei no artigo - ADO.NET - Uma visão geral I : Objetos Connection , Command e DataReader - o código para conexão com uma base de dados Access em c:\teste\northwind.mdb usando um DataReader pode ser expresso assim :
| Dim conn As New OleDbConnection() conn.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\teste\northwind.mdb" Dim cmd As OleDbCommand = conn.CreateCommand cmd.CommandText = "Select CódigoDoProduto,NomeDoProduto, PreçoUnitário from Produtos" conn.Open() Dim leitor As OleDbDataReader = cmd.ExecuteReader() Try
    While leitor.Read()
                'código para visualizar o relatório
    End While
    leitor.Close()
    conn.Close()
Catch erro As Exception
        MsgBox("Erro " & vbCrLf & erro.ToString, MsgBoxStyle.Critical, "Erro")
End Try
End Sub | 
Apenas para recordar temos que :
O objeto OleDbCommand representa uma instrução SQL que será executada para extrair os dados da tabela Pedidos
O método ExecuteReader() retorna um conjunto de registros de somente leitura
O objeto OleDbDataReader precisa de uma conexão ativa para ser criado (podemos compará-lo ao antigo Recordset de somente leitura)
Você precisa fechar o objeto OleDbDataReader para poder liberar a conexão que ele esta usando.
O objeto DataReader é alimentado via método ExecuteReader do objeto Command.
Para percorrer todos os registros que foram extraídos usamos o método Read()
No interior loop While/End While iremos é que vamos colocar o código que permite a visualização da impressão pois e no interior do loop que estaremos lendo os registros da tabela Produtos. Devemos fazer o seguinte:
Nota: Para saber mais leia o artigo : Impressão e Visualização. O que há de novo ?
O código deverá ficar assim :
| While drProdutos.Read             'cria um novo documento para impressão
            Dim pd As PrintDocument = New PrintDocument()           'relaciona o objeto pd ao procedimento rptProdutos
            AddHandler pd.PrintPage, AddressOf Me.rptProdutos'cria uma nova instância do objeto PrintPreviewDialog() objPrintPreview = New PrintPreviewDialog() 'define algumas propriedades do obejto With objPrintPreview 'indica qual o documento vai ser visualizado .Document = pd .WindowState = FormWindowState.Maximized .PrintPreviewControl.Zoom = 1 'maxima a visualização .Text = "Catálogo de Produtos" 'exibe a janela de visualização para o usuário .ShowDialog() End With End While | 
A única novidade seria o código que relaciona o objeto PrintDocument com o evento rptProdutos :
AddHandler pd.PrintPage, AddressOf Me.rptProdutos
Aqui estamos usando a declaração AddHandler e o operador AddressOf .
Neste caso estou associando o procedimento rptProdutos ao evento pd.PrintPage , ou seja , o procedimento rptProdutos irá os dados enviados pelo objeto DataReader e gerará o relatório formatado para visualização/impressão.
Agora só falta definir o código do procedimento rptProdutos. Embora seja um pouco trabalhoso o código é simples. Vamos a ele:
1- Primeiro vamos definir o procedimento que irá receber os dados do objeto DataReader sua declaração é a seguinte:
 Private Sub rptProdutos(ByVal 
sender As Object, ByVal Relatorio As System.Drawing.Printing.PrintPageEventArgs)
O procedimento possui um argumento do tipo PrintPageEventArgs chamado de
Relatorio que iremos usar para definir a 
formatação do relatório através de cabeçalho, rodapé , 
título , fontes , linhas , imagens , texto, etc.
Antes de prosseguir vamos assumir que: Estamos imprimindo na impressora padrão. Agora vamos definir os itens que irão formatar o nosso relatório:
- Margens : vamos assumir as margens definidas pela impressora padrão para isto vamos usar a propriedade MarginBounds do objeto
Dim margemEsq As Single = Relatorio.MarginBounds.LeftDim margemSup As Single = Relatorio.MarginBounds.Top
Dim margemDir As Single = Relatorio.MarginBounds.Right
Dim margemInf As Single = Relatorio.MarginBounds.Bottom
- Fontes : você pode usar a fonte que desejar com tamanho e estilos a sua escolha. Eu vou trabalhar com a fonte Verdana definida para Título , rodapé e normal. Para isto vou usar a classe Font.
Dim fonteTitulo As FontDim fonteRodape As Font
Dim fonteNormal 
As Font
 
fonteTitulo = New Font("Verdana", 15, FontStyle.Bold)
fonteRodape = New Font("Verdana", 8)
fonteNormal = New Font("Verdana", 10)
- Linhas , imagens e texto : Para imprimir linhas , imagens e/ou texto vamos usar os métodos do objeto PrintPageEventArgs(relatorio) conforme abaixo:
- Linhas - Para imprimir um a linha reta entre dois pontos usa o método : relatorio.Graphics.DrawLine - DrawLine ( pen , point1, point2) :
- Imagens - Para imprimir uma imagem (bmp,wmf, gif ou jpg) usamos o método: relatorio.Graphics.DrawImage - DrawImage(image,point):
- Textos - Para imprimir uma string usamos o método : relatorio.Graphics.DrawString - DrawString( string ,font, brush, point, format)
Então a seguir vou mostrar como podemos imprimir as secções do relatório usando os métodos e propriedades já vistos até aqui.
- Impressão do cabeçalho: Abaixo temos uma imagem do cabeçalho que desejamos:

e a seguir o código que implementa isto :
Relatorio.Graphics.DrawLine(caneta, margemEsq, 60, margemDir, 60)
Relatorio.Graphics.DrawImage(Image.FromFile("c:\teste\" & "maco10.gif"), 100, 68)
Relatorio.Graphics.DrawLine(caneta, margemEsq, 160, margemDir, 160)
Relatorio.Graphics.DrawString("Catálogo de Produtos", fonteTitulo, Brushes.Blue, margemEsq + 275, 80, New StringFormat())
- Impressão dos títulos das colunas do relatório.
O título das colunas irá exibir os nomes : Código , Produto e Preço no relatório. O código esta abaixo:
| 'impressão do titulo das colunas Relatorio.Graphics.DrawString("Código", fonteColuna, Brushes.Red, margemEsq, 140, New StringFormat()) Relatorio.Graphics.DrawString("Produto", fonteColuna, Brushes.Red, margemEsq + 100, 140, New StringFormat()) Relatorio.Graphics.DrawString("Preço", fonteColuna, Brushes.Red, margemEsq + 500, 140, New StringFormat()) Relatorio.Graphics.DrawLine(caneta, margemEsq, 170, margemDir, 170) 
 | 
o resultado é :
|  | 
| Nota 
    : As variáveis leitor e paginaAtual declaradas de forma serem 
    visíveis pelo projeto: Private leitor As OleDbDataReader Private paginaAtual As Integer = 1 | 
Precisamos definir o número de linhas por página conforme a área de impressão , assim poderemos ajeitar o rodapé na página. O código para isto é o seguinte:
| 'define o número de linhas por página
    
    
    'para isto faço a divisão da área de 
    impressão pelo tamanho da fonte subtraido do valor 10 linhasPorPagina = Relatorio.MarginBounds.Height / fonteNormal.GetHeight(Relatorio.Graphics) - 10 | 
Vamos a seguir atribuir os dados da fonte de dados para as variáveis já definidas
| 'para imprimir os dados da base de dados no 
    relatório teremos que atribuir as variáveis os valores 
    
    
    'através do objeto OleDbDatareader codigoProduto = leitor(0) 'codigo do produto nomeProduto = leitor(1) 'nome do produto precoProduto = leitor(2) 'preco do produto 
 | 
Finalmente iremos iniciar o laço While/End e ler enquanto a linha atual for menor que o número de linhas por página e houver dados. Quando o número de linhas atual for maior que o permitido iremos criar o rodapé , verificar se ainda há dados para serem lidos e , se for o caso , continuar a impressão na próxima página.
| 'agora vamos dar um laço através dos registros do DataReader levando em conta o número de linhas'permitido para a página. Enquanto a linha atual for menor que o número de linhas por página e não 'final de arquivo estaremo no loop While (linhaAtual < linhasPorPagina And leitor.Read()) 
 'acompanha a posição da linha atual posicaoDaLinha = margemSup + (linhaAtual * fonteNormal.GetHeight(Relatorio.Graphics)) 
 'imprime os dados relativo ao codigo , nome do produto e preço do produto Relatorio.Graphics.DrawString(codigoProduto, fonteNormal, Brushes.Black, margemEsq, posicaoDaLinha, New StringFormat()) Relatorio.Graphics.DrawString(nomeProduto, fonteNormal, Brushes.Black, margemEsq + 100, posicaoDaLinha, New StringFormat()) Relatorio.Graphics.DrawString(FormatCurrency(precoProduto), fonteNormal, Brushes.Black, margemEsq + 500, posicaoDaLinha, New StringFormat()) 
 'faz o incremento no número de linha linhaAtual += 1 
 'verifica se ainda podemos imprimir , ou seja , se a linha atual é menor que o número'de linhas permitido pela página. Se for continuamos a atribuir os dados e a imprimir If (linhaAtual < linhasPorPagina) Then codigoProduto = leitor(0) 'codigo do produto nomeProduto = leitor(1) 'nome do produto precoProduto = leitor(2) 'preco do produtoEnd If End While 'imprime o rodape no relatorio Relatorio.Graphics.DrawLine(caneta, margemEsq, margemInf, margemDir, margemInf) Relatorio.Graphics.DrawString(System.DateTime.Now, fonteRodape, Brushes.Black, margemEsq, margemInf, New StringFormat()) Relatorio.Graphics.DrawString("Pag. " & paginaAtual.ToString, fonteRodape, Brushes.Black, margemDir - 50, margemInf, New StringFormat()) 'incrementa a página atual paginaAtual += 1 
 'vamos verificar se ainda existem registros para serem impressos If (leitor(0) <> Nothing) Then Relatorio.HasMorePages = True Else Relatorio.HasMorePages = False End If End Sub 
 | 
O resultado da visualização da impressão será o seguinte :
|  | 
Usando as classes do System.Drawing.Printing podemos criar relatórios completos com recursos de visualização sem precisar recorrer a soluções de terceiros.
Até o próximo 
artigo VB.NET 
Referências:
| Veja os 
    Destaques e novidades do SUPER DVD Visual Basic 
(sempre atualizado) : clique e confira ! Quer migrar para o VB .NET ? 
 Quer aprender C# ?? 
 Quer aprender os conceitos da Programação Orientada a objetos ? Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ? | 
  Gostou ?   Compartilhe no Facebook
Compartilhe no Facebook
  
 Compartilhe no Twitter
 
Compartilhe no Twitter 
Referências: