Então vamos lá, Crie um novo projeto Windows Form, de um duplo click no formulário e declare um DataTable global para o form e no evento form_load digite o código abaixo, ele não tem nada haver com a impressão em si, ele e apenas para gera um DataTable com dados fictícios para o exemplo que usaremos na listagem que iremos criar, Em um projeto real ele deve ser substituído por um DataTable, DataSet ou DataReader em fim pela fonte de dados que você estiver usando.
Private tbDados As DataTable
Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim Nomes As String() = {"Maria", "João", "José", "Joaquim", "Joana", "Valdete", "Joca", "Rosa"}
Me.tbDados = New DataTable
Me.tbDados.Columns.Add("id")
Me.tbDados.Columns.Add("Nome")
While tbDados.Rows.Count <>
Dim dr As DataRow
dr = tbDados.NewRow
dr("id") = tbDados.Rows.Count + 1
Randomize()
dr("Nome") = Nomes(CInt(Int((8 * Rnd())))) & " " & Nomes(CInt(Int((8 * Rnd()))))
tbDados.Rows.Add(dr)
End While
End Sub
Agora sim, arraste da toolbox o PrintDocument para o seu projeto. E de um duplo click nele, se abrira para você o evento PrintPage, e aqui que se concentra a parte mais importante do codigo, na verdade toda a pagina sera construida aqui, este evento e disparado a cada nova pagina a ser construida no relatorio.
Antes de proceguir vamos ver este evento mas de perto, o evente args dele e o que vamos manipular para dizer ao componente o que queremos que apareça na pagina.O principal metodo que iremos usar e o Graphics.DrawString que tem a seguinte estrutura
e.Graphics.DrawString(<texto que sera impresso><Posição vertical do texto><posição horizontal do texto>)
Outra metodo importante e o HasMorePages, nele informas se haverar mais paginas ou não, se verdadeiro ele disparar novamente o evento para imprimirmos a proxima pagina, se falso ele saira do evento e ira inserar o relatorio.
Temos mas alguns metodos mas acredito que poderemos falar deles no momento em que forem usados.
Bem, creio que você notou que temos que controlar qual e a posição do relatorio que estamos, então para isto iremos criar duas propriedades X e Y no nosso formulario.
Private _X As Single
Private _Y As Single
Public Property X() As Single
Get
Return _X
End Get
Set(ByVal value As Single)
_X = value
End Set
End Property
Public Property Y() As Single
Get
Return _Y
End Get
Set(ByVal value As Single)
_Y = value
End Set
End Property
Só que tem um detalhe a nossa impressão começa a partir da margem superior, para não termos que ficar calculando esta distancia a toda hora vamos fazer a nossa propriedade Y já trazer esta valor calculado, para isto vamos criar mas uma propriedade que vai quardar a margem superior e fazermos uma pequena modificação na propriedade Y.
Private _MargemSuperior As Single
Property MargemSuperior() As Single
Get
Return _MargemSuperior
End Get
Set(ByVal value As Single)
_MargemSuperior = value
End Set
End Property
'Propriedade Y Rescrita
Public Property Y() As Single
Get
Return _Y + Me.MargemSuperior
End Get
Set(ByVal value As Single)
_Y = value - Me.MargemSuperior
End Set
End Property
O que fizemos aqui e quarda o valor da posição de Y onde estamos, na leirura que acrescento o valor da margem superior e na leitura eu disconto este valor, por que na maioria das vezes a posição Y vai ser acrescida do proprio valor, assim mantemos o valor correto.
Agora temos que ter uma maneira de sabermos em que registro estamos,como já que o evento sera disparado a cada pagina, você pode usar BindingContext para navegar os registros, mas optei por criar uma propriedade para guardar esta posição por achar mas facil de entender, então vamos criar a propriedade registro.
Private _Registro As Integer
Private Property Registro() As Integer
Get
Return _Registro
End Get
Set(ByVal value As Integer)
_Registro = value
End Set
End Property
Ainda temos a fonte do padrão do relatorio, vou criar mais uma propriedade para a fonte, que sera usada no relatorio.
Private _Fonte As System.Drawing.Font
Public Property Fonte() As System.Drawing.Font
Get
If IsNothing(_Fonte) Then
_Fonte = New System.Drawing.Font("Verdana", 10)
End If
Return _Fonte
End Get
Set(ByVal value As System.Drawing.Font)
_Fonte = value
End Set
End Property
Nada Imprede você de mudar de fonte, o codigo acho que alto explicativo, a fonte e solicitada se não estiver estanciada estanciamos uma fonte verdade de tamanho 10, bem simples.
Agora escreva este codigo no evento PrintPage
Private Sub PrintDocument1_PrintPage(ByVal sender As System.Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
Me.MargemSuperior = e.MarginBounds.Top
Me.X = e.MarginBounds.Left
While Me.Registro <= Me.tbDados.Rows.Count - 1
e.Graphics.DrawString(Me.tbDados.Rows(Me.Registro)("ID") & " - " & Me.tbDados.Rows(Me.Registro)("
Me.Y += Me.Fonte.Height
Me.Registro += 1
If Me.Y > (e.MarginBounds.Bottom - (Me.Fonte.Height + 3)) AndAlso (Me.Registro <= Me.tbDados.Rows.Count - 1) Then
Me.Y = Me.MargemSuperior
e.HasMorePages = True
Exit Sub
End If
End While
e.HasMorePages = False
End Sub
Com este codigo você já vai poder criar a listagem, deixa eu distacar algumas partes.
Neste trecho estamos pegando a margem superior do relatorio,e passando para a propriedade MargemSuperior, assim não teremos que ficar escrevendo toda hora o codigo de Y+margem , atraves do metodo MarginBounds do EventArgs temos acesso as informações das area util da pagina, ou seja o tamanho da pagina discontada as margens, e as proprias margens.
Me.MargemSuperior = e.MarginBounds.Top
E neste pegamos a posição inicial de X, que e a posição vertical de impresão do relatorio, pense como colunas.
Me.X = e.MarginBounds.Left
Então finalmente fazemos um loop nos registros do DataTable, para escrever a linha.A parte que efetivamente imprime a linha no relatorio e esta aqui.
e.Graphics.DrawString(Me.tbDados.Rows(Me.Registro)("ID") & " - " & Me.tbDados.Rows(Me.Registro)("NOME"), Me.Fonte, New System.Drawing.SolidBrush(Color.Black), Me.X, Me.Y)
Como mostrado no começo este metodo recebeu o texto, a fonte que usamos no relarorio um objeto SolidBrush para definir a cor, no caso usei a preta, e a posição X e Y em que queremos que o texto seja impresso. Depois acrescentamos a posição Y com a altura usada pela fonte do ultimo texto que escrevemos, assim na proxima passagem o texto ficara emediatamente a abaxo, e ascrecentamos também a propriedade registro para lermos o proximo registro.
Me.Y += Me.Fonte.Height
Me.Registro += 1
O If que vem em seguida determina se chegamos ao final da pagina ou não, se a posição Y for maior que a margem bottom no fim da pagina e não tiver acabado os registro então e porque temos mas paginas a imprimir
Então renicializamos o valor da propriedade Y para o inicio da pagina que e a margem superior e passamos o valor Verdadeiro para HasMorePages sinalizando para o aplicativo que temos mais paginas a imprimir e saimos da procedure, que sera disparada novamente para a impressão da proxima pagina,porem se a margem não for maior então e por que ainda não chegamos ao fim da pagina, e o processamente vai se dar até os registros acabarem, que sera quando a propriedade HasMorePages do eventargs receberar o valor falso sinalizando o fim do relatorio o Me.Fonte.Height + 3, entrou na conta so pra termos uma folga do rodapé.
While Me.Registro <= Me.tbDados.Rows.Count - 1
e.Graphics.DrawString(Me.tbDados.Rows(Me.Registro)("ID") & " - " & Me.tbDados.Rows(Me.Registro)("
Me.Y += Me.Fonte.Height
Me.Registro += 1
If Me.Y > (e.MarginBounds.Bottom - (Me.Fonte.Height + 3)) AndAlso (Me.Registro <= Me.tbDados.Rows.Count - 1) Then
Me.Y = Me.MargemSuperior
e.HasMorePages = True
Exit Sub
End If
End While
e.HasMorePages = False
Neste estante mas um componente vai entrar em cena,e o PrintPreviewDialog ele e o responsavel pela visualização do relatorio, então arraste um botão para o seu formulario e um PrintPreviewDialog e de duplo click no botão e no evento onClick do botão escrava o seguinte codigo.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Me.Registro = 0
Me.PrintPreviewDialog1.Document = Me.PrintDocument1
Me.PrintPreviewDialog1.ShowDialog()
End Sub
O que fizemos aqui e inicializar a propriedade registro como 0, para que a cada novo relatorio a contagem seja reiniciada, depois informamos ao PrintPreviewDialog qual o documento que sera vizualizado por ele, e finalmente mostramos.
O resulta esta bem feio, mas calma, ainda vamos fazer mas algumas coisas.
Private Sub Cabecalho(ByVal e As System.Drawing.Printing.PrintPageEventArgs)
e.Graphics.DrawRectangle(Pens.Red, Me.X, Me.Y, e.MarginBounds.Width, 60)
Me.Y += 0.5
e.Graphics.DrawImage(Image.FromFile("c:\divtopfundo.jpg"), Me.X, Me.Y, e.MarginBounds.Width, 60)
e.Graphics.DrawString(Now.ToString, New Font("Verdana", 10, FontStyle.Bold), New System.Drawing.SolidBrush(Color.White), e.MarginBounds.Width - 80, Me.Y)
Me.Y += 70
Dim pontos As Point() = {New Point(Me.X, Me.Y), New Point(e.MarginBounds.Width, Me.Y)}
e.Graphics.DrawLines(Pens.Black, pontos)
Me.Y += 0.5
e.Graphics.DrawString("Id:
Me.Y += 20
pontos = New Point() {New Point(Me.X, Me.Y), New Point(e.MarginBounds.Width, Me.Y)}
e.Graphics.DrawLines(Pens.Black, pontos)
Me.Y += 10
Me.AlturaDoCabecalho = Me.Y
End Sub
A primeira coisa que fezemos e imprimir um retangulo, com a cor vermelha,o codigo e bem tranguilo, e dizer onde começa o retangulo passando a posição X e Y, e depois dizemos o comprimente que no caso passei o comprimento da area util da pagina e depois a altura do retangulo.E como vamos escrever dentro do retangulo fiz um pegueno ascrecimo na propriedade Y para escrevemos logo a baixo da borda do retangulo
e.Graphics.DrawRectangle(Pens.Red, Me.X, Me.Y, e.MarginBounds.Width, 60)
Me.Y += 0.5
Depois vamos colocar uma imagem no cabeçalho, esta imagem poder ser carregada a partir de um arquivo ou de um streem de memoria no caso da imagem vir do banco,aqui eu fiz direto de um arguivo para o exemplo, no mais ele e igual ao retangulo, passamos X e Y e a area que a imagem ira ocupar, o ideal para que não deforme e que seja o tamanho certo da imagem.
e.Graphics.DrawImage(Image.FromFile("c:\divtopfundo.jpg"), Me.X, Me.Y, e.MarginBounds.Width, 60)
Depois da imagem escrevemos a data corrente, ai não tem novidades a não ser o deslocamento do ponto X.
Depois deslocamos o eixo Y, para depois do quadrados e vamos desenhar uma linha.Aqui tem alguns detalhes, que podem parecer estranha, num primeiro momento, mas que faz muito sentido na verdade.O comando para desenhar linha e e.Graphics.DrawLines(Pens.Black, pontos), como você pode ver, so passamos o labis de cor preta, e os pontos da reta, os pontos de reta nada são do que um array dizendo onde termina e começa cada ponto, isto mesmo pontos de reta igual as aulas de matematica do primario.você pode passar quantos pontos quiser se quiser fazer varias retas que formem uma outra imagem, você pode até mesmo fazer um quandrado,no nosso caso só temos dois pontos de reta o que tem a mesma altura que e eixo Y e que estão em estremidades diferentes que e o eixo X, Então vazemos isto, passamos Y para os dois eixos que e igual, e o eixo X para um e a largura da pagina para o outro. O codigo esta abaixo.
Dim pontos As Point() = {New Point(Me.X, Me.Y), New Point(e.MarginBounds.Width, Me.Y)}
e.Graphics.DrawLines(Pens.Black, pontos)
Depois escrevemos os titulos das colunas, como fizemos para escrever os dados, sem novidades aqui também.E escrevemos uma segundo linha,lembrando de acrescentar a cada linha escrita a propriedade Y, para escrever uma linha embaixo da outra.
Volta ao evento PrintPage e logo acima do while faça a chamada do metodo que gera o cabeçalho
Me.Cabecalho(e)
Agora você já deve poder rodar e gerar o relatorio.que ficara assim.
Esta faltando o Rodapé do relatorio,o codigo agora depois de tudo que já vimos e facil, a única novidade e o calculo do numero de paginas, para calcular isto temos que saber quantos registros nos temos e qual o espaço que eles ocupam, e quanto de espaço sobra na pagina discontando o rodape e o cabeçalho. Falando assim parece complicado mas não e, mas antes vamos fazer alguns ajustes, primeiro vamos criar uma propriedade para quardar o valor do cabeçalho,e quardaremos a posição de Y no momento que o cabeçalho foi impresso, assim se almentarmos o numero de linhas do cabelho ou dimuirmos não teremos problemas com o restante do codigo.
Private _AlturaDoCabecalho As Single
Property AlturaDoCabecalho() As Single
Get
Return _AlturaDoCabecalho
End Get
Set(ByVal value As Single)
_AlturaDoCabecalho = value
End Set
End Property
Declarada a propriedade de Altura do Cabeçalho vamos tabem fazer a propriedade da Pagina Atual, para usarmos no rodape.
Private _PaginaAtual As Integer
Public Property PaginaAtual() As Integer
Get
Return _PaginaAtual
End Get
Set(ByVal value As Integer)
_PaginaAtual = value
End Set
End Property
Agora vamos criar o metodo Rodape, como a maior parte do codigo já e conhecido vou postar direto e comentar.
Private Sub Rodape(ByVal e As System.Drawing.Printing.PrintPageEventArgs)
Me.Y = e.MarginBounds.Bottom - (Me.Fonte.Height + 1)
Dim pontos As Point() = {New Point(Me.X, Me.Y), New Point(e.MarginBounds.Width, Me.Y)}
e.Graphics.DrawLines(Pens.Black, pontos)
Me.Y += 0.5
Dim TotPag As Integer
TotPag = Math.Ceiling(((Me.Fonte.Height * tbDados.Rows.Count) / (e.MarginBounds.Bottom - Me.AlturaDoCabecalho)))
e.Graphics.DrawString("Pagina " & Me.PaginaAtual & " de " & TotPag.ToString, New Font("Verdana", 10, FontStyle.Bold), New System.Drawing.SolidBrush(Color.Blue), Me.X, Me.Y)
Me.Y += Me.Fonte.Height + 0.5
pontos = New Point() {New Point(Me.X, Me.Y), New Point(e.MarginBounds.Width, Me.Y)}
e.Graphics.DrawLines(Pens.Black, pontos)
End Sub
A primeira linha do codigo posicionamos o eixo Y na posição do rodape, isto faro com que o rodape fique no fim da pagina mesmo que os registros termiem no meio dela no casso e a margem inferior menos a linha que foi gasta com o texto somada ao espaço ocupado pelas duas linhas que seram impressas, este codigo deveria ser parametrizado, mas ficaria muito complicado para explicar.Mas acredito que você não tera dificuldades de fazer isto.
Depois vem o nosso velho conhecido codigo de desenhar linha, Agora sim vamos ao calculo do total de paginas.Aqui multiplicamos a altura da fonte que estamos usando pelo numero de registros do banco, assim temos o total de espaço que sera usado pelos registros, depois pegamos a margem de fundo da pagina e tiramos o espaço ocupado pelo cabeçalho, e teremos o espaço que ainda esta sobrando da area util da pagina, dividindo o espaço a ser ocupado pelos registros pelo o que ainda temos para usar na pagina, E passamos este valor para a função Ceiling do namespace Math,para arredondar o decimal sempre para cima, por que se tiver decimal já e sinal que teremos uma outra pagina que não esta completa, e o comportamento natural do inteiro ao receber um numero decimal e arredondar para o inteiro mas proximo, e o que queremos e arredondar sempre para cima.
Feito isto agora e fazer alguns acertos que são os seguintes.volte ao metodo cabecalho e no final atribua o valor de Y para alturacabecalho, para sabermos o tamanho do cabeçalho.
Private Sub Cabecalho(ByVal e As System.Drawing.Printing.PrintPageEventArgs)
e.Graphics.DrawRectangle(Pens.Red, Me.X, Me.Y, e.MarginBounds.Width, 60)
Me.Y += 0.5
e.Graphics.DrawImage(Image.FromFile("c:\divtopfundo.jpg"), Me.X, Me.Y, e.MarginBounds.Width, 60)
e.Graphics.DrawString(Now.ToString, New Font("Verdana", 10, FontStyle.Bold), New System.Drawing.SolidBrush(Color.White), e.MarginBounds.Width - 80, Me.Y)
Me.Y += 70
Dim pontos As Point() = {New Point(Me.X, Me.Y), New Point(e.MarginBounds.Width, Me.Y)}
e.Graphics.DrawLines(Pens.Black, pontos)
Me.Y += 0.5
e.Graphics.DrawString("Id:
Me.Y += 20
pontos = New Point() {New Point(Me.X, Me.Y), New Point(e.MarginBounds.Width, Me.Y)}
e.Graphics.DrawLines(Pens.Black, pontos)
Me.Y += 10
Me.AlturaDoCabecalho = Me.Y 'Acrecente este codigo
end Sub
Depois no PrintPage acrescente a chamada do rodape, antes das saidas do metodo
Private Sub PrintDocument1_PrintPage(ByVal sender As System.Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
Me.MargemSuperior = e.MarginBounds.Top
Me.X = e.MarginBounds.Left
Me.PaginaAtual += 1
Me.Cabecalho(e)
While Me.Registro <= Me.tbDados.Rows.Count - 1
e.Graphics.DrawString(Me.tbDados.Rows(Me.Registro)("ID") &amp;amp;amp; " - " & Me.tbDados.Rows(Me.Registro)("
Me.Y += Me.Fonte.Height
Me.Registro += 1
If Me.Y > (e.MarginBounds.Bottom - (Me.Fonte.Height + 3)) AndAlso (Me.Registro <= Me.tbDados.Rows.Count - 1) Then
Me.Rodape(e) 'Acrecente este codigo
Me.Y = Me.MargemSuperior
e.HasMorePages = True
Exit Sub
End If
End While
Me.Rodape(e) 'Acrescente este codigo
e.HasMorePages = False
End Sub
E por ultimo no botão em em que iremos clicar para chamar nosso relatorio, inicialize a pagina atual
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Me.Registro = 0
Me.PaginaAtual = 0 'Acrecente este codigo
Me.PrintPreviewDialog1.Document = Me.PrintDocument1
Me.PrintPreviewDialog1.ShowDialog()
End Sub
Tai o codigo para gerar relatorio, como falei e um pouco braçal.O melhor mesmo e vocÊ criar classes que herdem deste componente e criar codigos mas eficientes para o controle de colunas margens e tudo mais, para não ter que ficar sempre escrevendo este codigo.Acredito que com o que foi visto aqui da pra dar inicio aos relatorios e evitar os eternos problemas de dll e ocx do Crystal Report. Valeu e até mais o codigo completo esta em outro post.